Checklist: verify that sensor data reaches InfluxDB

New guided check "Sensor data in the database (InfluxDB)", also the last
step of the Quick check on the green path. It queries InfluxDB (read
token) for the newest point per measurement of the box and tells apart:
sensor data arrives / status arrives without sensor data / offline now /
never seen / check not possible. Polls every 30 s for up to 4 minutes.
With a stopped RTC in the log it concludes that sensor readings are lost
(stamped 1 Jan 2000): box A4:CB:8F:C9:47:3C sent only device_status and
microphone.

- service/influx.py: query, CSV parsing, classification (no new deps)
- guides: new step type "influx" (host answers via influx_result)
- Settings > InfluxDB access (or KOTA_INFLUX_TOKEN); token never logged
- service status mapping for tech.influx.* findings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
KrzyszurandClaude Opus 5.5 committed 2026-10-01 00:06:48 +02:00
1 parent 2b6d43eb45
commit 66cb804102
11 files changed
+676 -15

No files matched your search

+68
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
import os
import threading
from pathlib import Path
from PySide6.QtCore import QCoreApplication, QObject, QSettings, Qt, QTimer, QUrl, Signal
@@ -7,10 +9,14 @@ from PySide6.QtGui import QColor, QDesktopServices, QFont, QGuiApplication, QTex
from PySide6.QtWidgets import (
QCheckBox,
QComboBox,
QDialog,
QDialogButtonBox,
QFileDialog,
QFormLayout,
QHBoxLayout,
QInputDialog,
QLabel,
QLineEdit,
QMainWindow,
QMessageBox,
QPlainTextEdit,
@@ -32,6 +38,7 @@ from ..serialio.commands import loggable
from ..serialio.manager import SerialManager, State
from ..serialio.ports import PortInfo, list_serial_ports
from ..matter.payload import SetupPayload, payload_from_log
from ..service import influx
from ..service.report import DEFAULT_BASE, normalize_mac, report_url
from .cheatsheet import cheatsheet_html
from .diag_panel import DiagnosticsPanel
@@ -63,6 +70,7 @@ class _Bridge(QObject):
line = Signal(object)
state = Signal(object, str)
influx = Signal(object) # InfluxCheck from the query thread
class SerialTab(QWidget):
@@ -71,6 +79,7 @@ class SerialTab(QWidget):
self._bridge = _Bridge()
self._bridge.line.connect(self._on_line)
self._bridge.state.connect(self._on_state)
self._bridge.influx.connect(self._on_influx)
self._manager = SerialManager(self._bridge.line.emit, self._bridge.state.emit)
self._session = SessionLog()
self._ports: list[PortInfo] = []
@@ -377,8 +386,33 @@ class SerialTab(QWidget):
if action == "reset" and self._manager.state == State.CONNECTED:
self._manager.reset_board()
return True
if action == "influx":
cfg = self.influx_config()
mac = self._engine.mac or (self._active_port.mac if self._active_port else "") or ""
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)),
name="influx-check", daemon=True).start()
return True
return False
def influx_config(self) -> influx.InfluxConfig | None:
"""Read token: KOTA_INFLUX_TOKEN, else the tool settings. None when not set up."""
token = os.environ.get("KOTA_INFLUX_TOKEN") or str(self._settings.value("influx/token", ""))
if not token:
return None
return influx.InfluxConfig(
token=token,
url=str(self._settings.value("influx/url", influx.DEFAULT_URL)),
org=str(self._settings.value("influx/org", influx.DEFAULT_ORG)),
bucket=str(self._settings.value("influx/bucket", influx.DEFAULT_BUCKET)))
def _on_influx(self, check: influx.InfluxCheck) -> None:
self._runner.influx_result(check.outcome, check.summary, check.details)
self._diag_dirty = True
self.guide.refresh()
def _guide_log(self, text: str) -> None:
# Technician answers go into the session log and the log view.
line = host_event(text)
@@ -447,6 +481,7 @@ class MainWindow(QMainWindow):
self.setCentralWidget(tabs)
settings_menu = self.menuBar().addMenu("Settings")
settings_menu.addAction("Service system address...", self._edit_service_base)
settings_menu.addAction("InfluxDB access...", self._edit_influx)
def _edit_service_base(self) -> None:
st = self.serial_tab
@@ -459,6 +494,39 @@ class MainWindow(QMainWindow):
elif ok:
QMessageBox.warning(self, "Service system address", "The address must start with https://")
def _edit_influx(self) -> None:
st = self.serial_tab._settings
dlg = QDialog(self)
dlg.setWindowTitle("InfluxDB access")
form = QFormLayout(dlg)
form.addRow(QLabel("Used by the check 'Sensor data in the database'. Ask the firmware team for a "
"<b>read-only</b> token for the bucket.", wordWrap=True))
url = QLineEdit(str(st.value("influx/url", influx.DEFAULT_URL)))
org = QLineEdit(str(st.value("influx/org", influx.DEFAULT_ORG)))
bucket = QLineEdit(str(st.value("influx/bucket", influx.DEFAULT_BUCKET)))
token = QLineEdit(echoMode=QLineEdit.EchoMode.Password)
token.setPlaceholderText("stored - leave empty to keep" if st.value("influx/token", "")
else "paste the read token")
clear = QCheckBox("Remove the stored token")
for label, w in (("Address", url), ("Organisation", org), ("Bucket", bucket), ("Token", token), ("", clear)):
form.addRow(label, w)
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
buttons.accepted.connect(dlg.accept)
buttons.rejected.connect(dlg.reject)
form.addRow(buttons)
if dlg.exec() != QDialog.DialogCode.Accepted:
return
if not url.text().strip().startswith("https://"):
QMessageBox.warning(self, "InfluxDB access", "The address must start with https://")
return
st.setValue("influx/url", url.text().strip())
st.setValue("influx/org", org.text().strip() or influx.DEFAULT_ORG)
st.setValue("influx/bucket", bucket.text().strip() or influx.DEFAULT_BUCKET)
if clear.isChecked():
st.remove("influx/token")
elif token.text().strip():
st.setValue("influx/token", token.text().strip())
def closeEvent(self, event): # noqa: N802 (Qt naming)
self.serial_tab.shutdown()
super().closeEvent(event)