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:
1 parent
2b6d43eb45
commit
66cb804102
11 files changed
+676
-15
No files matched your search
@@ -167,7 +167,8 @@ class GuidePanel(QWidget):
|
||||
self.wait_bar.setMaximum(int(total))
|
||||
self.wait_bar.setValue(int(total - v.remaining_s))
|
||||
m, s = divmod(int(v.remaining_s), 60)
|
||||
self.wait_label.setText(f"Watching the live log... {m}:{s:02d} left")
|
||||
doing = "Checking InfluxDB" if v.step.type == "influx" else "Watching the live log"
|
||||
self.wait_label.setText(f"{doing}... {m}:{s:02d} left")
|
||||
self.note.setText(v.note)
|
||||
self.note.setVisible(bool(v.note))
|
||||
self.back_btn.setEnabled(v.can_back)
|
||||
@@ -212,9 +213,10 @@ class GuidePanel(QWidget):
|
||||
self.options.addWidget(b)
|
||||
step_type = v.step.type if v.step else None
|
||||
self.continue_btn.setVisible(step_type == "info")
|
||||
self.skip_btn.setVisible(step_type == "wait")
|
||||
self.wait_bar.setVisible(step_type == "wait")
|
||||
self.wait_label.setVisible(step_type == "wait")
|
||||
waits = step_type in ("wait", "influx")
|
||||
self.skip_btn.setVisible(waits)
|
||||
self.wait_bar.setVisible(waits)
|
||||
self.wait_label.setVisible(waits)
|
||||
self.end_box.setVisible(is_end)
|
||||
if is_end:
|
||||
e = v.end
|
||||
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user