Serial monitor redesign: Test box button, InfluxDB row in the checklist
- Test box: restart + start-up capture + online InfluxDB check (data newer than the restart), with a status strip; checklist and findings are the main view, live log / guided troubleshooting / box details are tabs, rarely used actions in a More menu - light palette on all systems (macOS dark mode made the colours unreadable) - new rule influx.invalid_value: -inf from a silent microphone makes InfluxDB reject every upload (HTTP 400); guide end names it - exclusive serial port on macOS/Linux: two tool windows no longer split the log between them - InfluxDB check: `since` filter, neutral wording when nothing new arrives Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
66cb804102
commit
bb933ff748
13 files changed
+422
-83
No files matched your search
+55
-19
@@ -1,9 +1,11 @@
|
||||
"""Diagnostics side panel: renders a diag.engine.Report.
|
||||
|
||||
Layout: verdict banner, expected LED (colour + pattern + meaning when the
|
||||
box reports it), firmware, the box's own self-report (KOTA lines), start-up
|
||||
milestones, a findings tree (root causes; their consequences as children;
|
||||
titles only) and a wrapped detail pane for the selected finding.
|
||||
Layout: verdict banner, the test checklist (start-up milestones plus the
|
||||
online InfluxDB check), a findings tree (root causes; their consequences as
|
||||
children; titles only) and a wrapped detail pane for the selected finding.
|
||||
`details` is a separate widget for the host to place: expected LED (colour +
|
||||
pattern + meaning when the box reports it), firmware and the box's own
|
||||
self-report (KOTA lines).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -17,6 +19,7 @@ from PySide6.QtWidgets import (
|
||||
QListWidget,
|
||||
QListWidgetItem,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
QSplitter,
|
||||
QTextBrowser,
|
||||
@@ -43,13 +46,19 @@ BANNER = {
|
||||
}
|
||||
_KEY = Qt.ItemDataRole.UserRole
|
||||
BOX_STATE = {"ok": ("#1b7a33", "✔"), "warn": ("#c77c02", "!"), "bad": ("#b03a2e", "✖"), "info": ("#2e6da4", "i")}
|
||||
# Extra checklist rows (e.g. the InfluxDB check): state -> (mark, colour)
|
||||
ROW_STATE = {"ok": ("✔", "#1b7a33"), "bad": ("✖", "#b03a2e"), "warn": ("!", "#a86400"),
|
||||
"running": ("…", "#2e6da4"), "pending": ("○", "#6b6b6b"), "off": ("–", "#6b6b6b"),
|
||||
"note": ("", "#444444")}
|
||||
|
||||
|
||||
def _heading(text: str) -> QLabel:
|
||||
lbl = QLabel(text)
|
||||
f = lbl.font()
|
||||
f.setBold(True)
|
||||
f.setPointSize(f.pointSize() + 1)
|
||||
lbl.setFont(f)
|
||||
lbl.setContentsMargins(0, 6, 0, 2)
|
||||
return lbl
|
||||
|
||||
|
||||
@@ -87,7 +96,14 @@ class DiagnosticsPanel(QWidget):
|
||||
self.milestones = QListWidget()
|
||||
self.milestones.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.milestones.setSelectionMode(QListWidget.SelectionMode.NoSelection)
|
||||
self.milestones.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||
self.milestones.setWordWrap(True)
|
||||
self.milestones.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
self.milestones.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum)
|
||||
self.milestones.setStyleSheet("QListWidget { background: transparent; } QListWidget::item { padding: 2px; }")
|
||||
mf = self.milestones.font()
|
||||
mf.setPointSize(mf.pointSize() + 1)
|
||||
self.milestones.setFont(mf)
|
||||
|
||||
self.tree = QTreeWidget()
|
||||
self.tree.setHeaderHidden(True)
|
||||
@@ -101,22 +117,31 @@ class DiagnosticsPanel(QWidget):
|
||||
findings.setSizes([220, 260])
|
||||
|
||||
lay = QVBoxLayout(self)
|
||||
lay.setContentsMargins(4, 0, 0, 0)
|
||||
lay.setContentsMargins(4, 0, 4, 0)
|
||||
lay.addWidget(self.verdict)
|
||||
lay.addWidget(_heading("Status LED should be"))
|
||||
lay.addLayout(led_row)
|
||||
lay.addWidget(_heading("Firmware"))
|
||||
lay.addWidget(self.firmware)
|
||||
self.qr_btn = QPushButton("Matter pairing QR code...")
|
||||
self.qr_btn.setToolTip("Show the box's Matter QR code on screen to scan with the phone")
|
||||
lay.addWidget(self.qr_btn, 0, Qt.AlignmentFlag.AlignLeft)
|
||||
lay.addWidget(self.box_title)
|
||||
lay.addWidget(self.box)
|
||||
lay.addWidget(_heading("Start-up progress (current boot)"))
|
||||
lay.addWidget(_heading("Test checklist (current boot)"))
|
||||
lay.addWidget(self.milestones)
|
||||
lay.addWidget(_heading("Findings - root causes first; click one for details"))
|
||||
lay.addWidget(findings, 1)
|
||||
|
||||
# Box details: placed by the host (a separate tab).
|
||||
inner = QWidget()
|
||||
dl = QVBoxLayout(inner)
|
||||
dl.addWidget(_heading("Status LED should be"))
|
||||
dl.addLayout(led_row)
|
||||
dl.addWidget(_heading("Firmware"))
|
||||
dl.addWidget(self.firmware)
|
||||
self.qr_btn = QPushButton("Matter pairing QR code...")
|
||||
self.qr_btn.setToolTip("Show the box's Matter QR code on screen to scan with the phone")
|
||||
dl.addWidget(self.qr_btn, 0, Qt.AlignmentFlag.AlignLeft)
|
||||
dl.addWidget(self.box_title)
|
||||
dl.addWidget(self.box)
|
||||
dl.addStretch(1)
|
||||
self.details = QScrollArea()
|
||||
self.details.setWidgetResizable(True)
|
||||
self.details.setFrameShape(QFrame.Shape.NoFrame)
|
||||
self.details.setWidget(inner)
|
||||
|
||||
self._findings: dict[str, Finding] = {}
|
||||
self._selected: str | None = None
|
||||
# key -> expanded, chosen by the user; survives the rebuild on refresh.
|
||||
@@ -126,7 +151,8 @@ class DiagnosticsPanel(QWidget):
|
||||
self.tree.itemExpanded.connect(lambda it: self._remember(it, True))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def show_report(self, r: Report) -> None:
|
||||
def show_report(self, r: Report, extra_rows: list[tuple[str, str]] = ()) -> None:
|
||||
"""extra_rows: (state, text) appended to the checklist, state in ROW_STATE."""
|
||||
bg, fg = BANNER.get(r.verdict_severity, BANNER[Severity.WARNING])
|
||||
if r.lines == 0:
|
||||
bg, fg = "#e6e6e6", "#333333"
|
||||
@@ -161,10 +187,20 @@ class DiagnosticsPanel(QWidget):
|
||||
done = t is not None
|
||||
it = QListWidgetItem(("✔ " if done else "○ ") + label
|
||||
+ (f" ({t / 1000:.1f} s)" if done else ""))
|
||||
it.setForeground(QBrush(QColor("#1b7a33" if done else "#8a8a8a")))
|
||||
it.setForeground(QBrush(QColor("#1b7a33" if done else "#6b6b6b")))
|
||||
self.milestones.addItem(it)
|
||||
rows = min(max(self.milestones.count(), 1), 15)
|
||||
self.milestones.setFixedHeight(rows * max(self.milestones.sizeHintForRow(0), 16) + 6)
|
||||
for state, text in extra_rows:
|
||||
mark, colour = ROW_STATE[state]
|
||||
it = QListWidgetItem(f"{mark} {text}" if mark else text)
|
||||
it.setForeground(QBrush(QColor(colour)))
|
||||
if state != "note":
|
||||
f = it.font()
|
||||
f.setBold(True)
|
||||
it.setFont(f)
|
||||
it.setToolTip(text)
|
||||
self.milestones.addItem(it)
|
||||
height = sum(max(self.milestones.sizeHintForRow(i), 16) for i in range(self.milestones.count()))
|
||||
self.milestones.setFixedHeight(min(max(height, 16), 560) + 6)
|
||||
|
||||
self._fill_tree(r)
|
||||
|
||||
|
||||
+204
-47
@@ -1,7 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QCoreApplication, QObject, QSettings, Qt, QTimer, QUrl, Signal
|
||||
@@ -18,12 +21,14 @@ from PySide6.QtWidgets import (
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QMenu,
|
||||
QMessageBox,
|
||||
QPlainTextEdit,
|
||||
QPushButton,
|
||||
QSplitter,
|
||||
QTabWidget,
|
||||
QTextBrowser,
|
||||
QToolButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
@@ -49,6 +54,13 @@ from .sd_tab import SdTab
|
||||
from .wifi_tab import WifiTab
|
||||
|
||||
MAX_LINES = 20000
|
||||
TEST_INFLUX_AFTER_S = 30 # Test box: start the online check this long after the restart
|
||||
INFLUX_ROW = "Sensor data in InfluxDB (online check)"
|
||||
# test status strip: (background, text colour)
|
||||
STRIP = {"idle": ("#eef1f4", "#333333"), "running": ("#dde9f7", "#123a63"), "ok": ("#d8f0dd", "#1b5e2a"),
|
||||
"info": ("#dde9f7", "#123a63"), "warning": ("#fdebc8", "#6b4400"), "error": ("#f9dedc", "#8a2019"),
|
||||
"critical": ("#f6d5d1", "#7b1e14")}
|
||||
CLOUD_STATE = {"ok": "ok", "sensors_missing": "bad", "silent": "warn", "never": "warn", "unavailable": "off"}
|
||||
# "KOTA" = the firmware's structured diagnostic lines (1.0.16+) plus errors.
|
||||
LEVEL_FILTERS = {"All lines": None, "KOTA diagnostics + errors": "KOTA",
|
||||
"Warnings + errors": {"W", "E"}, "Errors only": {"E"}}
|
||||
@@ -65,6 +77,10 @@ COLORS = {
|
||||
}
|
||||
|
||||
|
||||
def _esc(s: str) -> str:
|
||||
return s.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
class _Bridge(QObject):
|
||||
"""Marshals SerialManager callbacks (reader thread) onto the UI thread."""
|
||||
|
||||
@@ -90,82 +106,127 @@ class SerialTab(QWidget):
|
||||
self._line_listeners: list = [] # other tabs following the live log (Wi-Fi over USB)
|
||||
|
||||
# --- controls
|
||||
self.port_combo = QComboBox(minimumWidth=320)
|
||||
self.all_ports = QCheckBox("Show all ports")
|
||||
self.refresh_btn = QPushButton("Refresh")
|
||||
self._settings = QSettings("KOTA", "TechnicianTool")
|
||||
self.port_combo = QComboBox(minimumWidth=360)
|
||||
self.refresh_btn = QPushButton("↻")
|
||||
self.refresh_btn.setToolTip("Look for boxes again")
|
||||
self.refresh_btn.setFixedWidth(40)
|
||||
self.test_btn = QPushButton("▶ Test box")
|
||||
self.test_btn.setToolTip("Restart the box, check its start-up and whether its sensor data reaches "
|
||||
"InfluxDB (about 4 minutes)")
|
||||
self.test_btn.setMinimumHeight(38)
|
||||
self.test_btn.setStyleSheet(
|
||||
"QPushButton { background:#1f7a3a; color:white; font-weight:bold; font-size:15px; "
|
||||
"padding:4px 22px; border-radius:6px; border:none; }"
|
||||
"QPushButton:hover { background:#25904a; } QPushButton:disabled { background:#9dbfa6; }")
|
||||
self.connect_btn = QPushButton("Connect")
|
||||
self.reset_btn = QPushButton("Reset && capture")
|
||||
self.reset_btn.setToolTip("Reboot the sensorbox and record the log from the first boot line")
|
||||
self.connect_btn.setMinimumHeight(32)
|
||||
|
||||
# Less frequent actions live in the "More" menu.
|
||||
self.more_btn = QToolButton(text="More ▾")
|
||||
self.more_btn.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||
self.more_btn.setMinimumHeight(32)
|
||||
menu = QMenu(self.more_btn)
|
||||
self.reset_btn = menu.addAction("Reset && capture (restart the box, no online check)")
|
||||
menu.addSeparator()
|
||||
self.service_btn = menu.addAction("Service report...")
|
||||
self.service_btn.setToolTip("Open this box's page in the KOTA service system with the diagnosis "
|
||||
"filled in; check it and press Save there")
|
||||
self.copy_btn = menu.addAction("Copy report")
|
||||
self.save_btn = menu.addAction("Save log view as...")
|
||||
self.folder_btn = menu.addAction("Open logs folder")
|
||||
self.analyze_btn = menu.addAction("Analyze log file...")
|
||||
menu.addSeparator()
|
||||
self.auto_service = menu.addAction("Open service report on disconnect")
|
||||
self.auto_service.setCheckable(True)
|
||||
self.auto_service.setChecked(self._settings.value("service/auto", False, type=bool))
|
||||
self.auto_service.toggled.connect(lambda on: self._settings.setValue("service/auto", on))
|
||||
self.all_ports = menu.addAction("Show all serial ports")
|
||||
self.all_ports.setCheckable(True)
|
||||
self.more_btn.setMenu(menu)
|
||||
self._active_port: PortInfo | None = None
|
||||
|
||||
top = QHBoxLayout()
|
||||
box_lbl = QLabel("<b>Box:</b>")
|
||||
for w in (box_lbl, self.port_combo, self.refresh_btn):
|
||||
top.addWidget(w)
|
||||
top.addSpacing(12)
|
||||
top.addWidget(self.test_btn)
|
||||
top.addWidget(self.connect_btn)
|
||||
top.addStretch(1)
|
||||
top.addWidget(self.more_btn)
|
||||
|
||||
# Test status strip (what the test is doing / its result).
|
||||
self.test_status = QLabel(wordWrap=True)
|
||||
self.test_status.setMargin(8)
|
||||
tf = self.test_status.font()
|
||||
tf.setPointSize(tf.pointSize() + 1)
|
||||
self.test_status.setFont(tf)
|
||||
|
||||
# --- live log (a tab on the right)
|
||||
self.filter_combo = QComboBox()
|
||||
self.filter_combo.addItems(LEVEL_FILTERS)
|
||||
self.clear_btn = QPushButton("Clear view")
|
||||
self.save_btn = QPushButton("Save view as...")
|
||||
self.folder_btn = QPushButton("Open logs folder")
|
||||
self.analyze_btn = QPushButton("Analyze log file...")
|
||||
self.analyze_btn.setToolTip("Load a saved session log or serial capture and diagnose it")
|
||||
self.copy_btn = QPushButton("Copy report")
|
||||
self.copy_btn.setToolTip("Copy the diagnosis as text (e.g. to send to the firmware team)")
|
||||
self.service_btn = QPushButton("Service report...")
|
||||
self.service_btn.setToolTip("Open this box's page in the KOTA service system with the diagnosis "
|
||||
"filled in; check it and press Save there")
|
||||
self._settings = QSettings("KOTA", "TechnicianTool")
|
||||
self.auto_service = QCheckBox("Open service report on disconnect")
|
||||
self.auto_service.setChecked(self._settings.value("service/auto", False, type=bool))
|
||||
self.auto_service.toggled.connect(lambda on: self._settings.setValue("service/auto", on))
|
||||
self._active_port: PortInfo | None = None
|
||||
|
||||
row1 = QHBoxLayout()
|
||||
for w in (QLabel("Port:"), self.port_combo, self.all_ports, self.refresh_btn,
|
||||
self.connect_btn, self.reset_btn):
|
||||
row1.addWidget(w)
|
||||
row1.addStretch(1)
|
||||
row2 = QHBoxLayout()
|
||||
for w in (QLabel("Show:"), self.filter_combo, self.clear_btn, self.save_btn, self.folder_btn,
|
||||
self.analyze_btn, self.copy_btn, self.service_btn, self.auto_service):
|
||||
row2.addWidget(w)
|
||||
row2.addStretch(1)
|
||||
|
||||
self.view = QPlainTextEdit(readOnly=True)
|
||||
self.view.setMaximumBlockCount(MAX_LINES)
|
||||
self.view.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
|
||||
mono = QFont("Menlo" if QFont("Menlo").exactMatch() else "Consolas")
|
||||
mono.setStyleHint(QFont.StyleHint.Monospace)
|
||||
mono.setPointSize(10)
|
||||
mono.setPointSize(12 if sys.platform == "darwin" else 10)
|
||||
self.view.setFont(mono)
|
||||
self.view.setStyleSheet("QPlainTextEdit { background:#1e1e1e; color:#d4d4d4; }")
|
||||
log_page = QWidget()
|
||||
ll = QVBoxLayout(log_page)
|
||||
ll.setContentsMargins(0, 4, 0, 0)
|
||||
lrow = QHBoxLayout()
|
||||
for w in (QLabel("Show:"), self.filter_combo, self.clear_btn):
|
||||
lrow.addWidget(w)
|
||||
lrow.addStretch(1)
|
||||
ll.addLayout(lrow)
|
||||
ll.addWidget(self.view, 1)
|
||||
|
||||
self.diag = DiagnosticsPanel()
|
||||
self.guide = GuidePanel(self._runner, self._engine, matter_payload=self.matter_payload)
|
||||
self.side_tabs = QTabWidget()
|
||||
self.side_tabs.addTab(self.diag, "Diagnosis")
|
||||
self.side_tabs.addTab(log_page, "Live log")
|
||||
self.side_tabs.addTab(self.guide, "Guided troubleshooting")
|
||||
self.side_tabs.addTab(self.diag.details, "Box details")
|
||||
split = QSplitter(Qt.Orientation.Horizontal)
|
||||
split.addWidget(self.view)
|
||||
split.addWidget(self.diag)
|
||||
split.addWidget(self.side_tabs)
|
||||
split.setStretchFactor(0, 3)
|
||||
split.setStretchFactor(1, 2)
|
||||
split.setSizes([700, 480])
|
||||
split.setStretchFactor(0, 1)
|
||||
split.setStretchFactor(1, 1)
|
||||
split.setSizes([560, 620])
|
||||
|
||||
self.status = QLabel()
|
||||
self.status.setStyleSheet("color:#555")
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addLayout(row1)
|
||||
layout.addLayout(row2)
|
||||
layout.addLayout(top)
|
||||
layout.addWidget(self.test_status)
|
||||
layout.addWidget(split, 1)
|
||||
layout.addWidget(self.status)
|
||||
|
||||
# --- wiring
|
||||
self.refresh_btn.clicked.connect(self.refresh_ports)
|
||||
self.all_ports.toggled.connect(self.refresh_ports)
|
||||
self.test_btn.clicked.connect(self._test_box)
|
||||
self.connect_btn.clicked.connect(self._toggle_connect)
|
||||
self.reset_btn.clicked.connect(self._manager.reset_board)
|
||||
self.reset_btn.triggered.connect(self._manager.reset_board)
|
||||
self.clear_btn.clicked.connect(self.view.clear)
|
||||
self.save_btn.clicked.connect(self._save_view)
|
||||
self.folder_btn.clicked.connect(self._open_folder)
|
||||
self.analyze_btn.clicked.connect(self._analyze_file)
|
||||
self.copy_btn.clicked.connect(self._copy_report)
|
||||
self.service_btn.clicked.connect(lambda: self._open_service_report(auto=False))
|
||||
self.save_btn.triggered.connect(self._save_view)
|
||||
self.folder_btn.triggered.connect(self._open_folder)
|
||||
self.analyze_btn.triggered.connect(self._analyze_file)
|
||||
self.copy_btn.triggered.connect(self._copy_report)
|
||||
self.service_btn.triggered.connect(lambda: self._open_service_report(auto=False))
|
||||
self.diag.qr_btn.clicked.connect(lambda: MatterQrDialog(self.matter_payload(), self).exec())
|
||||
|
||||
# Test box state: None = no test; else monotonic start time.
|
||||
self._test_t0: float | None = None
|
||||
self._test_since: dt.datetime | None = None # UTC; InfluxDB data must be newer
|
||||
self._influx_started = False
|
||||
self._cloud: influx.InfluxCheck | None = None # last InfluxDB answer
|
||||
self._cloud_checking = False
|
||||
|
||||
# Report refresh is throttled; lines can arrive at >100/s.
|
||||
self._diag_timer = QTimer(self, interval=400)
|
||||
self._diag_timer.timeout.connect(self._refresh_diag)
|
||||
@@ -221,8 +282,98 @@ class SerialTab(QWidget):
|
||||
self._diag_dirty = True
|
||||
self._session.start(port.mac or port.device)
|
||||
self._active_port = port
|
||||
self._cloud, self._cloud_checking = None, False
|
||||
self._manager.connect(port)
|
||||
|
||||
# ---- Test box ----------------------------------------------------------
|
||||
def _test_box(self) -> None:
|
||||
"""Restart the box, record its start-up, then check InfluxDB online."""
|
||||
if self._manager.is_running:
|
||||
self._counts = {"E": 0, "W": 0, "boots": 0}
|
||||
self._engine.reset()
|
||||
self._runner.cancel()
|
||||
self._cloud, self._cloud_checking = None, False
|
||||
self._manager.reset_board()
|
||||
else:
|
||||
port = self._selected_port() or next((p for p in self._ports if p.is_sensorbox), None)
|
||||
if port is None:
|
||||
QMessageBox.information(self, "Test box", "No sensorbox found. Check the USB cable (data cable) "
|
||||
"and that the box's power switch is ON, then press ↻.")
|
||||
return
|
||||
self.connect_port(port, capture=True)
|
||||
self._test_t0 = time.monotonic()
|
||||
self._test_since = dt.datetime.now(dt.timezone.utc)
|
||||
self._influx_started = False
|
||||
self._test_end = None
|
||||
self._diag_dirty = True
|
||||
self._guide_log("Test box started: restarting the box and recording its start-up")
|
||||
|
||||
def _tick_test(self) -> None:
|
||||
if self._test_t0 is None:
|
||||
return
|
||||
self._diag_dirty = True # countdowns in the strip and the checklist
|
||||
if not self._manager.is_running:
|
||||
self._finish_test(None, "Box disconnected - test stopped.")
|
||||
return
|
||||
elapsed = time.monotonic() - self._test_t0
|
||||
if not self._influx_started and elapsed >= TEST_INFLUX_AFTER_S and self.is_connected:
|
||||
self._influx_started = True
|
||||
self._runner.start("influx") # polls, routes and records findings like a guided check
|
||||
self.guide.refresh()
|
||||
elif self._influx_started and (self._runner.end is not None or self._runner.guide is None):
|
||||
self._finish_test(self._runner.end)
|
||||
|
||||
def _finish_test(self, end, text: str = "") -> None:
|
||||
self._test_t0 = None
|
||||
self._test_since = None
|
||||
self._test_end = end
|
||||
self._test_text = text
|
||||
self._guide_log("Test box finished: " + (end.title if end else text or "stopped"))
|
||||
|
||||
def _cloud_rows(self) -> list[tuple[str, str]]:
|
||||
in_check = self._runner.step is not None and self._runner.step.type == "influx"
|
||||
if self.influx_config() is None:
|
||||
return [("off", f"{INFLUX_ROW}: not set up - Settings > InfluxDB access")]
|
||||
if in_check:
|
||||
left = int(self._runner.view().remaining_s)
|
||||
last = f" - last answer: {self._cloud.summary}" if self._cloud else ""
|
||||
rows = [("running", f"{INFLUX_ROW}: checking... {left // 60}:{left % 60:02d} left{last}")]
|
||||
elif self._cloud is not None:
|
||||
rows = [(CLOUD_STATE.get(self._cloud.outcome, "warn"), f"{INFLUX_ROW}: {self._cloud.summary}")]
|
||||
else:
|
||||
when = (f"starts {TEST_INFLUX_AFTER_S} s after the restart" if self._test_t0 is not None
|
||||
else "press Test box")
|
||||
return [("pending", f"{INFLUX_ROW} - {when}")]
|
||||
if self._cloud is not None:
|
||||
rows += [("note", f" {d}") for d in self._cloud.details]
|
||||
return rows
|
||||
|
||||
def _update_test_strip(self) -> None:
|
||||
if self._test_t0 is not None:
|
||||
el = int(time.monotonic() - self._test_t0)
|
||||
if self._influx_started:
|
||||
doing = "checking that the data reaches InfluxDB - " + (
|
||||
self._runner.view().note or "waiting for the first answer")
|
||||
else:
|
||||
ms = self._engine.report().milestones
|
||||
done = sum(t is not None for _, _, t in ms)
|
||||
doing = f"recording the start-up ({done} of {len(ms)} checklist steps done)"
|
||||
key, text = "running", f"<b>Test running {el // 60}:{el % 60:02d}</b> - {_esc(doing)}"
|
||||
elif getattr(self, "_test_end", None) is not None:
|
||||
e = self._test_end
|
||||
key = e.severity
|
||||
text = (f"<b>Test finished: {_esc(e.title)}</b><br>{_esc(e.text)} "
|
||||
"Details: Findings below and the <i>Guided troubleshooting</i> tab.")
|
||||
elif getattr(self, "_test_text", ""):
|
||||
key, text = "warning", f"<b>{_esc(self._test_text)}</b>"
|
||||
else:
|
||||
key = "idle"
|
||||
text = ("Press <b>▶ Test box</b>: the tool restarts the box, checks its start-up (sensors, SD card, "
|
||||
"Wi-Fi, clock) and then whether its sensor data reaches InfluxDB. Takes about 4 minutes.")
|
||||
bg, fg = STRIP[key]
|
||||
self.test_status.setStyleSheet(f"QLabel {{ background:{bg}; color:{fg}; border-radius:6px; }}")
|
||||
self.test_status.setText(text)
|
||||
|
||||
def matter_payload(self) -> SetupPayload:
|
||||
"""Pairing payload reported by this box (KOTA: matter), from its log, or the defaults."""
|
||||
return payload_from_log([self._engine.matter_advert] if self._engine.matter_advert else [],
|
||||
@@ -317,6 +468,7 @@ class SerialTab(QWidget):
|
||||
self._session.stop()
|
||||
self.connect_btn.setText("Disconnect" if running else "Connect")
|
||||
self.reset_btn.setEnabled(state == State.CONNECTED)
|
||||
self.test_btn.setEnabled(state != State.WAITING)
|
||||
self.port_combo.setEnabled(not running)
|
||||
self.all_ports.setEnabled(not running)
|
||||
self.refresh_btn.setEnabled(not running)
|
||||
@@ -376,9 +528,11 @@ class SerialTab(QWidget):
|
||||
|
||||
# ---- diagnostics -------------------------------------------------
|
||||
def _refresh_diag(self) -> None:
|
||||
self._tick_test()
|
||||
if self._diag_dirty:
|
||||
self._diag_dirty = False
|
||||
self.diag.show_report(self._engine.report())
|
||||
self.diag.show_report(self._engine.report(), self._cloud_rows())
|
||||
self._update_test_strip()
|
||||
self.guide.refresh() # countdowns and timeouts need a steady tick
|
||||
|
||||
# ---- guided troubleshooting hooks ------------------------------------
|
||||
@@ -392,7 +546,9 @@ class SerialTab(QWidget):
|
||||
if cfg is None:
|
||||
self._runner.note = "InfluxDB access is not set up: Settings > InfluxDB access."
|
||||
return False
|
||||
threading.Thread(target=lambda: self._bridge.influx.emit(influx.check_device(cfg, mac)),
|
||||
since = self._test_since
|
||||
self._cloud_checking = True
|
||||
threading.Thread(target=lambda: self._bridge.influx.emit(influx.check_device(cfg, mac, since=since)),
|
||||
name="influx-check", daemon=True).start()
|
||||
return True
|
||||
return False
|
||||
@@ -409,6 +565,7 @@ class SerialTab(QWidget):
|
||||
bucket=str(self._settings.value("influx/bucket", influx.DEFAULT_BUCKET)))
|
||||
|
||||
def _on_influx(self, check: influx.InfluxCheck) -> None:
|
||||
self._cloud, self._cloud_checking = check, False
|
||||
self._runner.influx_result(check.outcome, check.summary, check.details)
|
||||
self._diag_dirty = True
|
||||
self.guide.refresh()
|
||||
|
||||
Reference in new issue
Block a user