From 0d21c5c205329cd03aee66952796fcddbd894665 Mon Sep 17 00:00:00 2001 From: Krzysztof Kallas Date: Tue, 29 Sep 2026 17:30:25 +0200 Subject: [PATCH] Store all text files with LF line endings Some files had been saved with CRLF on Windows, so a one-line change showed as a whole-file diff. .gitattributes now stores text as LF (.bat stays CRLF for cmd, .command/.sh LF for macOS). Co-Authored-By: Claude Opus 5.5 --- .gitattributes | 2 + README.md | 154 +- kota_tech/core/logline.py | 404 +- kota_tech/diag/engine.py | 1998 ++++----- kota_tech/diag/firmware.py | 294 +- kota_tech/diag/guides.py | 802 ++-- kota_tech/diag/guides.toml | 1480 +++---- kota_tech/diag/rules.toml | 1464 +++---- kota_tech/flash/image.py | 520 +-- kota_tech/matter/payload.py | 226 +- kota_tech/sd/provision.py | 522 +-- kota_tech/service/report.py | 310 +- kota_tech/ui/cheatsheet.py | 174 +- kota_tech/ui/diag_panel.py | 504 +-- kota_tech/ui/flash_tab.py | 728 ++-- kota_tech/ui/main_window.py | 848 ++-- kota_tech/ui/matter_qr.py | 152 +- ...t_A4CB8FC946F8_fw1.0.16_uncommissioned.txt | 2126 ++++----- .../live_brownout_loop_A4CB8FC946F8_fw1.0.txt | 3870 ++++++++--------- 19 files changed, 8290 insertions(+), 8288 deletions(-) diff --git a/.gitattributes b/.gitattributes index adfdc3d..8df85fe 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,6 @@ +* text=auto eol=lf *.command text eol=lf *.sh text eol=lf *.bat text eol=crlf *.bin binary +*.png binary diff --git a/README.md b/README.md index 471f15b..64b29fd 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,77 @@ -# KOTA Sensorbox Technician Tool - -Diagnoza sensorboxa przez USB, wgrywanie firmware z folderu `firmware/` i przygotowanie karty SD. -Działa na Windows 10/11 i macOS 13 (Ventura) lub nowszym, na Macach z Intelem i Apple Silicon. - -Repozytorium: https://gitea.kota-app.com/kkallas/kota-technician-tool (dostęp nadaje właściciel repozytorium). - -## Dla technika: dwa pliki - -| System | Aktualizacja | Uruchomienie | -|---|---|---| -| Windows | `update_windows.bat` | `run_windows.bat` | -| macOS | `update_macos.command` | `run_macos.command` | - -- **Aktualizacja** sprawdza na Gitea najnowszą wydaną wersję i pobiera tylko to, co się zmieniło - (pliki programu, firmware i potrzebne biblioteki). Uruchom ją, gdy firmware team ogłosi nową wersję, - albo po prostu przed wyjazdem do klienta. Pokazuje, co się zmieniło. -- **Uruchomienie** startuje program. Nie łączy się z Gitea, działa też bez internetu (po pierwszej aktualizacji). - -Nie trzymaj folderu narzędzia w Google Drive ani OneDrive. - -## Pierwsza instalacja (raz na komputer) - -1. Na Gitea otwórz **Releases** i przy najnowszej wersji pobierz **Source Code (ZIP)**. -2. Rozpakuj do zwykłego folderu, np. `Dokumenty\KOTA\technician-tool` (macOS: `~/Documents/KOTA/technician-tool`). -3. Uruchom plik aktualizacji dla swojego systemu. Za pierwszym razem: - - połączy folder z Gitea i zapyta o login do Gitea (hasło lub token); system je zapamięta, - - zainstaluje brakujące narzędzia za Twoją zgodą: Git (Windows: przez winget; macOS: Apple Command Line Tools - - po ich instalacji uruchom aktualizację jeszcze raz) oraz `uv` (menedżer Pythona, tylko dla bieżącego - użytkownika), - - pobierze biblioteki Pythona (1–2 minuty, potrzebny internet). -4. Uruchom program plikiem `run_...`. - -**macOS:** jeśli Finder odpowie, że pliku nie można uruchomić (zdarza się po rozpakowaniu zip), otwórz Terminal, -wpisz `bash ` (ze spacją), przeciągnij `update_macos.command` do okna i naciśnij Enter. Po pierwszej aktualizacji -oba pliki `.command` dają się już otwierać dwuklikiem. - -Zamiast zip można też zrobić `git clone https://gitea.kota-app.com/kkallas/kota-technician-tool.git` i dalej -używać tych samych dwóch plików. - -Konkretna starsza wersja (np. do serwisu nietypowego boxa): w folderze narzędzia `git switch --detach v0.8`; -kolejna aktualizacja wraca do najnowszej. - -Wszystkie wersje używają jednego środowiska Pythona poza folderem narzędzia: -`%LOCALAPPDATA%\KotaTechnicianTool\venv` (Windows), `~/Library/Caches/KotaTechnicianTool/venv` (macOS). -Stare środowiska `venv-0.x` z poprzednich wersji można usunąć. - -Sesje, kopie zapasowe flash i ustawienia są w `~/KotaTechnicianTool` (Windows: `C:\Users\\KotaTechnicianTool`). - -## Port sensorboxa - -- Windows: `COMx - Sensorbox (native USB) [MAC]` -- macOS: `/dev/cu.usbmodemXXXX - Sensorbox (native USB) [MAC]` - -Kabel USB musi przenosić dane (nie tylko ładowanie). Przy restartach z komunikatem o brownout użyj innego portu -lub zasilacza. - -## Firmware - -Wgrać można tylko pliki `.bin` z folderu `firmware/`. Lista pokazuje datę kompilacji zapisaną w pliku, -najnowszy build jest na górze. Nowy build: wrzuć plik do `firmware/` i kliknij **Refresh list**. - -## Dla zespołu - -- Praca nad zmianą: gałąź (`git switch -c feature/`) → commity z przechodzącymi testami → - `git push -u origin feature/` → pull request na Gitea → merge do `main`. -- Wydanie wersji: numer w `pyproject.toml`, `kota_tech/__init__.py` i `uv.lock`, tag `vX.Y` - (`git tag -a vX.Y -m "Technician tool vX.Y"`, `git push origin vX.Y`), opis w **Releases** na Gitea. - Aktualizacja techników bierze **tylko tagi `v*`** – zmiany w `main` bez tagu do nich nie trafiają. -- Nie commitujemy `ENV.TXT` z prawdziwym tokenem ani haseł Wi-Fi. -- Testy: - -``` -uv run --frozen pytest -q -``` - -Zmiany w wersjach: `docs/CHANGES-v0.*.md`; historia v0.1–v0.8 jest w tagach gita. +# KOTA Sensorbox Technician Tool + +Diagnoza sensorboxa przez USB, wgrywanie firmware z folderu `firmware/` i przygotowanie karty SD. +Działa na Windows 10/11 i macOS 13 (Ventura) lub nowszym, na Macach z Intelem i Apple Silicon. + +Repozytorium: https://gitea.kota-app.com/kkallas/kota-technician-tool (dostęp nadaje właściciel repozytorium). + +## Dla technika: dwa pliki + +| System | Aktualizacja | Uruchomienie | +|---|---|---| +| Windows | `update_windows.bat` | `run_windows.bat` | +| macOS | `update_macos.command` | `run_macos.command` | + +- **Aktualizacja** sprawdza na Gitea najnowszą wydaną wersję i pobiera tylko to, co się zmieniło + (pliki programu, firmware i potrzebne biblioteki). Uruchom ją, gdy firmware team ogłosi nową wersję, + albo po prostu przed wyjazdem do klienta. Pokazuje, co się zmieniło. +- **Uruchomienie** startuje program. Nie łączy się z Gitea, działa też bez internetu (po pierwszej aktualizacji). + +Nie trzymaj folderu narzędzia w Google Drive ani OneDrive. + +## Pierwsza instalacja (raz na komputer) + +1. Na Gitea otwórz **Releases** i przy najnowszej wersji pobierz **Source Code (ZIP)**. +2. Rozpakuj do zwykłego folderu, np. `Dokumenty\KOTA\technician-tool` (macOS: `~/Documents/KOTA/technician-tool`). +3. Uruchom plik aktualizacji dla swojego systemu. Za pierwszym razem: + - połączy folder z Gitea i zapyta o login do Gitea (hasło lub token); system je zapamięta, + - zainstaluje brakujące narzędzia za Twoją zgodą: Git (Windows: przez winget; macOS: Apple Command Line Tools + - po ich instalacji uruchom aktualizację jeszcze raz) oraz `uv` (menedżer Pythona, tylko dla bieżącego + użytkownika), + - pobierze biblioteki Pythona (1–2 minuty, potrzebny internet). +4. Uruchom program plikiem `run_...`. + +**macOS:** jeśli Finder odpowie, że pliku nie można uruchomić (zdarza się po rozpakowaniu zip), otwórz Terminal, +wpisz `bash ` (ze spacją), przeciągnij `update_macos.command` do okna i naciśnij Enter. Po pierwszej aktualizacji +oba pliki `.command` dają się już otwierać dwuklikiem. + +Zamiast zip można też zrobić `git clone https://gitea.kota-app.com/kkallas/kota-technician-tool.git` i dalej +używać tych samych dwóch plików. + +Konkretna starsza wersja (np. do serwisu nietypowego boxa): w folderze narzędzia `git switch --detach v0.8`; +kolejna aktualizacja wraca do najnowszej. + +Wszystkie wersje używają jednego środowiska Pythona poza folderem narzędzia: +`%LOCALAPPDATA%\KotaTechnicianTool\venv` (Windows), `~/Library/Caches/KotaTechnicianTool/venv` (macOS). +Stare środowiska `venv-0.x` z poprzednich wersji można usunąć. + +Sesje, kopie zapasowe flash i ustawienia są w `~/KotaTechnicianTool` (Windows: `C:\Users\\KotaTechnicianTool`). + +## Port sensorboxa + +- Windows: `COMx - Sensorbox (native USB) [MAC]` +- macOS: `/dev/cu.usbmodemXXXX - Sensorbox (native USB) [MAC]` + +Kabel USB musi przenosić dane (nie tylko ładowanie). Przy restartach z komunikatem o brownout użyj innego portu +lub zasilacza. + +## Firmware + +Wgrać można tylko pliki `.bin` z folderu `firmware/`. Lista pokazuje datę kompilacji zapisaną w pliku, +najnowszy build jest na górze. Nowy build: wrzuć plik do `firmware/` i kliknij **Refresh list**. + +## Dla zespołu + +- Praca nad zmianą: gałąź (`git switch -c feature/`) → commity z przechodzącymi testami → + `git push -u origin feature/` → pull request na Gitea → merge do `main`. +- Wydanie wersji: numer w `pyproject.toml`, `kota_tech/__init__.py` i `uv.lock`, tag `vX.Y` + (`git tag -a vX.Y -m "Technician tool vX.Y"`, `git push origin vX.Y`), opis w **Releases** na Gitea. + Aktualizacja techników bierze **tylko tagi `v*`** – zmiany w `main` bez tagu do nich nie trafiają. +- Nie commitujemy `ENV.TXT` z prawdziwym tokenem ani haseł Wi-Fi. +- Testy: + +``` +uv run --frozen pytest -q +``` + +Zmiany w wersjach: `docs/CHANGES-v0.*.md`; historia v0.1–v0.8 jest w tagach gita. diff --git a/kota_tech/core/logline.py b/kota_tech/core/logline.py index 6967f3a..7b061de 100644 --- a/kota_tech/core/logline.py +++ b/kota_tech/core/logline.py @@ -1,202 +1,202 @@ -"""Serial byte stream -> framed, cleaned, parsed log lines. - -ESP-IDF log format: " () : ", optionally -wrapped in ANSI colour codes. ROM/bootloader output (boot banner, reset -reason) has no level prefix and is classified separately, since a boot -banner is the only reliable reboot marker. - -Firmware 1.0.16+ adds structured diagnostic lines logged under the "KOTA" -tag ("I (812) KOTA: boot fw=1.0.16 build=4f10989e ... last_restart=none") -and prefixes every ESP_LOGE message with a stable error ID ("[E-SD-03] "). -Both are parsed here so the diagnostics engine reads fields directly -instead of matching message text. -""" - -from __future__ import annotations - -import re -import time -from dataclasses import dataclass, field -from enum import Enum - -from .redact import redact - -ANSI_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]") -ESP_LOG_RE = re.compile(r"^([EWIDV]) \((\d+)\) ([^:]{1,40}?): ?(.*)$") -# ESP32-S3 ROM prints e.g. "ESP-ROM:esp32s3-20210327" then -# "rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)". -BOOT_BANNER_RE = re.compile(r"^ESP-ROM:") -RESET_REASON_RE = re.compile(r"^rst:0x([0-9a-fA-F]+) \(([A-Z0-9_]+)\)") -# "[E-SD-03] Failed to initialize the card ..." (firmware error_ids.csv). -ERROR_ID_RE = re.compile(r"^\[(E-[A-Z0-9]+-\d{2,})\] ?") -KOTA_TAG = "KOTA" -PANIC_RE = re.compile( - r"Guru Meditation Error|abort\(\) was called|ESP_ERROR_CHECK failed|" - r"^Backtrace:|Rebooting\.\.\.$" -) - - -class LineKind(str, Enum): - LOG = "log" # ESP_LOGx line - BOOT = "boot" # ROM boot banner (start of a new boot) - RESET = "reset" # ROM reset-reason line - PANIC = "panic" # crash / abort output - KOTA = "kota" # structured "KOTA: key=value ..." line - RAW = "raw" # anything else (task list rows, line protocol, ...) - HOST = "host" # event generated by this tool (connect, reset, ...) - - -def host_event(text: str) -> LogLine: - return LogLine(f"--- {text} ---", LineKind.HOST) - - -@dataclass(frozen=True) -class LogLine: - text: str # cleaned + redacted - kind: LineKind - host_time: float = field(default_factory=time.time) - level: str | None = None # E/W/I/D/V for LOG lines - uptime_ms: int | None = None - tag: str | None = None - message: str | None = None - reset_reason: str | None = None - error_id: str | None = None # "E-SD-03" when the message carries one - record: str | None = None # KOTA lines: "boot", "i2c", "health", ... - fields: dict[str, str] = field(default_factory=dict) # KOTA key=value pairs - words: tuple[str, ...] = () # KOTA bare words ("verify", "OK", "->", ...) - - @property - def match_text(self) -> str: - """Text for message rules: the error-ID prefix removed, so rules - written for older firmware keep matching.""" - if self.error_id and self.message is not None and self.tag: - return self.text.replace(f"[{self.error_id}] ", "", 1) - return self.text - - -def parse_kota(body: str) -> tuple[str, dict[str, str], tuple[str, ...]]: - """'rtc verify running=1 delta=2s ok=1' -> ('rtc', {...}, ('verify',)). - - Values never contain spaces. The mic line has a parenthesised aside - ("(GPIO3=1, expected 1)"): brackets and commas are stripped from keys - and values, and the aside's words are kept as bare words. - """ - tokens = body.split() - if not tokens: - return "", {}, () - fields: dict[str, str] = {} - words: list[str] = [] - for tok in tokens[1:]: - key, eq, value = tok.partition("=") - if eq and key.strip("(,"): - key = key.lstrip("(") - # "0x68=DS1307(missing)" keeps its bracket; "(GPIO3=1," loses it. - value = value.rstrip(",") - if tok.startswith("(") or value.endswith(")") and "(" not in value: - value = value.rstrip(")") - fields[key] = value - else: - words.append(tok.strip("(),")) - return tokens[0], fields, tuple(w for w in words if w) - - -def clean(raw: str) -> str: - return ANSI_RE.sub("", raw).replace("\r", "").rstrip() - - -def parse_line(raw: str, host_time: float | None = None) -> LogLine: - text = redact(clean(raw)) - t = time.time() if host_time is None else host_time - if m := ESP_LOG_RE.match(text): - level, ms, tag, msg = m.groups() - tag = tag.strip() - if tag == KOTA_TAG: - record, fields, words = parse_kota(msg) - return LogLine(text, LineKind.KOTA, t, level, int(ms), tag, msg, - record=record, fields=fields, words=words) - eid = None - if em := ERROR_ID_RE.match(msg): - eid, msg = em.group(1), msg[em.end():] - return LogLine(text, LineKind.LOG, t, level, int(ms), tag, msg, error_id=eid) - if BOOT_BANNER_RE.match(text): - return LogLine(text, LineKind.BOOT, t) - if m := RESET_REASON_RE.match(text): - return LogLine(text, LineKind.RESET, t, reset_reason=m.group(2)) - if PANIC_RE.search(text): - return LogLine(text, LineKind.PANIC, t) - return LogLine(text, LineKind.RAW, t) - - -class LineFramer: - """Accumulates bytes and yields complete lines. - - A partial line is released after `idle_flush_s` without new bytes so - prompts / unterminated output are not held back forever. - - Lines are also split where a new log line starts in the middle - ("I (2302) chip[DL]: I (2302) chip[DL]: Posting..."): the two S3 cores - interleave output, and the USB-Serial-JTAG FIFO keeps the tail of an old - line while no host reads, releasing it glued in front of the next live - output. After a (re)open, the fragment in front of the first such split - is stale and dropped. Timing cannot identify it, because the FIFO only - flushes when the board next prints, possibly seconds later. - """ - - def __init__(self, idle_flush_s: float = 0.3, max_line: int = 4096, - drop_first_partial: bool = False): - self._buf = bytearray() - self._last_rx = 0.0 - self.idle_flush_s = idle_flush_s - self.max_line = max_line - self._first_line_pending = drop_first_partial - - def cancel_drop(self) -> None: - self._first_line_pending = False - - def feed(self, data: bytes, now: float | None = None) -> list[str]: - now = time.monotonic() if now is None else now - if data: - self._buf.extend(data) - self._last_rx = now - out: list[str] = [] - while (i := self._buf.find(b"\n")) >= 0: - out += self._emit(self._buf[:i]) - del self._buf[: i + 1] - if len(self._buf) >= self.max_line: - out += self._emit(self._buf) - self._buf.clear() - return out - - def poll_idle(self, now: float | None = None) -> list[str]: - now = time.monotonic() if now is None else now - if self._buf and now - self._last_rx >= self.idle_flush_s: - return self.flush() - return [] - - def flush(self) -> list[str]: - if not self._buf: - return [] - out = self._emit(self._buf) - self._buf.clear() - return out - - def _emit(self, raw: bytes | bytearray) -> list[str]: - parts = split_glued(bytes(raw).decode("utf-8", errors="replace")) - if self._first_line_pending: - self._first_line_pending = False - if len(parts) > 1: - parts = parts[1:] # stale FIFO fragment from before the open - return parts - - -# Start of a log line (optionally ANSI-coloured) or ROM banner. -_LINE_START = re.compile(r"(?:\x1b\[[0-9;]*m)?(?:[EWIDV] \(\d+\) \S|ESP-ROM:)") - - -def split_glued(text: str) -> list[str]: - # Left-to-right scan so an ANSI colour prefix stays with its line. - cuts = [m.start() for m in _LINE_START.finditer(text) if m.start() > 0] - if not cuts: - return [text] - bounds = [0, *cuts, len(text)] - return [text[a:b] for a, b in zip(bounds, bounds[1:]) if a < b] +"""Serial byte stream -> framed, cleaned, parsed log lines. + +ESP-IDF log format: " () : ", optionally +wrapped in ANSI colour codes. ROM/bootloader output (boot banner, reset +reason) has no level prefix and is classified separately, since a boot +banner is the only reliable reboot marker. + +Firmware 1.0.16+ adds structured diagnostic lines logged under the "KOTA" +tag ("I (812) KOTA: boot fw=1.0.16 build=4f10989e ... last_restart=none") +and prefixes every ESP_LOGE message with a stable error ID ("[E-SD-03] "). +Both are parsed here so the diagnostics engine reads fields directly +instead of matching message text. +""" + +from __future__ import annotations + +import re +import time +from dataclasses import dataclass, field +from enum import Enum + +from .redact import redact + +ANSI_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]") +ESP_LOG_RE = re.compile(r"^([EWIDV]) \((\d+)\) ([^:]{1,40}?): ?(.*)$") +# ESP32-S3 ROM prints e.g. "ESP-ROM:esp32s3-20210327" then +# "rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)". +BOOT_BANNER_RE = re.compile(r"^ESP-ROM:") +RESET_REASON_RE = re.compile(r"^rst:0x([0-9a-fA-F]+) \(([A-Z0-9_]+)\)") +# "[E-SD-03] Failed to initialize the card ..." (firmware error_ids.csv). +ERROR_ID_RE = re.compile(r"^\[(E-[A-Z0-9]+-\d{2,})\] ?") +KOTA_TAG = "KOTA" +PANIC_RE = re.compile( + r"Guru Meditation Error|abort\(\) was called|ESP_ERROR_CHECK failed|" + r"^Backtrace:|Rebooting\.\.\.$" +) + + +class LineKind(str, Enum): + LOG = "log" # ESP_LOGx line + BOOT = "boot" # ROM boot banner (start of a new boot) + RESET = "reset" # ROM reset-reason line + PANIC = "panic" # crash / abort output + KOTA = "kota" # structured "KOTA: key=value ..." line + RAW = "raw" # anything else (task list rows, line protocol, ...) + HOST = "host" # event generated by this tool (connect, reset, ...) + + +def host_event(text: str) -> LogLine: + return LogLine(f"--- {text} ---", LineKind.HOST) + + +@dataclass(frozen=True) +class LogLine: + text: str # cleaned + redacted + kind: LineKind + host_time: float = field(default_factory=time.time) + level: str | None = None # E/W/I/D/V for LOG lines + uptime_ms: int | None = None + tag: str | None = None + message: str | None = None + reset_reason: str | None = None + error_id: str | None = None # "E-SD-03" when the message carries one + record: str | None = None # KOTA lines: "boot", "i2c", "health", ... + fields: dict[str, str] = field(default_factory=dict) # KOTA key=value pairs + words: tuple[str, ...] = () # KOTA bare words ("verify", "OK", "->", ...) + + @property + def match_text(self) -> str: + """Text for message rules: the error-ID prefix removed, so rules + written for older firmware keep matching.""" + if self.error_id and self.message is not None and self.tag: + return self.text.replace(f"[{self.error_id}] ", "", 1) + return self.text + + +def parse_kota(body: str) -> tuple[str, dict[str, str], tuple[str, ...]]: + """'rtc verify running=1 delta=2s ok=1' -> ('rtc', {...}, ('verify',)). + + Values never contain spaces. The mic line has a parenthesised aside + ("(GPIO3=1, expected 1)"): brackets and commas are stripped from keys + and values, and the aside's words are kept as bare words. + """ + tokens = body.split() + if not tokens: + return "", {}, () + fields: dict[str, str] = {} + words: list[str] = [] + for tok in tokens[1:]: + key, eq, value = tok.partition("=") + if eq and key.strip("(,"): + key = key.lstrip("(") + # "0x68=DS1307(missing)" keeps its bracket; "(GPIO3=1," loses it. + value = value.rstrip(",") + if tok.startswith("(") or value.endswith(")") and "(" not in value: + value = value.rstrip(")") + fields[key] = value + else: + words.append(tok.strip("(),")) + return tokens[0], fields, tuple(w for w in words if w) + + +def clean(raw: str) -> str: + return ANSI_RE.sub("", raw).replace("\r", "").rstrip() + + +def parse_line(raw: str, host_time: float | None = None) -> LogLine: + text = redact(clean(raw)) + t = time.time() if host_time is None else host_time + if m := ESP_LOG_RE.match(text): + level, ms, tag, msg = m.groups() + tag = tag.strip() + if tag == KOTA_TAG: + record, fields, words = parse_kota(msg) + return LogLine(text, LineKind.KOTA, t, level, int(ms), tag, msg, + record=record, fields=fields, words=words) + eid = None + if em := ERROR_ID_RE.match(msg): + eid, msg = em.group(1), msg[em.end():] + return LogLine(text, LineKind.LOG, t, level, int(ms), tag, msg, error_id=eid) + if BOOT_BANNER_RE.match(text): + return LogLine(text, LineKind.BOOT, t) + if m := RESET_REASON_RE.match(text): + return LogLine(text, LineKind.RESET, t, reset_reason=m.group(2)) + if PANIC_RE.search(text): + return LogLine(text, LineKind.PANIC, t) + return LogLine(text, LineKind.RAW, t) + + +class LineFramer: + """Accumulates bytes and yields complete lines. + + A partial line is released after `idle_flush_s` without new bytes so + prompts / unterminated output are not held back forever. + + Lines are also split where a new log line starts in the middle + ("I (2302) chip[DL]: I (2302) chip[DL]: Posting..."): the two S3 cores + interleave output, and the USB-Serial-JTAG FIFO keeps the tail of an old + line while no host reads, releasing it glued in front of the next live + output. After a (re)open, the fragment in front of the first such split + is stale and dropped. Timing cannot identify it, because the FIFO only + flushes when the board next prints, possibly seconds later. + """ + + def __init__(self, idle_flush_s: float = 0.3, max_line: int = 4096, + drop_first_partial: bool = False): + self._buf = bytearray() + self._last_rx = 0.0 + self.idle_flush_s = idle_flush_s + self.max_line = max_line + self._first_line_pending = drop_first_partial + + def cancel_drop(self) -> None: + self._first_line_pending = False + + def feed(self, data: bytes, now: float | None = None) -> list[str]: + now = time.monotonic() if now is None else now + if data: + self._buf.extend(data) + self._last_rx = now + out: list[str] = [] + while (i := self._buf.find(b"\n")) >= 0: + out += self._emit(self._buf[:i]) + del self._buf[: i + 1] + if len(self._buf) >= self.max_line: + out += self._emit(self._buf) + self._buf.clear() + return out + + def poll_idle(self, now: float | None = None) -> list[str]: + now = time.monotonic() if now is None else now + if self._buf and now - self._last_rx >= self.idle_flush_s: + return self.flush() + return [] + + def flush(self) -> list[str]: + if not self._buf: + return [] + out = self._emit(self._buf) + self._buf.clear() + return out + + def _emit(self, raw: bytes | bytearray) -> list[str]: + parts = split_glued(bytes(raw).decode("utf-8", errors="replace")) + if self._first_line_pending: + self._first_line_pending = False + if len(parts) > 1: + parts = parts[1:] # stale FIFO fragment from before the open + return parts + + +# Start of a log line (optionally ANSI-coloured) or ROM banner. +_LINE_START = re.compile(r"(?:\x1b\[[0-9;]*m)?(?:[EWIDV] \(\d+\) \S|ESP-ROM:)") + + +def split_glued(text: str) -> list[str]: + # Left-to-right scan so an ANSI colour prefix stays with its line. + cuts = [m.start() for m in _LINE_START.finditer(text) if m.start() > 0] + if not cuts: + return [text] + bounds = [0, *cuts, len(text)] + return [text[a:b] for a, b in zip(bounds, bounds[1:]) if a < b] diff --git a/kota_tech/diag/engine.py b/kota_tech/diag/engine.py index 8c77f31..3805f34 100644 --- a/kota_tech/diag/engine.py +++ b/kota_tech/diag/engine.py @@ -1,999 +1,999 @@ -"""Diagnostics engine: parsed log lines -> findings, milestones, LED, verdict. - -Line-level knowledge lives in rules.toml. This module adds what a single -line cannot express: - * boot tracking (ROM banner, reset reason, uptime drops when the banner - was missed) and classification of why the previous boot ended; - * the status-LED model (Status::set_status / set_status_conditional); - * crash capture (panic / ESP_ERROR_CHECK output); - * root-cause grouping via `explained_by` chains; - * the structured "KOTA:" lines of firmware 1.0.16+ (diag/kota.py): exact - status/LED, restart reasons, I2C/SD/RTC/Matter/mic self-report and the - 60 s health line, plus error IDs ("[E-SD-03]") looked up in - error_ids.csv. Older firmware is still diagnosed from message text. - -Findings are session-wide (a reboot loop must keep its cause visible); -milestones and the LED are per boot. -""" - -from __future__ import annotations - -import re -import tomllib -from collections import Counter, deque -from dataclasses import dataclass, field -from enum import IntEnum -from pathlib import Path - -from ..core.logline import LineKind, LogLine -from . import kota -from .firmware import FirmwareInfo - -RULES_PATH = Path(__file__).with_name("rules.toml") -_MAC_LINE = re.compile(r"Device MAC address: ([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})") - - -class Severity(IntEnum): - IGNORE = -1 - INFO = 1 - WARNING = 2 - ERROR = 3 - CRITICAL = 4 - - @property - def label(self) -> str: - return self.name.capitalize() - - -# name -> (label, swatch colour) -LED = { - "UNKNOWN": ("Unknown", "#888888"), - "OFF": ("Off", "#444444"), - "GREEN": ("Green", "#2e9e44"), - "PURPLE": ("Purple", "#8e44ad"), - "RED": ("Red", "#d64541"), - "BLUE": ("Blue", "#2f6fd6"), - "CYAN": ("Cyan", "#1fc8d8"), - "YELLOW": ("Yellow", "#e6c229"), - "ORANGE": ("Orange", "#f08c1a"), - "WHITE": ("White", "#f4f4f4"), -} - -# (id, label, optional) - optional milestones are listed only once reached. -MILESTONES = [ - ("rom_boot", "Board booted", False), - ("app_started", "Firmware started", False), - ("sd_mounted", "SD card mounted", False), - ("env_loaded", "ENV.TXT read (InfluxDB settings)", False), - ("matter_started", "Matter started", True), - ("matter_commissioned", "Paired in Matter", True), - ("local_wifi_mode", "Local Wi-Fi mode (SSID from SD card)", True), - ("i2c_ok", "All sensors answer on the I2C bus", True), - ("sensors_ready", "All sensors started", False), - ("wifi_connected", "Connected to Wi-Fi", False), - ("time_synced", "Clock set from the Internet", False), - ("clock_verified", "Clock keeps time (RTC verified)", True), - ("data_uploaded", "Data uploaded to InfluxDB", False), - ("ota_registered", "Registered for firmware updates", False), - ("ota_checked", "Checked for firmware updates", False), - ("ota_update_started", "Firmware update started", True), - ("ota_update_done", "Firmware update installed", True), - ("ota_update_accepted", "Updated firmware accepted", True), -] - -# Findings that stop start-up; a crash/reboot right after them is their effect. -_INIT_FAILURES = [ - "rtc.init_failed", "sensor.init_failed", "sd.card_fail", "sd.mount_fail", - "sd.env_open_fail", "influx.env_missing", "influx.init_fail", - "kota.i2c_stuck", "kota.sensor_missing", "kota.rtc_read_failed", "kota.sd_unmounted", -] -_NETWORK_ROOTS = [ - "influx.buffer_fail", "ota.check_failed", "net.dns_fail", - "matter.not_commissioned", "wifi.disconnected", -] -# Printed by the brown-out handler without a timestamp ("E BOD: ..."); on the -# ESP32-S3 the ROM then reports RTC_SW_SYS_RST, not a brown-out reset. -_BROWNOUT = "Brownout detector was triggered" -_RESET_BY_TOOL_WINDOW_S = 5.0 -_SHORT_BOOT_MS = 60_000 - - -class _Fmt(dict): - def __missing__(self, key: str) -> str: - return "?" - - -def _fmt(s: str, groups: dict[str, str]) -> str: - return s.format_map(_Fmt(groups)) if s else s - - -@dataclass(frozen=True) -class Rule: - id: str - pattern: re.Pattern[str] | None # None: raised from KOTA: fields - severity: Severity - title: str = "" - meaning: str = "" - actions: tuple[str, ...] = () - explained_by: tuple[str, ...] = () - milestone: str | None = None - clears: tuple[str, ...] = () - led: str | None = None - led_from: str | None = None - split_by: str | None = None - min_count: int = 1 - min_uptime_ms: int = 0 - - -def load_rules(path: Path = RULES_PATH) -> list[Rule]: - data = tomllib.loads(path.read_text(encoding="utf-8")) - rules = [] - for r in data["rule"]: - rules.append(Rule( - id=r["id"], - pattern=re.compile(r["pattern"]) if "pattern" in r else None, - severity=Severity[r["severity"].upper()], - title=r.get("title", ""), - meaning=r.get("meaning", ""), - actions=tuple(r.get("actions", ())), - explained_by=tuple(r.get("explained_by", ())), - milestone=r.get("milestone"), - clears=tuple(r.get("clears", ())), - led=r.get("led"), - led_from=r.get("led_from"), - split_by=r.get("split_by"), - min_count=int(r.get("min_count", 1)), - min_uptime_ms=int(r.get("min_uptime_ms", 0)), - )) - _validate(rules) - return rules - - -def _validate(rules: list[Rule]) -> None: - ids = [r.id for r in rules] - dupes = [i for i, n in Counter(ids).items() if n > 1] - if dupes: - raise ValueError(f"duplicate rule ids: {dupes}") - known = set(ids) - for r in rules: - for ref in (*r.explained_by, *r.clears): - # status.* / reboot.* findings are created by the engine itself. - if ref not in known and not ref.startswith(("status.", "reboot.")): - raise ValueError(f"{r.id}: unknown rule id {ref!r}") - for colour in (r.led, r.led_from): - if colour and colour not in LED: - raise ValueError(f"{r.id}: unknown LED colour {colour!r}") - if r.severity != Severity.IGNORE and not r.title: - raise ValueError(f"{r.id}: visible rule needs a title") - - -@dataclass -class Finding: - key: str # rule id, or "rule id:split value" - rule_id: str - severity: Severity - title: str - meaning: str - actions: tuple[str, ...] - explained_by: tuple[str, ...] = () - min_count: int = 1 - count: int = 0 - first_uptime_ms: int | None = None - last_uptime_ms: int | None = None - last_seen: float = 0.0 - boot: int = 0 - resolved: bool = False - resolved_by: str | None = None - evidence: deque[str] = field(default_factory=lambda: deque(maxlen=3)) - error_ids: dict[str, str] = field(default_factory=dict) # "E-SD-03" -> technician action - - @property - def active(self) -> bool: - return self.count >= self.min_count and not self.resolved - - def seen_text(self) -> str: - """'Seen 3x, first at 2.9 s after start, last at 61.2 s' (panel and service note).""" - s = f"Seen {self.count}x" - if self.first_uptime_ms is not None: - first = f"{self.first_uptime_ms / 1000:.1f}" - s += f", first at {first} s after start" - last = f"{self.last_uptime_ms / 1000:.1f}" if self.last_uptime_ms is not None else first - if self.count > 1 and last != first: - s += f", last at {last} s" - if self.resolved and self.resolved_by: - s += f" - resolved ({self.resolved_by})" - return s - - -@dataclass -class BootRecord: - index: int - host_time: float - explicit: bool # ROM banner seen (vs. inferred) - reset_reason: str | None = None - tool_reset: bool = False - last_uptime_ms: int = 0 - saw_log: bool = False - led_at_end: str | None = None - panic_lines: list[str] = field(default_factory=list) - brownout: bool = False # "E BOD: Brownout detector was triggered" before the restart - recent_errors: deque[str] = field(default_factory=lambda: deque(maxlen=5)) - intentional_restart: bool = False # factory reset / setup-mode toggle - # KOTA: lines (firmware 1.0.16+) - kota: dict[str, LogLine] = field(default_factory=dict) # last line per record - last_restart: str | None = None # "boot ... last_restart=" (why the previous boot ended) - restart_reason: str | None = None # "restart reason=" printed just before this boot ended - previous_status: tuple[str, int] | None = None # "led previous_boot status= code=" - mode: str | None = None # matter | wifi | provisioning - - -@dataclass -class RootCause: - finding: Finding - consequences: list[Finding] - - @property - def severity(self) -> Severity: - return max([self.finding.severity, *(c.severity for c in self.consequences)]) - - -@dataclass -class Report: - verdict: str - verdict_severity: Severity | None # None = healthy / nothing found - led: str - led_reason: str - firmware: FirmwareInfo - milestones: list[tuple[str, str, int | None]] # (id, label, uptime_ms) - roots: list[RootCause] - earlier: list[Finding] # seen only in previous boots - resolved: list[Finding] - boots: list[BootRecord] - lines: int - mid_run: bool # no boot seen yet - technician: list[str] = field(default_factory=list) # guided-check answers - led_pattern: str = "" # "2 pulses", "solid", ... (exact when from a KOTA led line) - led_meaning: str = "" - led_exact: bool = False # reported by the box itself - box: list[tuple[str, str, str]] = field(default_factory=list) # (topic, text, ok|warn|bad|info) - matter_qr: str | None = None - matter_manual: str | None = None - - def led_text(self) -> str: - t = LED[self.led][0] + (f", {self.led_pattern}" if self.led_pattern else "") - return t + (f" = {self.led_meaning}" if self.led_meaning else "") - - def to_text(self) -> str: - out = [f"VERDICT: {self.verdict}", - f"Expected status LED: {self.led_text()}" - + (f" (last change: {self.led_reason})" if self.led_reason else ""), - f"Firmware: {self.firmware.describe()}", - f"Boots seen: {max(len(self.boots) - (0 if self.boots and self.boots[0].explicit else 1), 0)}"] - if self.box: - out += ["", "Box self-report (KOTA lines):"] - out += [f" {topic}: {text}" for topic, text, _ in self.box] - out += ["", "Milestones (current boot):"] - for _, label, t in self.milestones: - out.append(f" [{'x' if t is not None else ' '}] {label}" - + (f" @ {t / 1000:.1f} s" if t is not None else "")) - out.append("") - out.append("Findings:" if self.roots else "Findings: none") - for rc in self.roots: - out += _finding_text(rc.finding, " ") - for c in rc.consequences: - out.append(" consequence:") - out += _finding_text(c, " ") - if self.earlier: - out.append("") - out.append("Seen in earlier boots only:") - for f in self.earlier: - out.append(f" - [{f.severity.label}] {f.title} (x{f.count})") - if self.resolved: - out.append("") - out.append("Resolved during this session:") - for f in self.resolved: - out.append(f" - {f.title}" + (f" (by: {f.resolved_by})" if f.resolved_by else "")) - if self.technician: - out.append("") - out.append("Technician checks:") - out += [f" - {t}" for t in self.technician] - return "\n".join(out) - - -def _finding_text(f: Finding, ind: str) -> list[str]: - lines = [f"{ind}- [{f.severity.label}] {f.title} (x{f.count})", f"{ind} {f.meaning}"] - lines += [f"{ind} {i}. {a}" for i, a in enumerate(f.actions, 1)] - for eid, action in f.error_ids.items(): - lines.append(f"{ind} error {eid}" + (f": {action}" if action else "")) - if f.evidence: - lines.append(f"{ind} log: {f.evidence[-1]}") - return lines - - -class DiagnosticsEngine: - def __init__(self, rules: list[Rule] | None = None): - self.rules = rules if rules is not None else load_rules() - self._rule_by_id = {r.id: r for r in self.rules} - self._pattern_rules = [r for r in self.rules if r.pattern is not None] - self.error_ids = kota.load_error_ids() - self.reset() - - # ---------------------------------------------------------------- API - def reset(self) -> None: - self.findings: dict[str, Finding] = {} - self.boots: list[BootRecord] = [BootRecord(0, 0.0, explicit=False)] - self.milestones: dict[str, int] = {} - self.led = "UNKNOWN" - self.led_reason = "" - self.firmware = FirmwareInfo() - self.lines = 0 - self._tool_reset_at: float | None = None - self._panic_tail = 0 # RAW lines after a panic line to keep - self.technician: list[str] = [] - self.mac: str | None = None # from "ID: Device MAC address: ..." at boot - self.matter_advert: str | None = None # "Advertise commission parameter vendorID=..." line - self.matter_qr: str | None = None # "KOTA: matter ... qr=MT:..." - self.matter_manual: str | None = None - self.kota_seen = False # firmware prints KOTA: lines - self._reset_led_state() - - def _reset_led_state(self) -> None: - self.led_pattern = "" - self.led_exact = False # a KOTA led line was seen this boot - self.status: tuple[str, int] | None = None - - @property - def boot(self) -> BootRecord: - return self.boots[-1] - - def feed(self, line: LogLine) -> None: - self.lines += 1 - text = line.text - if line.kind == LineKind.HOST: - if "reset issued by tool" in text: - self._tool_reset_at = line.host_time - return - if _BROWNOUT in text: - self.boot.brownout = True - if line.kind == LineKind.BOOT: - self._new_boot(line.host_time, explicit=True) - self.milestones["rom_boot"] = 0 - return - if line.kind == LineKind.RESET: - self.boot.reset_reason = line.reset_reason - if line.reset_reason and "POWERON" in line.reset_reason: - self.led, self.led_reason = "OFF", "power-on - firmware not started yet" - return - if line.kind in (LineKind.LOG, LineKind.KOTA) and line.uptime_ms is not None: - b = self.boot - # Uptime jumped back to a fresh-boot value: the ROM banner was - # missed (port reconnect, capture started late). - if b.saw_log and line.uptime_ms + 3000 < b.last_uptime_ms and line.uptime_ms < 10_000: - self._new_boot(line.host_time, explicit=False) - b = self.boot - b.saw_log = True - b.last_uptime_ms = max(b.last_uptime_ms, line.uptime_ms) - - if line.kind == LineKind.PANIC: - self._on_panic(line) - elif self._panic_tail and line.kind == LineKind.RAW: - self.boot.panic_lines.append(text) - self._panic_tail -= 1 - self._crash_finding().evidence.append(text) - - if line.kind == LineKind.KOTA: - self._on_kota(line) - return - - self.firmware.observe(text) - if m := _MAC_LINE.search(text): - self.mac = m.group(1).upper() - if "Advertise commission parameter" in text: - self.matter_advert = text - matched = False - hit: list[Finding] = [] - rule_text = line.match_text - for rule in self._pattern_rules: - m = rule.pattern.search(rule_text) - if not m: - continue - matched = True - if rule.min_uptime_ms and (line.uptime_ms or 0) < rule.min_uptime_ms: - continue - groups = {k: v for k, v in m.groupdict().items() if v is not None} - if rule.milestone and rule.milestone not in self.milestones: - self.milestones[rule.milestone] = line.uptime_ms or 0 - # The box's own "KOTA: led" lines are exact; message rules only guess. - if rule.led and not self.led_exact: - self._set_led(rule.led, rule.led_from, text) - for cid in rule.clears: - for f in self.findings.values(): - if f.rule_id == cid and not f.resolved: - f.resolved = True - f.resolved_by = rule.milestone or rule.id - if rule.id in ("sys.factory_reset",) or "Short press:" in text: - self.boot.intentional_restart = True - if rule.severity != Severity.IGNORE: - hit.append(self._hit(rule, groups, line)) - - self._stateful(line) - if line.error_id: - info = self.error_ids.get(line.error_id) - for f in hit: - f.error_ids[line.error_id] = info.action if info else "" - if not matched: - self._hit_error_id(line, info) - if line.level == "E": - self.boot.recent_errors.append(text) - if not matched and line.tag and not line.error_id: - self._hit_other(line) - - def report(self) -> Report: - all_f = list(self.findings.values()) + self._reboot_findings() - cur = self.boot.index - active = [f for f in all_f if f.active] - current = [f for f in active if f.boot == cur] - earlier = [f for f in active if f not in current] - resolved = [f for f in all_f if f.resolved and f.count >= f.min_count] - roots = _group(current) - # Worst consequence first, then the root's own severity, then order seen. - roots.sort(key=lambda rc: (-rc.severity, -rc.finding.severity, rc.finding.first_uptime_ms or 0)) - - mid_run = (not any(b.explicit or "i2c" in b.kota or "boot" in b.kota for b in self.boots) - and self.boot.index == 0) - if self.lines == 0: - verdict, vsev = "Waiting for data from the sensorbox...", None - elif roots and roots[0].severity >= Severity.WARNING: - vsev = roots[0].severity - prefix = "Problem" if vsev >= Severity.ERROR else "Attention" - verdict = f"{prefix}: {roots[0].finding.title}" - elif "data_uploaded" in self.milestones: - verdict, vsev = "Healthy: data is reaching InfluxDB", None - else: - verdict, vsev = "No problems found so far", None - if mid_run and self.lines: - verdict += " (connected mid-run - press Reset & capture for a full check)" - - ms = [(mid, label, self.milestones.get(mid)) for mid, label, optional in MILESTONES - if not optional or mid in self.milestones] - # Reached milestones in the order they happened, then the pending ones. - ms.sort(key=lambda m: (m[2] is None, m[2] or 0)) - pattern = self.led_pattern - return Report(verdict, vsev, self.led, self.led_reason, self.firmware, ms, roots, - earlier, resolved, list(self.boots), self.lines, mid_run, list(self.technician), - led_pattern=pattern, led_meaning=kota.led_meaning(self.led, pattern) if pattern else "", - led_exact=self.led_exact, box=self._box_summary(), - matter_qr=self.matter_qr, matter_manual=self.matter_manual) - - def add_confirmed(self, key: str, severity: Severity, title: str, meaning: str, - actions: tuple[str, ...]) -> Finding: - """A finding established by a guided check (technician answer + log).""" - f = self.findings.get(key) - if f is None: - f = self.findings[key] = Finding(key, key, severity, title, meaning, tuple(actions)) - f.count += 1 - f.boot = self.boot.index - f.resolved = False - f.evidence.append("confirmed by the technician during a guided check") - return f - - def note_technician(self, text: str) -> None: - self.technician.append(text) - - # ------------------------------------------------------------ internals - def _new_boot(self, host_time: float, explicit: bool) -> None: - prev = self.boot - # An inferred boot right after a banner-started boot is the same boot. - if not explicit and prev.explicit and not prev.saw_log: - return - prev.led_at_end = self.led - b = BootRecord(prev.index + 1, host_time, explicit) - if self._tool_reset_at is not None and host_time - self._tool_reset_at <= _RESET_BY_TOOL_WINDOW_S: - b.tool_reset = True - self._tool_reset_at = None - self.boots.append(b) - self.milestones = {} - # The WS2812 latches its colour through a software reset until - # Status::init() rewrites it; only a power-on turns it dark. - self.led_reason = "restarting - LED keeps its last colour until the firmware starts" - self.firmware = FirmwareInfo() - self._panic_tail = 0 - self._reset_led_state() - - def _set_led(self, colour: str, led_from: str | None, text: str) -> None: - if led_from and self.led != led_from: - return - self.led = colour - self.led_reason = text - - def _hit(self, rule: Rule, groups: dict[str, str], line: LogLine) -> None: - key = rule.id - if rule.split_by and rule.split_by in groups: - key = f"{rule.id}:{groups[rule.split_by]}" - f = self.findings.get(key) - if f is None: - f = self.findings[key] = Finding( - key, rule.id, rule.severity, _fmt(rule.title, groups), _fmt(rule.meaning, groups), - tuple(_fmt(a, groups) for a in rule.actions), rule.explained_by, rule.min_count) - self._touch(f, line) - return f - - def _touch(self, f: Finding, line: LogLine) -> None: - f.count += 1 - # Uptime restarts every boot, so "first seen" is per current boot. - if f.first_uptime_ms is None or f.boot != self.boot.index: - f.first_uptime_ms = line.uptime_ms - f.last_uptime_ms = line.uptime_ms - f.last_seen = line.host_time - f.boot = self.boot.index - f.evidence.append(line.text) - if f.resolved: - f.resolved, f.resolved_by = False, None # problem came back - - def _custom(self, key: str, severity: Severity, title: str, meaning: str, - actions: tuple[str, ...], explained_by: tuple[str, ...] = ()) -> Finding: - f = self.findings.get(key) - if f is None: - f = self.findings[key] = Finding(key, key, severity, title, meaning, actions, explained_by) - return f - - def _hit_other(self, line: LogLine) -> None: - f = self._custom( - f"other.{line.tag}", Severity.INFO, f"Unrecognised error messages from {line.tag}", - "These errors are not in the rule set yet; they may be harmless.", - ("If the box misbehaves, save the session log and send it to the firmware team.",)) - self._touch(f, line) - - def _crash_finding(self) -> Finding: - return self._custom( - "sys.crash", Severity.CRITICAL, "Firmware stopped with an error and restarts", - "The firmware hit a fatal error. The error printed just before it is the real cause.", - ("See the grouped cause; otherwise save the session log for the firmware team.",), - tuple(_INIT_FAILURES) + ("reboot.brownout",)) - - def _on_panic(self, line: LogLine) -> None: - b = self.boot - if len(b.panic_lines) < 8: - b.panic_lines.append(line.text) - self._panic_tail = 4 # file/func/expression lines follow ESP_ERROR_CHECK - f = self._crash_finding() - if f.count == 0 and b.recent_errors: - f.meaning += f" Last error before it: {b.recent_errors[-1]}" - self._touch(f, line) - - def _stateful(self, line: LogLine) -> None: - # RTC set by SNTP but reads "uninitialized" again later: the chip - # does not keep time (crystal/module fault), not just a flat cell. - synced = self.milestones.get("time_synced") - if (synced is not None and line.uptime_ms is not None - and line.uptime_ms > synced + 10_000 - and "RTC is uninitialized" in line.text): - f = self._custom( - "rtc.lost_after_sync", Severity.ERROR, - "Clock stops again after being set from the Internet", - "The clock was set from the Internet but stopped running shortly after. The RTC module or its crystal is faulty.", - ("Replace the RTC module (inside the case).",)) - self._touch(f, line) - - # ------------------------------------------------------- KOTA: lines - def _on_kota(self, line: LogLine) -> None: - self.kota_seen = True - rec, f = line.record or "", line.fields - # A second boot line in one boot: the ROM banner and the uptime drop - # were both missed (e.g. capture started with a gap). - if rec == "boot" and "boot" in self.boot.kota: - self._new_boot(line.host_time, explicit=False) - key = f"{rec}.verify" if rec == "rtc" and "verify" in line.words else rec - self.boot.kota[key] = line - handler = getattr(self, f"_kota_{rec}", None) - if handler is not None: - handler(line, f) - - def _raise(self, rule_id: str, line: LogLine, groups: dict[str, str] | None = None) -> Finding: - """Raise a rules.toml finding from parsed KOTA fields (no pattern matching).""" - return self._hit(self._rule_by_id[rule_id], groups or {}, line) - - def _resolve(self, rule_id: str, by: str, key: str | None = None) -> None: - for f in self.findings.values(): - if f.rule_id == rule_id and (key is None or f.key == key) and not f.resolved: - f.resolved, f.resolved_by = True, by - - def _milestone(self, mid: str, line: LogLine) -> None: - if mid not in self.milestones: - self.milestones[mid] = line.uptime_ms or 0 - - def _kota_boot(self, line: LogLine, f: dict[str, str]) -> None: - b = self.boot - self.firmware.observe_kota(f) - mac = f.get("mac", "") - if re.fullmatch(r"[0-9A-Fa-f]{12}", mac): - self.mac = ":".join(mac[i:i + 2] for i in range(0, 12, 2)).upper() - b.reset_reason = b.reset_reason or f.get("reset") - b.last_restart = f.get("last_restart") - b.mode = f.get("mode") - self._milestone("app_started", line) - - def _kota_restart(self, line: LogLine, f: dict[str, str]) -> None: - reason = f.get("reason", "") - self.boot.restart_reason = reason - if reason in kota.INTENTIONAL_RESTARTS: - self.boot.intentional_restart = True - - def _kota_i2c(self, line: LogLine, f: dict[str, str]) -> None: - if f.get("bus") == "stuck": - self._raise("kota.i2c_stuck", line, {"sda": f.get("SDA", "?"), "scl": f.get("SCL", "?")}) - return - self._resolve("kota.i2c_stuck", "I2C bus free again") - missing = False - for addr, value in f.items(): - if not addr.startswith("0x") or value == "?": - continue - name = value.removesuffix("(missing)") - if value.endswith("(missing)"): - missing = True - self._raise("kota.sensor_missing", line, {"sensor": name, "addr": addr}) - else: - self._resolve("kota.sensor_missing", f"{name} found on I2C", f"kota.sensor_missing:{name}") - if not missing: - self._milestone("i2c_ok", line) - - def _kota_rtc(self, line: LogLine, f: dict[str, str]) -> None: - if "verify" in line.words: - if f.get("ok") == "1": - self._milestone("clock_verified", line) - self._resolve("kota.rtc_not_ticking", "clock verified") - else: - self._raise("kota.rtc_not_ticking", line, - {"running": f.get("running", "?"), "delta": f.get("delta", "no reading")}) - elif "read_failed" in line.words: - self._raise("kota.rtc_read_failed", line, {"err": f.get("err", "?")}) - elif f.get("running") == "0": - self._raise("kota.rtc_stopped", line, {"time": f.get("time", "?")}) - elif f.get("running") == "1": - self._resolve("kota.rtc_read_failed", "clock answers") - self._resolve("kota.rtc_stopped", "clock running") - - def _kota_sd(self, line: LogLine, f: dict[str, str]) -> None: - if f.get("mounted") == "0": - self._raise("kota.sd_unmounted", line, {"err": f.get("err", "card missing or unreadable")}) - return - self._milestone("sd_mounted", line) - self._resolve("kota.sd_unmounted", "SD card mounted") - keys = {k for k in f.get("env_keys", "").split(",") if k and k != "-"} - missing = [k for k in ("INFLUX_URL", "INFLUX_TOKEN") if k not in keys] - for k in missing: - self._raise("influx.env_missing", line, {"key": k}) - if not missing: - self._milestone("env_loaded", line) - if f.get("fs") and f["fs"] != "FAT32": - self._raise("kota.sd_not_fat32", line, {"fs": f["fs"]}) - free = kota.parse_size_gb(f.get("free", "")) - if free is not None and free < 0.1: - self._raise("kota.sd_full", line, {"free": f["free"]}) - lines = f.get("buffer_lines", "0") - if lines.isdigit() and int(lines) > 500: - self._raise("kota.buffer_backlog", line, {"lines": lines}) - - def _kota_matter(self, line: LogLine, f: dict[str, str]) -> None: - if f.get("qr", "-").startswith("MT:"): - self.matter_qr = f["qr"] - if f.get("manual", "-") != "-": - self.matter_manual = f["manual"] - fabrics = f.get("fabrics", "") - if fabrics == "0": - self._raise("matter.not_commissioned", line) - elif fabrics.isdigit(): - self._milestone("matter_commissioned", line) - self._resolve("matter.not_commissioned", "paired in Matter") - - def _kota_mic(self, line: LogLine, f: dict[str, str]) -> None: - if f.get("enabled") == "0": - self._raise("mic.disabled", line) - elif f.get("enabled") == "1": - self._resolve("mic.disabled", "microphone switched on") - - def _kota_status(self, line: LogLine, f: dict[str, str]) -> None: - w = line.words - new = w[w.index("->") + 1] if "->" in w and w.index("->") + 1 < len(w) else (w[-1] if w else "") - code = int(f["code"]) if f.get("code", "").isdigit() else 0 - self._apply_status(new, code, line, f.get("reason", "")) - - def _apply_status(self, status: str, code: int, line: LogLine, reason: str) -> None: - self.status = (status, code) - if not self.led_exact: - self.led, self.led_pattern = kota.led_for_status(status, code) - self.led_reason = f"status {status}" + (f" code={code}" if code else "") + ( - f", reason: {reason}" if reason else "") - sf = kota.status_finding(status, code) - key = sf[0] if sf else None - for fd in self.findings.values(): - if fd.rule_id.startswith("status.") and fd.key != key and not fd.resolved: - fd.resolved, fd.resolved_by = True, f"status -> {status}" - if sf: - key, sev, title, meaning, actions, expl = sf - fd = self._custom(key, Severity[sev.upper()], title, meaning, actions, expl) - self._touch(fd, line) - - def _kota_led(self, line: LogLine, f: dict[str, str]) -> None: - if "previous_boot" in line.words: - code = f.get("code", "0") - self.boot.previous_status = (f.get("status", "?"), int(code) if code.isdigit() else 0) - return - if "restart_flash" in line.words: - return - colour = kota.LED_COLOURS.get(f.get("color", "")) - if colour is None: - return - pattern = kota.pattern_text(f) - self.led, self.led_pattern, self.led_exact = ("OFF", "", True) if pattern == "off" else (colour, pattern, True) - if not self.led_reason or self.status is None: - self.led_reason = "reported by the box" - - def _kota_health(self, line: LogLine, f: dict[str, str]) -> None: - up = kota.parse_seconds(f.get("up", "")) or 0 - status = f.get("status", "") - # Connected mid-run: the health line is the first exact status we get. - if status and (self.status is None or self.status[0] != status): - self._apply_status(status, 0, line, "from the health report") - mode = self.boot.mode - wifi = f.get("wifi") - rssi = int(f["rssi"]) if f.get("rssi", "").lstrip("-").isdigit() else None - if wifi == "connected": - self._milestone("wifi_connected", line) - self._resolve("kota.wifi_down", "Wi-Fi connected") - if rssi is not None and rssi < -75: - self._raise("kota.weak_wifi", line, {"rssi": str(rssi)}) - elif rssi is not None: - self._resolve("kota.weak_wifi", "signal OK") - elif wifi == "disconnected" and mode != "provisioning": - self._raise("kota.wifi_down", line) - if f.get("time_valid") == "1": - self._milestone("time_synced", line) - self._resolve("kota.time_invalid", "time valid") - elif f.get("time_valid") == "0" and up >= 120: - self._raise("kota.time_invalid", line) - if f.get("sd") == "missing": - self._raise("kota.sd_unmounted", line, {"err": "reported missing by the health check"}) - last = f.get("last_upload", "never") - if last != "never": - self._milestone("data_uploaded", line) - self._resolve("kota.no_upload", "data uploaded") - elif wifi == "connected" and up >= 300: - self._raise("kota.no_upload", line) - heap = f.get("heap", "") - if heap.isdigit() and int(heap) < 20_000: - self._raise("kota.low_heap", line, {"heap": heap}) - ota = f.get("ota") - if ota == "registered": - self._milestone("ota_registered", line) - self._resolve("kota.ota_unregistered", "registered") - elif ota == "unregistered" and wifi == "connected" and mode != "provisioning": - self._raise("kota.ota_unregistered", line) - - def _hit_error_id(self, line: LogLine, info: kota.ErrorId | None) -> None: - eid = line.error_id or "?" - msg = (line.message or "").strip() - msg = msg if len(msg) <= 90 else msg[:87] + "..." - if info: - sev = Severity.WARNING if info.severity == "warning" else Severity.ERROR - f = self._custom(f"eid.{eid}", sev, f"{info.module_name}: {msg}", - f"The firmware reported error {eid} ({info.module_name}).", - (info.action, "If it repeats, save the session log for the firmware team.")) - f.rule_id = f"eid.{info.module}" - f.error_ids[eid] = info.action - else: - f = self._custom(f"eid.{eid}", Severity.WARNING, f"Error {eid}: {msg}", - "This error ID is not in the tool's error table (newer firmware?).", - ("Save the session log and send it to the firmware team.",)) - self._touch(f, line) - - def _box_summary(self) -> list[tuple[str, str, str]]: - """What the box reported about itself in this boot (KOTA lines), for the panel.""" - k = self.boot.kota - out: list[tuple[str, str, str]] = [] - if ln := k.get("boot"): - f = ln.fields - lr = f.get("last_restart", "none") - bad = kota.restart_finding(lr) is not None - out.append(("Boot", f"v{f.get('fw', '?')} build {f.get('build', '?')}, mode {f.get('mode', '?')}, " - f"reset {f.get('reset', '?')}, last restart: {lr}", "warn" if bad else "ok")) - if ps := self.boot.previous_status: - if ps[0] != "OK": - out.append(("Before restart", f"status {ps[0]}" + (f" code={ps[1]}" if ps[1] else ""), "warn")) - if ln := k.get("i2c"): - f = ln.fields - if f.get("bus") == "stuck": - out.append(("I2C", f"bus stuck (SDA={f.get('SDA')}, SCL={f.get('SCL')})", "bad")) - else: - devs = [(v.removesuffix("(missing)"), v.endswith("(missing)")) - for a, v in f.items() if a.startswith("0x") and v != "?"] - miss = [n for n, m in devs if m] - text = ("missing: " + ", ".join(miss) + "; " if miss else "") + "found: " + ( - ", ".join(n for n, m in devs if not m) or "none") - out.append(("I2C", text, "bad" if miss else "ok")) - if ln := k.get("sd"): - f = ln.fields - if f.get("mounted") == "0": - out.append(("SD card", "not mounted" + (f" ({f['err']})" if f.get("err") else ""), "bad")) - else: - keys = f.get("env_keys", "-") - ok = "INFLUX_URL" in keys and "INFLUX_TOKEN" in keys - out.append(("SD card", f"{f.get('type', '?')} {f.get('size', '?')} {f.get('fs', '?')}, " - f"{f.get('free', '?')} free, ENV keys: {keys}, buffered: {f.get('buffer_lines', '?')}", - "ok" if ok else "bad")) - rtc, verify = k.get("rtc"), k.get("rtc.verify") - if rtc or verify: - parts, state = [], "ok" - if rtc: - if "read_failed" in rtc.words: - parts.append(f"read failed ({rtc.fields.get('err', '?')})") - state = "bad" - else: - running = rtc.fields.get("running") == "1" - parts.append(("running" if running else "STOPPED") + f", time {rtc.fields.get('time', '?')}") - state = "ok" if running else "warn" - if verify: - ok = verify.fields.get("ok") == "1" - parts.append("verified ticking" if ok else "NOT keeping time") - state = state if ok else "bad" - out.append(("Clock (RTC)", "; ".join(parts), state)) - if ln := k.get("matter"): - f = ln.fields - fabrics = f.get("fabrics", "?") - out.append(("Matter", f"{'paired' if fabrics not in ('0', '?') else 'NOT paired'} (fabrics {fabrics}), " - f"pairing window {f.get('window', '?')}", "ok" if fabrics not in ("0", "?") else "warn")) - if ln := k.get("mic"): - on = ln.fields.get("enabled") == "1" - out.append(("Microphone", "on" if on else "switched OFF (switch on the back)", "ok" if on else "info")) - if ln := k.get("health"): - f = ln.fields - wifi = f.get("wifi", "?") - text = (f"up {f.get('up', '?')}, status {f.get('status', '?')}, Wi-Fi {wifi}" - + (f" ({f.get('rssi')} dBm)" if wifi == "connected" else "") - + f", time {'OK' if f.get('time_valid') == '1' else 'NOT set'}, SD {f.get('sd', '?')}, " - f"buffer {f.get('buffer', '?')}, heap {f.get('heap', '?')}, last upload {f.get('last_upload', '?')}" - + ("" if f.get("last_upload") == "never" else " ago") + f", OTA {f.get('ota', '?')}") - good = f.get("status") == "OK" and wifi == "connected" and f.get("last_upload") != "never" - out.append(("Health", text, "ok" if good else "warn")) - return out - - # ------------------------------------------------------------ reboots - def _reboot_findings(self) -> list[Finding]: - """Classify why each earlier boot ended (needs the next boot's reset reason).""" - out: list[Finding] = [] - short_unexpected: list[BootRecord] = [] - cur = self.boot - # The box keeps its status across a restart ("led previous_boot"): if - # it was in an error state right before the latest restart (often the - # technician's Reset & capture), that state is worth showing. - if cur.previous_status and cur.previous_status[0] not in ("OK", "UPDATING", "SELF_CHECK"): - sf = kota.status_finding(*cur.previous_status) - st, code = cur.previous_status - # Only informative once the box reports OK again in this boot. - sev = Severity.INFO if self.status and self.status[0] == "OK" else Severity.WARNING - f = Finding("boot.previous_status", "boot.previous_status", sev, - f"Before the last restart: {sf[2] if sf else st}", - "The box remembers its status across a restart. Right before the latest restart it was in " - f"state {st}" + (f" code={code}" if code else "") + ". " - + (sf[3] if sf else "") + " Check whether the problem comes back in this boot.", - sf[4] if sf else ()) - f.count, f.boot, f.last_seen = 1, cur.index, cur.host_time - f.evidence.append(f"led previous_boot status={st} code={code}") - out.append(f) - for prev, nxt in zip(self.boots, self.boots[1:]): - if not prev.saw_log and prev.index == 0: - continue # nothing captured before the first boot - reason = nxt.reset_reason or "unknown" - if nxt.tool_reset or prev.intentional_restart: - continue - # Firmware 1.0.16+ records why it restarted itself. - last = nxt.last_restart or prev.restart_reason - if last in kota.INTENTIONAL_RESTARTS: - continue - key, sev, title, meaning, actions, expl = None, Severity.INFO, "", "", (), () - rf = kota.restart_finding(last) if last else None - if prev.brownout or "BROWN" in reason or last == "brownout": - # A crash right after a brown-out is its effect, not a firmware bug. - key, sev, title = "reboot.brownout", Severity.CRITICAL, "Power supply voltage drops (brown-out)" - meaning = ("The supply voltage fell too low and the chip reset itself - typically when the " - "Wi-Fi/Bluetooth radio starts. The box cannot run like this.") - actions = ("Switch the box's power switch ON - USB from a laptop alone may not be enough.", - "Check the power supply, cable and connectors; try another supply.", - "If it persists on a good supply: the board's power circuit is faulty.") - elif rf and not (last == "panic" and prev.panic_lines): - sev_name, title, meaning, actions = rf - sev = Severity[sev_name.upper()] - key = {"status_watchdog": "reboot.status_watchdog", "panic": "reboot.crash", - "brownout": "reboot.brownout", "task_wdt": "reboot.watchdog", "int_wdt": "reboot.watchdog", - "wdt": "reboot.watchdog"}.get(last, f"reboot.{last}") - if nxt.previous_status and nxt.previous_status[0] != "OK": - st, code = nxt.previous_status - meaning += f" Before the restart the box was in state {st}" + (f" code={code}" if code else "") + "." - if last == "status_watchdog": - expl = tuple(_INIT_FAILURES) + kota._SENSOR_ROOTS + ("status.error", "sys.out_of_resources") - elif last == "status_oom_watchdog": - expl = ("sys.out_of_resources",) - elif rf: - pass # panic with captured crash output: covered by sys.crash - elif "USB_UART_CHIP_RESET" in reason: - key, title = "reboot.usb", "Box was reset over USB" - meaning = "The board was reset through its USB port (a flashing tool, a serial program, or this tool in an earlier session)." - elif "POWERON" in reason: - key, title = "reboot.power", "Box was switched off and on" - meaning = "Power was interrupted (power switch, cable or supply)." - actions = ("If nobody switched it off: check the power supply and cable.",) - elif "WDT" in reason: - key, sev, title = "reboot.watchdog", Severity.ERROR, "Firmware froze and was restarted by the watchdog" - meaning = "The firmware stopped responding; the hardware watchdog restarted it." - actions = ("Save the session log for the firmware team.",) - elif prev.panic_lines: - pass # covered by sys.crash - elif (not nxt.last_restart and prev.led_at_end in ("PURPLE", "RED") - and prev.last_uptime_ms >= 140_000): # older firmware: no restart record - key, title = "reboot.status_watchdog", "Box restarted itself after 2.5 minutes of errors" - meaning = "By design the firmware restarts when it stays in an error state (purple/red) for 150 s." - actions = ("Fix the grouped cause; the restarts stop by themselves.",) - expl = tuple(_NETWORK_ROOTS) + ("sys.out_of_resources",) - else: - key, sev, title = "reboot.unexpected", Severity.WARNING, "Unexpected restart" - meaning = f"The firmware restarted (reset reason {reason})." - actions = ("Watch whether it repeats; save the session log for the firmware team.",) - if key: - out.append(self._reboot_finding(key, sev, title, meaning, actions, expl, nxt)) - if prev.last_uptime_ms < _SHORT_BOOT_MS and "POWERON" not in reason: - short_unexpected.append(prev) - if len(short_unexpected) >= 2: - avg = sum(b.last_uptime_ms for b in short_unexpected) / len(short_unexpected) / 1000 - f = self._reboot_finding( - "reboot.loop", Severity.CRITICAL, "Reboot loop", - f"The box restarted {len(short_unexpected)} times, about every {avg:.0f} s. " - "The error printed just before each restart is the cause.", - ("Fix the grouped cause below.",), tuple(_INIT_FAILURES) + ("reboot.brownout",), self.boot) - f.count = len(short_unexpected) - out.append(f) - # One finding per cause, counting how often it happened. - merged: dict[str, Finding] = {} - for f in out: - if f.key in merged: - merged[f.key].count += f.count - merged[f.key].evidence.extend(f.evidence) - else: - merged[f.key] = f - return list(merged.values()) - - def _reboot_finding(self, key, sev, title, meaning, actions, expl, boot: BootRecord) -> Finding: - f = Finding(key, key, sev, title, meaning, actions, expl) - f.count, f.boot, f.last_seen = 1, self.boot.index, boot.host_time - if boot.reset_reason: - f.evidence.append(f"reset reason: {boot.reset_reason}") - return f - - -def _group(findings: list[Finding]) -> list[RootCause]: - """Attach each finding to the root of its explained_by chain.""" - by_rule: dict[str, list[Finding]] = {} - for f in findings: - by_rule.setdefault(f.rule_id, []).append(f) - - def parent(f: Finding) -> Finding | None: - for rid in f.explained_by: - if by_rule.get(rid): - return by_rule[rid][0] - return None - - def root(f: Finding) -> Finding: - seen = {f.key} - while (p := parent(f)) is not None and p.key not in seen: - seen.add(p.key) - f = p - return f - - roots: dict[str, RootCause] = {} - for f in findings: - r = root(f) - rc = roots.setdefault(r.key, RootCause(r, [])) - if r is not f: - rc.consequences.append(f) - for rc in roots.values(): - rc.consequences.sort(key=lambda c: (-c.severity, c.first_uptime_ms or 0)) - return list(roots.values()) +"""Diagnostics engine: parsed log lines -> findings, milestones, LED, verdict. + +Line-level knowledge lives in rules.toml. This module adds what a single +line cannot express: + * boot tracking (ROM banner, reset reason, uptime drops when the banner + was missed) and classification of why the previous boot ended; + * the status-LED model (Status::set_status / set_status_conditional); + * crash capture (panic / ESP_ERROR_CHECK output); + * root-cause grouping via `explained_by` chains; + * the structured "KOTA:" lines of firmware 1.0.16+ (diag/kota.py): exact + status/LED, restart reasons, I2C/SD/RTC/Matter/mic self-report and the + 60 s health line, plus error IDs ("[E-SD-03]") looked up in + error_ids.csv. Older firmware is still diagnosed from message text. + +Findings are session-wide (a reboot loop must keep its cause visible); +milestones and the LED are per boot. +""" + +from __future__ import annotations + +import re +import tomllib +from collections import Counter, deque +from dataclasses import dataclass, field +from enum import IntEnum +from pathlib import Path + +from ..core.logline import LineKind, LogLine +from . import kota +from .firmware import FirmwareInfo + +RULES_PATH = Path(__file__).with_name("rules.toml") +_MAC_LINE = re.compile(r"Device MAC address: ([0-9A-Fa-f]{2}(?::[0-9A-Fa-f]{2}){5})") + + +class Severity(IntEnum): + IGNORE = -1 + INFO = 1 + WARNING = 2 + ERROR = 3 + CRITICAL = 4 + + @property + def label(self) -> str: + return self.name.capitalize() + + +# name -> (label, swatch colour) +LED = { + "UNKNOWN": ("Unknown", "#888888"), + "OFF": ("Off", "#444444"), + "GREEN": ("Green", "#2e9e44"), + "PURPLE": ("Purple", "#8e44ad"), + "RED": ("Red", "#d64541"), + "BLUE": ("Blue", "#2f6fd6"), + "CYAN": ("Cyan", "#1fc8d8"), + "YELLOW": ("Yellow", "#e6c229"), + "ORANGE": ("Orange", "#f08c1a"), + "WHITE": ("White", "#f4f4f4"), +} + +# (id, label, optional) - optional milestones are listed only once reached. +MILESTONES = [ + ("rom_boot", "Board booted", False), + ("app_started", "Firmware started", False), + ("sd_mounted", "SD card mounted", False), + ("env_loaded", "ENV.TXT read (InfluxDB settings)", False), + ("matter_started", "Matter started", True), + ("matter_commissioned", "Paired in Matter", True), + ("local_wifi_mode", "Local Wi-Fi mode (SSID from SD card)", True), + ("i2c_ok", "All sensors answer on the I2C bus", True), + ("sensors_ready", "All sensors started", False), + ("wifi_connected", "Connected to Wi-Fi", False), + ("time_synced", "Clock set from the Internet", False), + ("clock_verified", "Clock keeps time (RTC verified)", True), + ("data_uploaded", "Data uploaded to InfluxDB", False), + ("ota_registered", "Registered for firmware updates", False), + ("ota_checked", "Checked for firmware updates", False), + ("ota_update_started", "Firmware update started", True), + ("ota_update_done", "Firmware update installed", True), + ("ota_update_accepted", "Updated firmware accepted", True), +] + +# Findings that stop start-up; a crash/reboot right after them is their effect. +_INIT_FAILURES = [ + "rtc.init_failed", "sensor.init_failed", "sd.card_fail", "sd.mount_fail", + "sd.env_open_fail", "influx.env_missing", "influx.init_fail", + "kota.i2c_stuck", "kota.sensor_missing", "kota.rtc_read_failed", "kota.sd_unmounted", +] +_NETWORK_ROOTS = [ + "influx.buffer_fail", "ota.check_failed", "net.dns_fail", + "matter.not_commissioned", "wifi.disconnected", +] +# Printed by the brown-out handler without a timestamp ("E BOD: ..."); on the +# ESP32-S3 the ROM then reports RTC_SW_SYS_RST, not a brown-out reset. +_BROWNOUT = "Brownout detector was triggered" +_RESET_BY_TOOL_WINDOW_S = 5.0 +_SHORT_BOOT_MS = 60_000 + + +class _Fmt(dict): + def __missing__(self, key: str) -> str: + return "?" + + +def _fmt(s: str, groups: dict[str, str]) -> str: + return s.format_map(_Fmt(groups)) if s else s + + +@dataclass(frozen=True) +class Rule: + id: str + pattern: re.Pattern[str] | None # None: raised from KOTA: fields + severity: Severity + title: str = "" + meaning: str = "" + actions: tuple[str, ...] = () + explained_by: tuple[str, ...] = () + milestone: str | None = None + clears: tuple[str, ...] = () + led: str | None = None + led_from: str | None = None + split_by: str | None = None + min_count: int = 1 + min_uptime_ms: int = 0 + + +def load_rules(path: Path = RULES_PATH) -> list[Rule]: + data = tomllib.loads(path.read_text(encoding="utf-8")) + rules = [] + for r in data["rule"]: + rules.append(Rule( + id=r["id"], + pattern=re.compile(r["pattern"]) if "pattern" in r else None, + severity=Severity[r["severity"].upper()], + title=r.get("title", ""), + meaning=r.get("meaning", ""), + actions=tuple(r.get("actions", ())), + explained_by=tuple(r.get("explained_by", ())), + milestone=r.get("milestone"), + clears=tuple(r.get("clears", ())), + led=r.get("led"), + led_from=r.get("led_from"), + split_by=r.get("split_by"), + min_count=int(r.get("min_count", 1)), + min_uptime_ms=int(r.get("min_uptime_ms", 0)), + )) + _validate(rules) + return rules + + +def _validate(rules: list[Rule]) -> None: + ids = [r.id for r in rules] + dupes = [i for i, n in Counter(ids).items() if n > 1] + if dupes: + raise ValueError(f"duplicate rule ids: {dupes}") + known = set(ids) + for r in rules: + for ref in (*r.explained_by, *r.clears): + # status.* / reboot.* findings are created by the engine itself. + if ref not in known and not ref.startswith(("status.", "reboot.")): + raise ValueError(f"{r.id}: unknown rule id {ref!r}") + for colour in (r.led, r.led_from): + if colour and colour not in LED: + raise ValueError(f"{r.id}: unknown LED colour {colour!r}") + if r.severity != Severity.IGNORE and not r.title: + raise ValueError(f"{r.id}: visible rule needs a title") + + +@dataclass +class Finding: + key: str # rule id, or "rule id:split value" + rule_id: str + severity: Severity + title: str + meaning: str + actions: tuple[str, ...] + explained_by: tuple[str, ...] = () + min_count: int = 1 + count: int = 0 + first_uptime_ms: int | None = None + last_uptime_ms: int | None = None + last_seen: float = 0.0 + boot: int = 0 + resolved: bool = False + resolved_by: str | None = None + evidence: deque[str] = field(default_factory=lambda: deque(maxlen=3)) + error_ids: dict[str, str] = field(default_factory=dict) # "E-SD-03" -> technician action + + @property + def active(self) -> bool: + return self.count >= self.min_count and not self.resolved + + def seen_text(self) -> str: + """'Seen 3x, first at 2.9 s after start, last at 61.2 s' (panel and service note).""" + s = f"Seen {self.count}x" + if self.first_uptime_ms is not None: + first = f"{self.first_uptime_ms / 1000:.1f}" + s += f", first at {first} s after start" + last = f"{self.last_uptime_ms / 1000:.1f}" if self.last_uptime_ms is not None else first + if self.count > 1 and last != first: + s += f", last at {last} s" + if self.resolved and self.resolved_by: + s += f" - resolved ({self.resolved_by})" + return s + + +@dataclass +class BootRecord: + index: int + host_time: float + explicit: bool # ROM banner seen (vs. inferred) + reset_reason: str | None = None + tool_reset: bool = False + last_uptime_ms: int = 0 + saw_log: bool = False + led_at_end: str | None = None + panic_lines: list[str] = field(default_factory=list) + brownout: bool = False # "E BOD: Brownout detector was triggered" before the restart + recent_errors: deque[str] = field(default_factory=lambda: deque(maxlen=5)) + intentional_restart: bool = False # factory reset / setup-mode toggle + # KOTA: lines (firmware 1.0.16+) + kota: dict[str, LogLine] = field(default_factory=dict) # last line per record + last_restart: str | None = None # "boot ... last_restart=" (why the previous boot ended) + restart_reason: str | None = None # "restart reason=" printed just before this boot ended + previous_status: tuple[str, int] | None = None # "led previous_boot status= code=" + mode: str | None = None # matter | wifi | provisioning + + +@dataclass +class RootCause: + finding: Finding + consequences: list[Finding] + + @property + def severity(self) -> Severity: + return max([self.finding.severity, *(c.severity for c in self.consequences)]) + + +@dataclass +class Report: + verdict: str + verdict_severity: Severity | None # None = healthy / nothing found + led: str + led_reason: str + firmware: FirmwareInfo + milestones: list[tuple[str, str, int | None]] # (id, label, uptime_ms) + roots: list[RootCause] + earlier: list[Finding] # seen only in previous boots + resolved: list[Finding] + boots: list[BootRecord] + lines: int + mid_run: bool # no boot seen yet + technician: list[str] = field(default_factory=list) # guided-check answers + led_pattern: str = "" # "2 pulses", "solid", ... (exact when from a KOTA led line) + led_meaning: str = "" + led_exact: bool = False # reported by the box itself + box: list[tuple[str, str, str]] = field(default_factory=list) # (topic, text, ok|warn|bad|info) + matter_qr: str | None = None + matter_manual: str | None = None + + def led_text(self) -> str: + t = LED[self.led][0] + (f", {self.led_pattern}" if self.led_pattern else "") + return t + (f" = {self.led_meaning}" if self.led_meaning else "") + + def to_text(self) -> str: + out = [f"VERDICT: {self.verdict}", + f"Expected status LED: {self.led_text()}" + + (f" (last change: {self.led_reason})" if self.led_reason else ""), + f"Firmware: {self.firmware.describe()}", + f"Boots seen: {max(len(self.boots) - (0 if self.boots and self.boots[0].explicit else 1), 0)}"] + if self.box: + out += ["", "Box self-report (KOTA lines):"] + out += [f" {topic}: {text}" for topic, text, _ in self.box] + out += ["", "Milestones (current boot):"] + for _, label, t in self.milestones: + out.append(f" [{'x' if t is not None else ' '}] {label}" + + (f" @ {t / 1000:.1f} s" if t is not None else "")) + out.append("") + out.append("Findings:" if self.roots else "Findings: none") + for rc in self.roots: + out += _finding_text(rc.finding, " ") + for c in rc.consequences: + out.append(" consequence:") + out += _finding_text(c, " ") + if self.earlier: + out.append("") + out.append("Seen in earlier boots only:") + for f in self.earlier: + out.append(f" - [{f.severity.label}] {f.title} (x{f.count})") + if self.resolved: + out.append("") + out.append("Resolved during this session:") + for f in self.resolved: + out.append(f" - {f.title}" + (f" (by: {f.resolved_by})" if f.resolved_by else "")) + if self.technician: + out.append("") + out.append("Technician checks:") + out += [f" - {t}" for t in self.technician] + return "\n".join(out) + + +def _finding_text(f: Finding, ind: str) -> list[str]: + lines = [f"{ind}- [{f.severity.label}] {f.title} (x{f.count})", f"{ind} {f.meaning}"] + lines += [f"{ind} {i}. {a}" for i, a in enumerate(f.actions, 1)] + for eid, action in f.error_ids.items(): + lines.append(f"{ind} error {eid}" + (f": {action}" if action else "")) + if f.evidence: + lines.append(f"{ind} log: {f.evidence[-1]}") + return lines + + +class DiagnosticsEngine: + def __init__(self, rules: list[Rule] | None = None): + self.rules = rules if rules is not None else load_rules() + self._rule_by_id = {r.id: r for r in self.rules} + self._pattern_rules = [r for r in self.rules if r.pattern is not None] + self.error_ids = kota.load_error_ids() + self.reset() + + # ---------------------------------------------------------------- API + def reset(self) -> None: + self.findings: dict[str, Finding] = {} + self.boots: list[BootRecord] = [BootRecord(0, 0.0, explicit=False)] + self.milestones: dict[str, int] = {} + self.led = "UNKNOWN" + self.led_reason = "" + self.firmware = FirmwareInfo() + self.lines = 0 + self._tool_reset_at: float | None = None + self._panic_tail = 0 # RAW lines after a panic line to keep + self.technician: list[str] = [] + self.mac: str | None = None # from "ID: Device MAC address: ..." at boot + self.matter_advert: str | None = None # "Advertise commission parameter vendorID=..." line + self.matter_qr: str | None = None # "KOTA: matter ... qr=MT:..." + self.matter_manual: str | None = None + self.kota_seen = False # firmware prints KOTA: lines + self._reset_led_state() + + def _reset_led_state(self) -> None: + self.led_pattern = "" + self.led_exact = False # a KOTA led line was seen this boot + self.status: tuple[str, int] | None = None + + @property + def boot(self) -> BootRecord: + return self.boots[-1] + + def feed(self, line: LogLine) -> None: + self.lines += 1 + text = line.text + if line.kind == LineKind.HOST: + if "reset issued by tool" in text: + self._tool_reset_at = line.host_time + return + if _BROWNOUT in text: + self.boot.brownout = True + if line.kind == LineKind.BOOT: + self._new_boot(line.host_time, explicit=True) + self.milestones["rom_boot"] = 0 + return + if line.kind == LineKind.RESET: + self.boot.reset_reason = line.reset_reason + if line.reset_reason and "POWERON" in line.reset_reason: + self.led, self.led_reason = "OFF", "power-on - firmware not started yet" + return + if line.kind in (LineKind.LOG, LineKind.KOTA) and line.uptime_ms is not None: + b = self.boot + # Uptime jumped back to a fresh-boot value: the ROM banner was + # missed (port reconnect, capture started late). + if b.saw_log and line.uptime_ms + 3000 < b.last_uptime_ms and line.uptime_ms < 10_000: + self._new_boot(line.host_time, explicit=False) + b = self.boot + b.saw_log = True + b.last_uptime_ms = max(b.last_uptime_ms, line.uptime_ms) + + if line.kind == LineKind.PANIC: + self._on_panic(line) + elif self._panic_tail and line.kind == LineKind.RAW: + self.boot.panic_lines.append(text) + self._panic_tail -= 1 + self._crash_finding().evidence.append(text) + + if line.kind == LineKind.KOTA: + self._on_kota(line) + return + + self.firmware.observe(text) + if m := _MAC_LINE.search(text): + self.mac = m.group(1).upper() + if "Advertise commission parameter" in text: + self.matter_advert = text + matched = False + hit: list[Finding] = [] + rule_text = line.match_text + for rule in self._pattern_rules: + m = rule.pattern.search(rule_text) + if not m: + continue + matched = True + if rule.min_uptime_ms and (line.uptime_ms or 0) < rule.min_uptime_ms: + continue + groups = {k: v for k, v in m.groupdict().items() if v is not None} + if rule.milestone and rule.milestone not in self.milestones: + self.milestones[rule.milestone] = line.uptime_ms or 0 + # The box's own "KOTA: led" lines are exact; message rules only guess. + if rule.led and not self.led_exact: + self._set_led(rule.led, rule.led_from, text) + for cid in rule.clears: + for f in self.findings.values(): + if f.rule_id == cid and not f.resolved: + f.resolved = True + f.resolved_by = rule.milestone or rule.id + if rule.id in ("sys.factory_reset",) or "Short press:" in text: + self.boot.intentional_restart = True + if rule.severity != Severity.IGNORE: + hit.append(self._hit(rule, groups, line)) + + self._stateful(line) + if line.error_id: + info = self.error_ids.get(line.error_id) + for f in hit: + f.error_ids[line.error_id] = info.action if info else "" + if not matched: + self._hit_error_id(line, info) + if line.level == "E": + self.boot.recent_errors.append(text) + if not matched and line.tag and not line.error_id: + self._hit_other(line) + + def report(self) -> Report: + all_f = list(self.findings.values()) + self._reboot_findings() + cur = self.boot.index + active = [f for f in all_f if f.active] + current = [f for f in active if f.boot == cur] + earlier = [f for f in active if f not in current] + resolved = [f for f in all_f if f.resolved and f.count >= f.min_count] + roots = _group(current) + # Worst consequence first, then the root's own severity, then order seen. + roots.sort(key=lambda rc: (-rc.severity, -rc.finding.severity, rc.finding.first_uptime_ms or 0)) + + mid_run = (not any(b.explicit or "i2c" in b.kota or "boot" in b.kota for b in self.boots) + and self.boot.index == 0) + if self.lines == 0: + verdict, vsev = "Waiting for data from the sensorbox...", None + elif roots and roots[0].severity >= Severity.WARNING: + vsev = roots[0].severity + prefix = "Problem" if vsev >= Severity.ERROR else "Attention" + verdict = f"{prefix}: {roots[0].finding.title}" + elif "data_uploaded" in self.milestones: + verdict, vsev = "Healthy: data is reaching InfluxDB", None + else: + verdict, vsev = "No problems found so far", None + if mid_run and self.lines: + verdict += " (connected mid-run - press Reset & capture for a full check)" + + ms = [(mid, label, self.milestones.get(mid)) for mid, label, optional in MILESTONES + if not optional or mid in self.milestones] + # Reached milestones in the order they happened, then the pending ones. + ms.sort(key=lambda m: (m[2] is None, m[2] or 0)) + pattern = self.led_pattern + return Report(verdict, vsev, self.led, self.led_reason, self.firmware, ms, roots, + earlier, resolved, list(self.boots), self.lines, mid_run, list(self.technician), + led_pattern=pattern, led_meaning=kota.led_meaning(self.led, pattern) if pattern else "", + led_exact=self.led_exact, box=self._box_summary(), + matter_qr=self.matter_qr, matter_manual=self.matter_manual) + + def add_confirmed(self, key: str, severity: Severity, title: str, meaning: str, + actions: tuple[str, ...]) -> Finding: + """A finding established by a guided check (technician answer + log).""" + f = self.findings.get(key) + if f is None: + f = self.findings[key] = Finding(key, key, severity, title, meaning, tuple(actions)) + f.count += 1 + f.boot = self.boot.index + f.resolved = False + f.evidence.append("confirmed by the technician during a guided check") + return f + + def note_technician(self, text: str) -> None: + self.technician.append(text) + + # ------------------------------------------------------------ internals + def _new_boot(self, host_time: float, explicit: bool) -> None: + prev = self.boot + # An inferred boot right after a banner-started boot is the same boot. + if not explicit and prev.explicit and not prev.saw_log: + return + prev.led_at_end = self.led + b = BootRecord(prev.index + 1, host_time, explicit) + if self._tool_reset_at is not None and host_time - self._tool_reset_at <= _RESET_BY_TOOL_WINDOW_S: + b.tool_reset = True + self._tool_reset_at = None + self.boots.append(b) + self.milestones = {} + # The WS2812 latches its colour through a software reset until + # Status::init() rewrites it; only a power-on turns it dark. + self.led_reason = "restarting - LED keeps its last colour until the firmware starts" + self.firmware = FirmwareInfo() + self._panic_tail = 0 + self._reset_led_state() + + def _set_led(self, colour: str, led_from: str | None, text: str) -> None: + if led_from and self.led != led_from: + return + self.led = colour + self.led_reason = text + + def _hit(self, rule: Rule, groups: dict[str, str], line: LogLine) -> None: + key = rule.id + if rule.split_by and rule.split_by in groups: + key = f"{rule.id}:{groups[rule.split_by]}" + f = self.findings.get(key) + if f is None: + f = self.findings[key] = Finding( + key, rule.id, rule.severity, _fmt(rule.title, groups), _fmt(rule.meaning, groups), + tuple(_fmt(a, groups) for a in rule.actions), rule.explained_by, rule.min_count) + self._touch(f, line) + return f + + def _touch(self, f: Finding, line: LogLine) -> None: + f.count += 1 + # Uptime restarts every boot, so "first seen" is per current boot. + if f.first_uptime_ms is None or f.boot != self.boot.index: + f.first_uptime_ms = line.uptime_ms + f.last_uptime_ms = line.uptime_ms + f.last_seen = line.host_time + f.boot = self.boot.index + f.evidence.append(line.text) + if f.resolved: + f.resolved, f.resolved_by = False, None # problem came back + + def _custom(self, key: str, severity: Severity, title: str, meaning: str, + actions: tuple[str, ...], explained_by: tuple[str, ...] = ()) -> Finding: + f = self.findings.get(key) + if f is None: + f = self.findings[key] = Finding(key, key, severity, title, meaning, actions, explained_by) + return f + + def _hit_other(self, line: LogLine) -> None: + f = self._custom( + f"other.{line.tag}", Severity.INFO, f"Unrecognised error messages from {line.tag}", + "These errors are not in the rule set yet; they may be harmless.", + ("If the box misbehaves, save the session log and send it to the firmware team.",)) + self._touch(f, line) + + def _crash_finding(self) -> Finding: + return self._custom( + "sys.crash", Severity.CRITICAL, "Firmware stopped with an error and restarts", + "The firmware hit a fatal error. The error printed just before it is the real cause.", + ("See the grouped cause; otherwise save the session log for the firmware team.",), + tuple(_INIT_FAILURES) + ("reboot.brownout",)) + + def _on_panic(self, line: LogLine) -> None: + b = self.boot + if len(b.panic_lines) < 8: + b.panic_lines.append(line.text) + self._panic_tail = 4 # file/func/expression lines follow ESP_ERROR_CHECK + f = self._crash_finding() + if f.count == 0 and b.recent_errors: + f.meaning += f" Last error before it: {b.recent_errors[-1]}" + self._touch(f, line) + + def _stateful(self, line: LogLine) -> None: + # RTC set by SNTP but reads "uninitialized" again later: the chip + # does not keep time (crystal/module fault), not just a flat cell. + synced = self.milestones.get("time_synced") + if (synced is not None and line.uptime_ms is not None + and line.uptime_ms > synced + 10_000 + and "RTC is uninitialized" in line.text): + f = self._custom( + "rtc.lost_after_sync", Severity.ERROR, + "Clock stops again after being set from the Internet", + "The clock was set from the Internet but stopped running shortly after. The RTC module or its crystal is faulty.", + ("Replace the RTC module (inside the case).",)) + self._touch(f, line) + + # ------------------------------------------------------- KOTA: lines + def _on_kota(self, line: LogLine) -> None: + self.kota_seen = True + rec, f = line.record or "", line.fields + # A second boot line in one boot: the ROM banner and the uptime drop + # were both missed (e.g. capture started with a gap). + if rec == "boot" and "boot" in self.boot.kota: + self._new_boot(line.host_time, explicit=False) + key = f"{rec}.verify" if rec == "rtc" and "verify" in line.words else rec + self.boot.kota[key] = line + handler = getattr(self, f"_kota_{rec}", None) + if handler is not None: + handler(line, f) + + def _raise(self, rule_id: str, line: LogLine, groups: dict[str, str] | None = None) -> Finding: + """Raise a rules.toml finding from parsed KOTA fields (no pattern matching).""" + return self._hit(self._rule_by_id[rule_id], groups or {}, line) + + def _resolve(self, rule_id: str, by: str, key: str | None = None) -> None: + for f in self.findings.values(): + if f.rule_id == rule_id and (key is None or f.key == key) and not f.resolved: + f.resolved, f.resolved_by = True, by + + def _milestone(self, mid: str, line: LogLine) -> None: + if mid not in self.milestones: + self.milestones[mid] = line.uptime_ms or 0 + + def _kota_boot(self, line: LogLine, f: dict[str, str]) -> None: + b = self.boot + self.firmware.observe_kota(f) + mac = f.get("mac", "") + if re.fullmatch(r"[0-9A-Fa-f]{12}", mac): + self.mac = ":".join(mac[i:i + 2] for i in range(0, 12, 2)).upper() + b.reset_reason = b.reset_reason or f.get("reset") + b.last_restart = f.get("last_restart") + b.mode = f.get("mode") + self._milestone("app_started", line) + + def _kota_restart(self, line: LogLine, f: dict[str, str]) -> None: + reason = f.get("reason", "") + self.boot.restart_reason = reason + if reason in kota.INTENTIONAL_RESTARTS: + self.boot.intentional_restart = True + + def _kota_i2c(self, line: LogLine, f: dict[str, str]) -> None: + if f.get("bus") == "stuck": + self._raise("kota.i2c_stuck", line, {"sda": f.get("SDA", "?"), "scl": f.get("SCL", "?")}) + return + self._resolve("kota.i2c_stuck", "I2C bus free again") + missing = False + for addr, value in f.items(): + if not addr.startswith("0x") or value == "?": + continue + name = value.removesuffix("(missing)") + if value.endswith("(missing)"): + missing = True + self._raise("kota.sensor_missing", line, {"sensor": name, "addr": addr}) + else: + self._resolve("kota.sensor_missing", f"{name} found on I2C", f"kota.sensor_missing:{name}") + if not missing: + self._milestone("i2c_ok", line) + + def _kota_rtc(self, line: LogLine, f: dict[str, str]) -> None: + if "verify" in line.words: + if f.get("ok") == "1": + self._milestone("clock_verified", line) + self._resolve("kota.rtc_not_ticking", "clock verified") + else: + self._raise("kota.rtc_not_ticking", line, + {"running": f.get("running", "?"), "delta": f.get("delta", "no reading")}) + elif "read_failed" in line.words: + self._raise("kota.rtc_read_failed", line, {"err": f.get("err", "?")}) + elif f.get("running") == "0": + self._raise("kota.rtc_stopped", line, {"time": f.get("time", "?")}) + elif f.get("running") == "1": + self._resolve("kota.rtc_read_failed", "clock answers") + self._resolve("kota.rtc_stopped", "clock running") + + def _kota_sd(self, line: LogLine, f: dict[str, str]) -> None: + if f.get("mounted") == "0": + self._raise("kota.sd_unmounted", line, {"err": f.get("err", "card missing or unreadable")}) + return + self._milestone("sd_mounted", line) + self._resolve("kota.sd_unmounted", "SD card mounted") + keys = {k for k in f.get("env_keys", "").split(",") if k and k != "-"} + missing = [k for k in ("INFLUX_URL", "INFLUX_TOKEN") if k not in keys] + for k in missing: + self._raise("influx.env_missing", line, {"key": k}) + if not missing: + self._milestone("env_loaded", line) + if f.get("fs") and f["fs"] != "FAT32": + self._raise("kota.sd_not_fat32", line, {"fs": f["fs"]}) + free = kota.parse_size_gb(f.get("free", "")) + if free is not None and free < 0.1: + self._raise("kota.sd_full", line, {"free": f["free"]}) + lines = f.get("buffer_lines", "0") + if lines.isdigit() and int(lines) > 500: + self._raise("kota.buffer_backlog", line, {"lines": lines}) + + def _kota_matter(self, line: LogLine, f: dict[str, str]) -> None: + if f.get("qr", "-").startswith("MT:"): + self.matter_qr = f["qr"] + if f.get("manual", "-") != "-": + self.matter_manual = f["manual"] + fabrics = f.get("fabrics", "") + if fabrics == "0": + self._raise("matter.not_commissioned", line) + elif fabrics.isdigit(): + self._milestone("matter_commissioned", line) + self._resolve("matter.not_commissioned", "paired in Matter") + + def _kota_mic(self, line: LogLine, f: dict[str, str]) -> None: + if f.get("enabled") == "0": + self._raise("mic.disabled", line) + elif f.get("enabled") == "1": + self._resolve("mic.disabled", "microphone switched on") + + def _kota_status(self, line: LogLine, f: dict[str, str]) -> None: + w = line.words + new = w[w.index("->") + 1] if "->" in w and w.index("->") + 1 < len(w) else (w[-1] if w else "") + code = int(f["code"]) if f.get("code", "").isdigit() else 0 + self._apply_status(new, code, line, f.get("reason", "")) + + def _apply_status(self, status: str, code: int, line: LogLine, reason: str) -> None: + self.status = (status, code) + if not self.led_exact: + self.led, self.led_pattern = kota.led_for_status(status, code) + self.led_reason = f"status {status}" + (f" code={code}" if code else "") + ( + f", reason: {reason}" if reason else "") + sf = kota.status_finding(status, code) + key = sf[0] if sf else None + for fd in self.findings.values(): + if fd.rule_id.startswith("status.") and fd.key != key and not fd.resolved: + fd.resolved, fd.resolved_by = True, f"status -> {status}" + if sf: + key, sev, title, meaning, actions, expl = sf + fd = self._custom(key, Severity[sev.upper()], title, meaning, actions, expl) + self._touch(fd, line) + + def _kota_led(self, line: LogLine, f: dict[str, str]) -> None: + if "previous_boot" in line.words: + code = f.get("code", "0") + self.boot.previous_status = (f.get("status", "?"), int(code) if code.isdigit() else 0) + return + if "restart_flash" in line.words: + return + colour = kota.LED_COLOURS.get(f.get("color", "")) + if colour is None: + return + pattern = kota.pattern_text(f) + self.led, self.led_pattern, self.led_exact = ("OFF", "", True) if pattern == "off" else (colour, pattern, True) + if not self.led_reason or self.status is None: + self.led_reason = "reported by the box" + + def _kota_health(self, line: LogLine, f: dict[str, str]) -> None: + up = kota.parse_seconds(f.get("up", "")) or 0 + status = f.get("status", "") + # Connected mid-run: the health line is the first exact status we get. + if status and (self.status is None or self.status[0] != status): + self._apply_status(status, 0, line, "from the health report") + mode = self.boot.mode + wifi = f.get("wifi") + rssi = int(f["rssi"]) if f.get("rssi", "").lstrip("-").isdigit() else None + if wifi == "connected": + self._milestone("wifi_connected", line) + self._resolve("kota.wifi_down", "Wi-Fi connected") + if rssi is not None and rssi < -75: + self._raise("kota.weak_wifi", line, {"rssi": str(rssi)}) + elif rssi is not None: + self._resolve("kota.weak_wifi", "signal OK") + elif wifi == "disconnected" and mode != "provisioning": + self._raise("kota.wifi_down", line) + if f.get("time_valid") == "1": + self._milestone("time_synced", line) + self._resolve("kota.time_invalid", "time valid") + elif f.get("time_valid") == "0" and up >= 120: + self._raise("kota.time_invalid", line) + if f.get("sd") == "missing": + self._raise("kota.sd_unmounted", line, {"err": "reported missing by the health check"}) + last = f.get("last_upload", "never") + if last != "never": + self._milestone("data_uploaded", line) + self._resolve("kota.no_upload", "data uploaded") + elif wifi == "connected" and up >= 300: + self._raise("kota.no_upload", line) + heap = f.get("heap", "") + if heap.isdigit() and int(heap) < 20_000: + self._raise("kota.low_heap", line, {"heap": heap}) + ota = f.get("ota") + if ota == "registered": + self._milestone("ota_registered", line) + self._resolve("kota.ota_unregistered", "registered") + elif ota == "unregistered" and wifi == "connected" and mode != "provisioning": + self._raise("kota.ota_unregistered", line) + + def _hit_error_id(self, line: LogLine, info: kota.ErrorId | None) -> None: + eid = line.error_id or "?" + msg = (line.message or "").strip() + msg = msg if len(msg) <= 90 else msg[:87] + "..." + if info: + sev = Severity.WARNING if info.severity == "warning" else Severity.ERROR + f = self._custom(f"eid.{eid}", sev, f"{info.module_name}: {msg}", + f"The firmware reported error {eid} ({info.module_name}).", + (info.action, "If it repeats, save the session log for the firmware team.")) + f.rule_id = f"eid.{info.module}" + f.error_ids[eid] = info.action + else: + f = self._custom(f"eid.{eid}", Severity.WARNING, f"Error {eid}: {msg}", + "This error ID is not in the tool's error table (newer firmware?).", + ("Save the session log and send it to the firmware team.",)) + self._touch(f, line) + + def _box_summary(self) -> list[tuple[str, str, str]]: + """What the box reported about itself in this boot (KOTA lines), for the panel.""" + k = self.boot.kota + out: list[tuple[str, str, str]] = [] + if ln := k.get("boot"): + f = ln.fields + lr = f.get("last_restart", "none") + bad = kota.restart_finding(lr) is not None + out.append(("Boot", f"v{f.get('fw', '?')} build {f.get('build', '?')}, mode {f.get('mode', '?')}, " + f"reset {f.get('reset', '?')}, last restart: {lr}", "warn" if bad else "ok")) + if ps := self.boot.previous_status: + if ps[0] != "OK": + out.append(("Before restart", f"status {ps[0]}" + (f" code={ps[1]}" if ps[1] else ""), "warn")) + if ln := k.get("i2c"): + f = ln.fields + if f.get("bus") == "stuck": + out.append(("I2C", f"bus stuck (SDA={f.get('SDA')}, SCL={f.get('SCL')})", "bad")) + else: + devs = [(v.removesuffix("(missing)"), v.endswith("(missing)")) + for a, v in f.items() if a.startswith("0x") and v != "?"] + miss = [n for n, m in devs if m] + text = ("missing: " + ", ".join(miss) + "; " if miss else "") + "found: " + ( + ", ".join(n for n, m in devs if not m) or "none") + out.append(("I2C", text, "bad" if miss else "ok")) + if ln := k.get("sd"): + f = ln.fields + if f.get("mounted") == "0": + out.append(("SD card", "not mounted" + (f" ({f['err']})" if f.get("err") else ""), "bad")) + else: + keys = f.get("env_keys", "-") + ok = "INFLUX_URL" in keys and "INFLUX_TOKEN" in keys + out.append(("SD card", f"{f.get('type', '?')} {f.get('size', '?')} {f.get('fs', '?')}, " + f"{f.get('free', '?')} free, ENV keys: {keys}, buffered: {f.get('buffer_lines', '?')}", + "ok" if ok else "bad")) + rtc, verify = k.get("rtc"), k.get("rtc.verify") + if rtc or verify: + parts, state = [], "ok" + if rtc: + if "read_failed" in rtc.words: + parts.append(f"read failed ({rtc.fields.get('err', '?')})") + state = "bad" + else: + running = rtc.fields.get("running") == "1" + parts.append(("running" if running else "STOPPED") + f", time {rtc.fields.get('time', '?')}") + state = "ok" if running else "warn" + if verify: + ok = verify.fields.get("ok") == "1" + parts.append("verified ticking" if ok else "NOT keeping time") + state = state if ok else "bad" + out.append(("Clock (RTC)", "; ".join(parts), state)) + if ln := k.get("matter"): + f = ln.fields + fabrics = f.get("fabrics", "?") + out.append(("Matter", f"{'paired' if fabrics not in ('0', '?') else 'NOT paired'} (fabrics {fabrics}), " + f"pairing window {f.get('window', '?')}", "ok" if fabrics not in ("0", "?") else "warn")) + if ln := k.get("mic"): + on = ln.fields.get("enabled") == "1" + out.append(("Microphone", "on" if on else "switched OFF (switch on the back)", "ok" if on else "info")) + if ln := k.get("health"): + f = ln.fields + wifi = f.get("wifi", "?") + text = (f"up {f.get('up', '?')}, status {f.get('status', '?')}, Wi-Fi {wifi}" + + (f" ({f.get('rssi')} dBm)" if wifi == "connected" else "") + + f", time {'OK' if f.get('time_valid') == '1' else 'NOT set'}, SD {f.get('sd', '?')}, " + f"buffer {f.get('buffer', '?')}, heap {f.get('heap', '?')}, last upload {f.get('last_upload', '?')}" + + ("" if f.get("last_upload") == "never" else " ago") + f", OTA {f.get('ota', '?')}") + good = f.get("status") == "OK" and wifi == "connected" and f.get("last_upload") != "never" + out.append(("Health", text, "ok" if good else "warn")) + return out + + # ------------------------------------------------------------ reboots + def _reboot_findings(self) -> list[Finding]: + """Classify why each earlier boot ended (needs the next boot's reset reason).""" + out: list[Finding] = [] + short_unexpected: list[BootRecord] = [] + cur = self.boot + # The box keeps its status across a restart ("led previous_boot"): if + # it was in an error state right before the latest restart (often the + # technician's Reset & capture), that state is worth showing. + if cur.previous_status and cur.previous_status[0] not in ("OK", "UPDATING", "SELF_CHECK"): + sf = kota.status_finding(*cur.previous_status) + st, code = cur.previous_status + # Only informative once the box reports OK again in this boot. + sev = Severity.INFO if self.status and self.status[0] == "OK" else Severity.WARNING + f = Finding("boot.previous_status", "boot.previous_status", sev, + f"Before the last restart: {sf[2] if sf else st}", + "The box remembers its status across a restart. Right before the latest restart it was in " + f"state {st}" + (f" code={code}" if code else "") + ". " + + (sf[3] if sf else "") + " Check whether the problem comes back in this boot.", + sf[4] if sf else ()) + f.count, f.boot, f.last_seen = 1, cur.index, cur.host_time + f.evidence.append(f"led previous_boot status={st} code={code}") + out.append(f) + for prev, nxt in zip(self.boots, self.boots[1:]): + if not prev.saw_log and prev.index == 0: + continue # nothing captured before the first boot + reason = nxt.reset_reason or "unknown" + if nxt.tool_reset or prev.intentional_restart: + continue + # Firmware 1.0.16+ records why it restarted itself. + last = nxt.last_restart or prev.restart_reason + if last in kota.INTENTIONAL_RESTARTS: + continue + key, sev, title, meaning, actions, expl = None, Severity.INFO, "", "", (), () + rf = kota.restart_finding(last) if last else None + if prev.brownout or "BROWN" in reason or last == "brownout": + # A crash right after a brown-out is its effect, not a firmware bug. + key, sev, title = "reboot.brownout", Severity.CRITICAL, "Power supply voltage drops (brown-out)" + meaning = ("The supply voltage fell too low and the chip reset itself - typically when the " + "Wi-Fi/Bluetooth radio starts. The box cannot run like this.") + actions = ("Switch the box's power switch ON - USB from a laptop alone may not be enough.", + "Check the power supply, cable and connectors; try another supply.", + "If it persists on a good supply: the board's power circuit is faulty.") + elif rf and not (last == "panic" and prev.panic_lines): + sev_name, title, meaning, actions = rf + sev = Severity[sev_name.upper()] + key = {"status_watchdog": "reboot.status_watchdog", "panic": "reboot.crash", + "brownout": "reboot.brownout", "task_wdt": "reboot.watchdog", "int_wdt": "reboot.watchdog", + "wdt": "reboot.watchdog"}.get(last, f"reboot.{last}") + if nxt.previous_status and nxt.previous_status[0] != "OK": + st, code = nxt.previous_status + meaning += f" Before the restart the box was in state {st}" + (f" code={code}" if code else "") + "." + if last == "status_watchdog": + expl = tuple(_INIT_FAILURES) + kota._SENSOR_ROOTS + ("status.error", "sys.out_of_resources") + elif last == "status_oom_watchdog": + expl = ("sys.out_of_resources",) + elif rf: + pass # panic with captured crash output: covered by sys.crash + elif "USB_UART_CHIP_RESET" in reason: + key, title = "reboot.usb", "Box was reset over USB" + meaning = "The board was reset through its USB port (a flashing tool, a serial program, or this tool in an earlier session)." + elif "POWERON" in reason: + key, title = "reboot.power", "Box was switched off and on" + meaning = "Power was interrupted (power switch, cable or supply)." + actions = ("If nobody switched it off: check the power supply and cable.",) + elif "WDT" in reason: + key, sev, title = "reboot.watchdog", Severity.ERROR, "Firmware froze and was restarted by the watchdog" + meaning = "The firmware stopped responding; the hardware watchdog restarted it." + actions = ("Save the session log for the firmware team.",) + elif prev.panic_lines: + pass # covered by sys.crash + elif (not nxt.last_restart and prev.led_at_end in ("PURPLE", "RED") + and prev.last_uptime_ms >= 140_000): # older firmware: no restart record + key, title = "reboot.status_watchdog", "Box restarted itself after 2.5 minutes of errors" + meaning = "By design the firmware restarts when it stays in an error state (purple/red) for 150 s." + actions = ("Fix the grouped cause; the restarts stop by themselves.",) + expl = tuple(_NETWORK_ROOTS) + ("sys.out_of_resources",) + else: + key, sev, title = "reboot.unexpected", Severity.WARNING, "Unexpected restart" + meaning = f"The firmware restarted (reset reason {reason})." + actions = ("Watch whether it repeats; save the session log for the firmware team.",) + if key: + out.append(self._reboot_finding(key, sev, title, meaning, actions, expl, nxt)) + if prev.last_uptime_ms < _SHORT_BOOT_MS and "POWERON" not in reason: + short_unexpected.append(prev) + if len(short_unexpected) >= 2: + avg = sum(b.last_uptime_ms for b in short_unexpected) / len(short_unexpected) / 1000 + f = self._reboot_finding( + "reboot.loop", Severity.CRITICAL, "Reboot loop", + f"The box restarted {len(short_unexpected)} times, about every {avg:.0f} s. " + "The error printed just before each restart is the cause.", + ("Fix the grouped cause below.",), tuple(_INIT_FAILURES) + ("reboot.brownout",), self.boot) + f.count = len(short_unexpected) + out.append(f) + # One finding per cause, counting how often it happened. + merged: dict[str, Finding] = {} + for f in out: + if f.key in merged: + merged[f.key].count += f.count + merged[f.key].evidence.extend(f.evidence) + else: + merged[f.key] = f + return list(merged.values()) + + def _reboot_finding(self, key, sev, title, meaning, actions, expl, boot: BootRecord) -> Finding: + f = Finding(key, key, sev, title, meaning, actions, expl) + f.count, f.boot, f.last_seen = 1, self.boot.index, boot.host_time + if boot.reset_reason: + f.evidence.append(f"reset reason: {boot.reset_reason}") + return f + + +def _group(findings: list[Finding]) -> list[RootCause]: + """Attach each finding to the root of its explained_by chain.""" + by_rule: dict[str, list[Finding]] = {} + for f in findings: + by_rule.setdefault(f.rule_id, []).append(f) + + def parent(f: Finding) -> Finding | None: + for rid in f.explained_by: + if by_rule.get(rid): + return by_rule[rid][0] + return None + + def root(f: Finding) -> Finding: + seen = {f.key} + while (p := parent(f)) is not None and p.key not in seen: + seen.add(p.key) + f = p + return f + + roots: dict[str, RootCause] = {} + for f in findings: + r = root(f) + rc = roots.setdefault(r.key, RootCause(r, [])) + if r is not f: + rc.consequences.append(f) + for rc in roots.values(): + rc.consequences.sort(key=lambda c: (-c.severity, c.first_uptime_ms or 0)) + return list(roots.values()) diff --git a/kota_tech/diag/firmware.py b/kota_tech/diag/firmware.py index 88aad52..420cd6c 100644 --- a/kota_tech/diag/firmware.py +++ b/kota_tech/diag/firmware.py @@ -1,147 +1,147 @@ -"""Firmware identification from the boot log. - -Up to firmware 1.0 the app version string was "1.0" in every build, so -builds are told apart by the ELF SHA256 prefix printed by app_init. Boxes -receive new firmware over OTA; an unknown SHA usually means a box has already -updated. - -Firmware 1.0.16+ prints "KOTA: boot fw=1.0.16 build=4f10989e ..." (version -unique per build, first 8 hex digits of the ELF SHA256). Builds found in the -tool's firmware/ folder are registered with `register_local_build` so the -box's build can be shown with its compile date. -""" - -from __future__ import annotations - -import re -from dataclasses import dataclass, field - - -@dataclass(frozen=True) -class KnownFirmware: - name: str - source: str - sd_wifi: bool # reads SSID/PASSWORD from ENV.TXT - setup_portal: bool # short press -> Wi-Fi setup portal - - -KNOWN: dict[str, KnownFirmware] = { - "45fa9d5b9": KnownFirmware( - name="v1.0 (built Jun 29 2026)", - source="factory image new_system.bin", - sd_wifi=False, - setup_portal=False, - ), - "c94ba0e9b": KnownFirmware( - name="v1.0 source build (Sep 26 2026)", - source="developer build, built by Krzysztof Kallas", - sd_wifi=True, - setup_portal=True, - ), -} - -# elf sha (full or prefix) -> (version, compile time, file name) of builds in firmware/ -LOCAL_BUILDS: dict[str, tuple[str, str, str]] = {} - - -def register_local_build(elf_sha: str, version: str, built: str, file_name: str) -> None: - LOCAL_BUILDS[elf_sha.lower()] = (version, built, file_name) - - -def same_build(a: str | None, b: str | None) -> bool: - """ELF SHA prefixes of different lengths (8 from KOTA: boot, 9+ from - app_init, 64 from an image) name the same build if they agree.""" - if not a or not b: - return False - n = min(len(a), len(b)) - return n >= 6 and a[:n].lower() == b[:n].lower() - - -def local_build(elf_sha: str | None) -> tuple[str, str, str] | None: - return next((v for sha, v in LOCAL_BUILDS.items() if same_build(sha, elf_sha)), None) - - -_ELF = re.compile(r"app_init: ELF file SHA256:\s+(?P[0-9a-f]{6,})") -_VER = re.compile(r"app_init: App version:\s+(?P\S+)") -_BUILT = re.compile(r"app_init: Compile time:\s+(?P.+)$") -_IDF = re.compile(r"app_init: ESP-IDF:\s+(?P\S+)") -# Evidence of features, for builds not in KNOWN. -_FEATURE_SD_WIFI = re.compile(r"WIFI_CREDENTIALS: ") -_FEATURE_PORTAL = re.compile(r"WIFI_PROVISIONING: |Short press: ") -# Deployed v1.0 logs the SD mount under the INFLUX_BUFFER tag. -_LEGACY_SD_TAG = re.compile(r"INFLUX_BUFFER: SD card mounted") - - -@dataclass -class FirmwareInfo: - elf_sha: str | None = None - version: str | None = None - built: str | None = None - idf: str | None = None - hw: str | None = None - evidence: set[str] = field(default_factory=set) - - def observe_kota(self, fields: dict[str, str]) -> None: - """'KOTA: boot fw=1.0.16 build=4f10989e hw=2 ...'""" - if fields.get("fw"): - self.version = fields["fw"] - build = fields.get("build", "") - # app_init's longer prefix is kept if it agrees. - if build and not same_build(self.elf_sha, build): - self.elf_sha = build.lower() - if fields.get("hw"): - self.hw = fields["hw"] - self.evidence.add("kota") # 1.0.16+: SD Wi-Fi and setup portal exist - - def observe(self, text: str) -> None: - if m := _ELF.search(text): - self.elf_sha = m.group("sha") - elif m := _VER.search(text): - self.version = m.group("v") - elif m := _BUILT.search(text): - self.built = m.group("t").strip() - elif m := _IDF.search(text): - self.idf = m.group("v") - if _FEATURE_SD_WIFI.search(text): - self.evidence.add("sd_wifi") - if _FEATURE_PORTAL.search(text): - self.evidence.add("setup_portal") - if _LEGACY_SD_TAG.search(text): - self.evidence.add("legacy_v1") - - @property - def known(self) -> KnownFirmware | None: - if not self.elf_sha: - return None - return next((k for sha, k in KNOWN.items() if same_build(self.elf_sha, sha)), None) - - @property - def supports_sd_wifi(self) -> bool | None: - """True/False when determinable, None when unknown (no boot seen).""" - if k := self.known: - return k.sd_wifi - if "sd_wifi" in self.evidence or "kota" in self.evidence: - return True - if "legacy_v1" in self.evidence: - return False - return None - - def describe(self) -> str: - if k := self.known: - return f"{k.name} - {k.source}" - if self.elf_sha and (self.version not in (None, "1.0") or local_build(self.elf_sha)): - local = local_build(self.elf_sha) - built = self.built or (local[1] if local else None) - text = f"v{self.version or (local[0] if local else '?')} (build {self.elf_sha[:8]}" - text += f", compiled {built})" if built else ")" - if self.hw: - text += f", hardware {self.hw}" - if local: - text += f" - same as firmware/{local[2]}" - return text - if self.elf_sha: - built = f", built {self.built}" if self.built else "" - return f"Unknown build {self.elf_sha[:9]}{built} (probably updated over OTA)" - if "legacy_v1" in self.evidence: - return "Probably v1.0 factory image (press Reset & capture to confirm)" - return "Not identified yet - press Reset & capture" +"""Firmware identification from the boot log. + +Up to firmware 1.0 the app version string was "1.0" in every build, so +builds are told apart by the ELF SHA256 prefix printed by app_init. Boxes +receive new firmware over OTA; an unknown SHA usually means a box has already +updated. + +Firmware 1.0.16+ prints "KOTA: boot fw=1.0.16 build=4f10989e ..." (version +unique per build, first 8 hex digits of the ELF SHA256). Builds found in the +tool's firmware/ folder are registered with `register_local_build` so the +box's build can be shown with its compile date. +""" + +from __future__ import annotations + +import re +from dataclasses import dataclass, field + + +@dataclass(frozen=True) +class KnownFirmware: + name: str + source: str + sd_wifi: bool # reads SSID/PASSWORD from ENV.TXT + setup_portal: bool # short press -> Wi-Fi setup portal + + +KNOWN: dict[str, KnownFirmware] = { + "45fa9d5b9": KnownFirmware( + name="v1.0 (built Jun 29 2026)", + source="factory image new_system.bin", + sd_wifi=False, + setup_portal=False, + ), + "c94ba0e9b": KnownFirmware( + name="v1.0 source build (Sep 26 2026)", + source="developer build, built by Krzysztof Kallas", + sd_wifi=True, + setup_portal=True, + ), +} + +# elf sha (full or prefix) -> (version, compile time, file name) of builds in firmware/ +LOCAL_BUILDS: dict[str, tuple[str, str, str]] = {} + + +def register_local_build(elf_sha: str, version: str, built: str, file_name: str) -> None: + LOCAL_BUILDS[elf_sha.lower()] = (version, built, file_name) + + +def same_build(a: str | None, b: str | None) -> bool: + """ELF SHA prefixes of different lengths (8 from KOTA: boot, 9+ from + app_init, 64 from an image) name the same build if they agree.""" + if not a or not b: + return False + n = min(len(a), len(b)) + return n >= 6 and a[:n].lower() == b[:n].lower() + + +def local_build(elf_sha: str | None) -> tuple[str, str, str] | None: + return next((v for sha, v in LOCAL_BUILDS.items() if same_build(sha, elf_sha)), None) + + +_ELF = re.compile(r"app_init: ELF file SHA256:\s+(?P[0-9a-f]{6,})") +_VER = re.compile(r"app_init: App version:\s+(?P\S+)") +_BUILT = re.compile(r"app_init: Compile time:\s+(?P.+)$") +_IDF = re.compile(r"app_init: ESP-IDF:\s+(?P\S+)") +# Evidence of features, for builds not in KNOWN. +_FEATURE_SD_WIFI = re.compile(r"WIFI_CREDENTIALS: ") +_FEATURE_PORTAL = re.compile(r"WIFI_PROVISIONING: |Short press: ") +# Deployed v1.0 logs the SD mount under the INFLUX_BUFFER tag. +_LEGACY_SD_TAG = re.compile(r"INFLUX_BUFFER: SD card mounted") + + +@dataclass +class FirmwareInfo: + elf_sha: str | None = None + version: str | None = None + built: str | None = None + idf: str | None = None + hw: str | None = None + evidence: set[str] = field(default_factory=set) + + def observe_kota(self, fields: dict[str, str]) -> None: + """'KOTA: boot fw=1.0.16 build=4f10989e hw=2 ...'""" + if fields.get("fw"): + self.version = fields["fw"] + build = fields.get("build", "") + # app_init's longer prefix is kept if it agrees. + if build and not same_build(self.elf_sha, build): + self.elf_sha = build.lower() + if fields.get("hw"): + self.hw = fields["hw"] + self.evidence.add("kota") # 1.0.16+: SD Wi-Fi and setup portal exist + + def observe(self, text: str) -> None: + if m := _ELF.search(text): + self.elf_sha = m.group("sha") + elif m := _VER.search(text): + self.version = m.group("v") + elif m := _BUILT.search(text): + self.built = m.group("t").strip() + elif m := _IDF.search(text): + self.idf = m.group("v") + if _FEATURE_SD_WIFI.search(text): + self.evidence.add("sd_wifi") + if _FEATURE_PORTAL.search(text): + self.evidence.add("setup_portal") + if _LEGACY_SD_TAG.search(text): + self.evidence.add("legacy_v1") + + @property + def known(self) -> KnownFirmware | None: + if not self.elf_sha: + return None + return next((k for sha, k in KNOWN.items() if same_build(self.elf_sha, sha)), None) + + @property + def supports_sd_wifi(self) -> bool | None: + """True/False when determinable, None when unknown (no boot seen).""" + if k := self.known: + return k.sd_wifi + if "sd_wifi" in self.evidence or "kota" in self.evidence: + return True + if "legacy_v1" in self.evidence: + return False + return None + + def describe(self) -> str: + if k := self.known: + return f"{k.name} - {k.source}" + if self.elf_sha and (self.version not in (None, "1.0") or local_build(self.elf_sha)): + local = local_build(self.elf_sha) + built = self.built or (local[1] if local else None) + text = f"v{self.version or (local[0] if local else '?')} (build {self.elf_sha[:8]}" + text += f", compiled {built})" if built else ")" + if self.hw: + text += f", hardware {self.hw}" + if local: + text += f" - same as firmware/{local[2]}" + return text + if self.elf_sha: + built = f", built {self.built}" if self.built else "" + return f"Unknown build {self.elf_sha[:9]}{built} (probably updated over OTA)" + if "legacy_v1" in self.evidence: + return "Probably v1.0 factory image (press Reset & capture to confirm)" + return "Not identified yet - press Reset & capture" diff --git a/kota_tech/diag/guides.py b/kota_tech/diag/guides.py index cad6b4b..6ae4839 100644 --- a/kota_tech/diag/guides.py +++ b/kota_tech/diag/guides.py @@ -1,401 +1,401 @@ -"""Guided troubleshooting: executes the decision trees in guides.toml. - -GUI-independent. The runner is fed the same LogLines as the diagnostics -engine, reads the engine's report to route and to skip questions the log -already answers, asks the host to perform tool actions (board reset), and -records every answer and conclusion in the engine so they appear in the -report and the session log. -""" - -from __future__ import annotations - -import re -import time -import tomllib -from collections.abc import Callable -from dataclasses import dataclass, field -from pathlib import Path - -from ..core.logline import LogLine -from .engine import LED, DiagnosticsEngine, Severity - -GUIDES_PATH = Path(__file__).with_name("guides.toml") -# LED answers a technician can give (engine colours + a reboot-loop pattern). -LED_ANSWERS = ["GREEN", "PURPLE", "RED", "BLUE", "WHITE", "YELLOW", "ORANGE", "CYAN", "OFF", "CYCLING"] -LED_ANSWER_LABELS = {**{k: v[0] for k, v in LED.items()}, - "CYCLING": "Keeps changing / goes dark every few seconds"} -STEP_TYPES = {"led", "question", "info", "wait", "route"} -END_SEVERITY = {"ok": None, "info": Severity.INFO, "warning": Severity.WARNING, - "error": Severity.ERROR, "critical": Severity.CRITICAL} - - -@dataclass(frozen=True) -class Option: - label: str - next: str - - -@dataclass(frozen=True) -class Step: - id: str - type: str - text: str = "" - options: tuple[Option, ...] = () - route: dict[str, str] = field(default_factory=dict) # led colour -> target - routes: dict[str, str] = field(default_factory=dict) # rule id -> target - default: str | None = None - next: str | None = None - tool_action: str | None = None - until: re.Pattern[str] | None = None - until_milestone: str | None = None - fail_on: re.Pattern[str] | None = None - timeout_s: float = 60.0 - pass_on_timeout: bool = False - on_ok: str | None = None - on_fail: str | None = None - on_timeout: str | None = None - skip_if_finding: str | None = None - skip_unless_feature: str | None = None - skip_to: str | None = None - show_qr: bool = False # show the Matter pairing QR next to the step text - - def targets(self) -> list[str]: - t = [o.next for o in self.options] + list(self.route.values()) + list(self.routes.values()) - t += [x for x in (self.default, self.next, self.on_ok, self.on_fail, self.on_timeout, self.skip_to) if x] - return t - - -@dataclass(frozen=True) -class End: - id: str - severity: str - title: str - text: str - actions: tuple[str, ...] = () - - -@dataclass(frozen=True) -class Guide: - id: str - title: str - summary: str - recommend: tuple[str, ...] - start: str - steps: dict[str, Step] - ends: dict[str, End] - - -def load_guides(path: Path = GUIDES_PATH) -> dict[str, Guide]: - data = tomllib.loads(path.read_text(encoding="utf-8")) - guides: dict[str, Guide] = {} - for g in data["guide"]: - steps = {} - for s in g.get("step", []): - steps[s["id"]] = Step( - id=s["id"], type=s["type"], text=s.get("text", ""), - options=tuple(Option(o["label"], o["next"]) for o in s.get("options", ())), - route=dict(s.get("route", {})), routes=dict(s.get("routes", {})), - default=s.get("default"), next=s.get("next"), tool_action=s.get("tool_action"), - until=re.compile(s["until"]) if "until" in s else None, - until_milestone=s.get("until_milestone"), - fail_on=re.compile(s["fail_on"]) if "fail_on" in s else None, - timeout_s=float(s.get("timeout_s", 60)), pass_on_timeout=bool(s.get("pass_on_timeout", False)), - on_ok=s.get("on_ok"), on_fail=s.get("on_fail"), on_timeout=s.get("on_timeout"), - skip_if_finding=s.get("skip_if_finding"), skip_unless_feature=s.get("skip_unless_feature"), - skip_to=s.get("skip_to"), show_qr=bool(s.get("show_qr", False)), - ) - ends = {e["id"]: End(e["id"], e["severity"], e["title"], e["text"], tuple(e.get("actions", ()))) - for e in g.get("end", [])} - guides[g["id"]] = Guide(g["id"], g["title"], g["summary"], tuple(g.get("recommend", ())), - g["start"], steps, ends) - _validate(guides) - return guides - - -def _validate(guides: dict[str, Guide]) -> None: - for g in guides.values(): - if g.start not in g.steps: - raise ValueError(f"guide {g.id}: start step {g.start!r} missing") - for s in g.steps.values(): - if s.type not in STEP_TYPES: - raise ValueError(f"{g.id}.{s.id}: unknown step type {s.type!r}") - if s.type == "led" and set(s.route) - set(LED_ANSWERS): - raise ValueError(f"{g.id}.{s.id}: unknown LED colours {set(s.route) - set(LED_ANSWERS)}") - if s.type == "wait" and not (s.until or s.until_milestone or s.pass_on_timeout): - raise ValueError(f"{g.id}.{s.id}: wait step needs until/until_milestone/pass_on_timeout") - if s.tool_action not in (None, "reset"): - raise ValueError(f"{g.id}.{s.id}: unknown tool_action {s.tool_action!r}") - for t in s.targets(): - kind, _, ref = t.partition(":") - if kind == "end" and ref not in g.ends: - raise ValueError(f"{g.id}.{s.id}: unknown end {ref!r}") - elif kind == "guide" and ref not in guides: - raise ValueError(f"{g.id}.{s.id}: unknown guide {ref!r}") - elif not ref and t not in g.steps: - raise ValueError(f"{g.id}.{s.id}: unknown step {t!r}") - for e in g.ends.values(): - if e.severity not in END_SEVERITY: - raise ValueError(f"{g.id}.end.{e.id}: unknown severity {e.severity!r}") - - -def recommended(guides: dict[str, Guide], engine: DiagnosticsEngine) -> list[Guide]: - """Guides matching the current root findings (in root order), then the LED.""" - report = engine.report() - keys: list[str] = [] - for rc in report.roots: - keys.append(rc.finding.rule_id) - keys += [c.rule_id for c in rc.consequences] - if report.led not in ("UNKNOWN",): - keys.append(f"led:{report.led}") - out: list[Guide] = [g for g in guides.values() if "always" in g.recommend] - for k in keys: - for g in guides.values(): - if k in g.recommend and g not in out: - out.append(g) - return out - - -@dataclass -class View: - """What the UI should show right now.""" - guide: Guide | None = None - step: Step | None = None - text: str = "" - options: list[tuple[str, str]] = field(default_factory=list) # (key, label) - waiting: bool = False - remaining_s: float = 0.0 - note: str = "" - end: End | None = None - can_back: bool = False - - -class GuideRunner: - def __init__(self, engine: DiagnosticsEngine, guides: dict[str, Guide] | None = None, - on_action: Callable[[str], bool] | None = None, - on_log: Callable[[str], None] | None = None, - clock: Callable[[], float] = time.monotonic): - self.engine = engine - self.guides = guides if guides is not None else load_guides() - self._on_action = on_action or (lambda _a: False) - self._on_log = on_log or (lambda _t: None) - self._clock = clock - self.cancel() - - # ------------------------------------------------------------ control - def cancel(self) -> None: - self.guide: Guide | None = None - self.step: Step | None = None - self.end: End | None = None - self.note = "" - self._stack: list[tuple[str, str]] = [] # (guide id, step id) for Back - self._wait_started = 0.0 - self._wait_boot = 0 - self._wait_milestones: set[str] = set() - - def start(self, guide_id: str) -> None: - self.cancel() - self._enter_guide(guide_id) - - def answer(self, key: str) -> None: - """Pick an option: LED colour name for led steps, option index otherwise.""" - s = self.step - if s is None: - return - if s.type == "led": - label = LED_ANSWER_LABELS.get(key, key) - self._record(s, label) - self._check_led(key) - target = s.route.get(key) - elif s.type == "question": - opt = s.options[int(key)] - self._record(s, opt.label) - target = opt.next - else: - return - if target: - self._push() - self._go(target) - - def proceed(self) -> None: - """Continue on info steps; 'skip waiting' on wait steps.""" - s = self.step - if s is None: - return - if s.type == "info" and s.next: - self._push() - self._go(s.next) - elif s.type == "wait": - self._record(s, "technician skipped the wait") - self._finish_wait("timeout") - - def back(self) -> None: - # Never step back into a wait: re-entering it could restart the box. - while self._stack: - gid, sid = self._stack.pop() - if self.guides[gid].steps[sid].type != "wait": - self.guide, self.end, self.note = self.guides[gid], None, "" - self._enter_step(sid) - return - - # --------------------------------------------------------------- feed - def feed(self, line: LogLine) -> None: - s = self.step - if s is None or s.type != "wait": - return - if s.fail_on and s.fail_on.search(line.text): - self._record(s, f"log: {line.text}") - self._finish_wait("fail") - elif s.until and s.until.search(line.text): - self._record(s, f"log: {line.text}") - self._finish_wait("ok") - elif s.until_milestone and self._milestone_new(s.until_milestone): - self._record(s, f"milestone reached: {s.until_milestone}") - self._finish_wait("ok") - - def tick(self) -> None: - s = self.step - if s is not None and s.type == "wait" and self._clock() - self._wait_started >= s.timeout_s: - self._finish_wait("ok" if s.pass_on_timeout else "timeout") - - # --------------------------------------------------------------- view - def view(self) -> View: - v = View(guide=self.guide, step=self.step, end=self.end, note=self.note, can_back=bool(self._stack)) - s = self.step - if s is None: - return v - v.text = self._render(s.text) - if s.type == "led": - v.options = [(k, LED_ANSWER_LABELS[k]) for k in LED_ANSWERS if k in s.route] - elif s.type == "question": - v.options = [(str(i), o.label) for i, o in enumerate(s.options)] - elif s.type == "wait": - v.waiting = True - v.remaining_s = max(0.0, s.timeout_s - (self._clock() - self._wait_started)) - return v - - # ---------------------------------------------------------- internals - def _push(self) -> None: - if self.guide and self.step: - self._stack.append((self.guide.id, self.step.id)) - - def _enter_guide(self, guide_id: str) -> None: - self.guide = self.guides[guide_id] - self._on_log(f"technician: started guide '{self.guide.title}'") - self._enter_step(self.guide.start) - - def _go(self, target: str) -> None: - kind, _, ref = target.partition(":") - if kind == "end" and ref: - self._conclude(self.guide.ends[ref]) - elif kind == "guide" and ref: - self._enter_guide(ref) - else: - self._enter_step(target) - - def _enter_step(self, step_id: str) -> None: - s = self.guide.steps[step_id] - self.step, self.note = s, "" - if s.skip_if_finding and self._finding_active(s.skip_if_finding): - self._auto(s, f"skipped - log already shows {s.skip_if_finding}", s.skip_to) - return - if s.skip_unless_feature and not self._feature(s.skip_unless_feature): - self._auto(s, f"skipped - firmware has no {s.skip_unless_feature}", s.skip_to) - return - if s.type == "route": - target = self._route(s) - self._auto(s, f"routed by log findings -> {target}", target) - return - if s.type == "wait": - self._wait_started = self._clock() - self._wait_boot = self.engine.boot.index - self._wait_milestones = set(self.engine.milestones) - if s.tool_action and not self._on_action(s.tool_action): - self.note = "Could not restart the box from the tool - connect to it first, or switch it off and on." - # A milestone that is already reached satisfies the wait at once - # unless the step first restarts the box. - if s.until_milestone and not s.tool_action and s.until_milestone in self.engine.milestones: - self._record(s, f"already reached: {s.until_milestone}") - self._finish_wait("ok") - - def _auto(self, s: Step, why: str, target: str | None) -> None: - self._on_log(f"technician guide: {s.id}: {why}") - if target: - self._go(target) - - def _finish_wait(self, outcome: str) -> None: - s = self.step - target = {"ok": s.on_ok, "fail": s.on_fail or s.on_timeout, "timeout": s.on_timeout or s.on_fail}[outcome] - if target is None: - return - self._push() - self._go(target) - - def _milestone_new(self, mid: str) -> bool: - if mid not in self.engine.milestones: - return False - return self.engine.boot.index != self._wait_boot or mid not in self._wait_milestones - - def _route(self, s: Step) -> str: - report = self.engine.report() - for rc in report.roots: - for f in (rc.finding, *rc.consequences): - if f.rule_id in s.routes: - return s.routes[f.rule_id] - return s.default or "end:" + next(iter(self.guide.ends)) - - def _check_led(self, seen: str) -> None: - expected = self.engine.led - if expected in ("UNKNOWN", "OFF") or seen == "CYCLING" or seen == expected: - return - exp_label, see_label = LED[expected][0], LED_ANSWER_LABELS[seen] - self.engine.add_confirmed( - "tech.led_mismatch", Severity.WARNING, - f"LED shows {see_label}, but the log implies {exp_label}", - f"The firmware state in the log corresponds to {exp_label}, while the technician sees {see_label}. " - "Either the LED just changed (check again in a few seconds), the LED/its wiring is faulty, " - "or the box runs different firmware. Trust the log findings.", - ("Look at the LED again for 10 seconds.", "If it stays different: note it for repair (LED board).")) - - def _conclude(self, end: End) -> None: - self.step, self.end = None, end - self._on_log(f"technician guide result: {end.title}") - sev = END_SEVERITY[end.severity] - self.engine.note_technician(f"{self.guide.title}: {end.title}") - if sev is not None: - self.engine.add_confirmed(f"tech.{self.guide.id}.{end.id}", sev, end.title, end.text, end.actions) - - def _record(self, s: Step, answer: str) -> None: - q = self._render(s.text) - self.engine.note_technician(f"{q} -> {answer}") - self._on_log(f"technician: {q[:80]} -> {answer}") - - def _finding_active(self, rule_id: str) -> bool: - return any(f.rule_id == rule_id and f.active for f in self.engine.findings.values()) - - def _feature(self, name: str) -> bool: - fw = self.engine.firmware - if name == "sd_wifi": - return fw.supports_sd_wifi is not False - if name == "setup_portal": - if fw.known is not None: - return fw.known.setup_portal - if "setup_portal" in fw.evidence: - return True - return "legacy_v1" not in fw.evidence # unknown firmware: ask - return False - - def _render(self, text: str) -> str: - report = self.engine.report() - sensors = sorted({f.title.removesuffix(" failed to start") for f in self.engine.findings.values() - if f.rule_id == "sensor.init_failed" and f.active}) - ctx = { - "expected_led": LED[report.led][0], - "failing_sensors": ", ".join(sensors) or "see the Diagnosis tab", - "top_finding": report.roots[0].finding.title if report.roots else "none", - } - return text.format_map(_Fmt(ctx)) - - -class _Fmt(dict): - def __missing__(self, key: str) -> str: - return "{" + key + "}" +"""Guided troubleshooting: executes the decision trees in guides.toml. + +GUI-independent. The runner is fed the same LogLines as the diagnostics +engine, reads the engine's report to route and to skip questions the log +already answers, asks the host to perform tool actions (board reset), and +records every answer and conclusion in the engine so they appear in the +report and the session log. +""" + +from __future__ import annotations + +import re +import time +import tomllib +from collections.abc import Callable +from dataclasses import dataclass, field +from pathlib import Path + +from ..core.logline import LogLine +from .engine import LED, DiagnosticsEngine, Severity + +GUIDES_PATH = Path(__file__).with_name("guides.toml") +# LED answers a technician can give (engine colours + a reboot-loop pattern). +LED_ANSWERS = ["GREEN", "PURPLE", "RED", "BLUE", "WHITE", "YELLOW", "ORANGE", "CYAN", "OFF", "CYCLING"] +LED_ANSWER_LABELS = {**{k: v[0] for k, v in LED.items()}, + "CYCLING": "Keeps changing / goes dark every few seconds"} +STEP_TYPES = {"led", "question", "info", "wait", "route"} +END_SEVERITY = {"ok": None, "info": Severity.INFO, "warning": Severity.WARNING, + "error": Severity.ERROR, "critical": Severity.CRITICAL} + + +@dataclass(frozen=True) +class Option: + label: str + next: str + + +@dataclass(frozen=True) +class Step: + id: str + type: str + text: str = "" + options: tuple[Option, ...] = () + route: dict[str, str] = field(default_factory=dict) # led colour -> target + routes: dict[str, str] = field(default_factory=dict) # rule id -> target + default: str | None = None + next: str | None = None + tool_action: str | None = None + until: re.Pattern[str] | None = None + until_milestone: str | None = None + fail_on: re.Pattern[str] | None = None + timeout_s: float = 60.0 + pass_on_timeout: bool = False + on_ok: str | None = None + on_fail: str | None = None + on_timeout: str | None = None + skip_if_finding: str | None = None + skip_unless_feature: str | None = None + skip_to: str | None = None + show_qr: bool = False # show the Matter pairing QR next to the step text + + def targets(self) -> list[str]: + t = [o.next for o in self.options] + list(self.route.values()) + list(self.routes.values()) + t += [x for x in (self.default, self.next, self.on_ok, self.on_fail, self.on_timeout, self.skip_to) if x] + return t + + +@dataclass(frozen=True) +class End: + id: str + severity: str + title: str + text: str + actions: tuple[str, ...] = () + + +@dataclass(frozen=True) +class Guide: + id: str + title: str + summary: str + recommend: tuple[str, ...] + start: str + steps: dict[str, Step] + ends: dict[str, End] + + +def load_guides(path: Path = GUIDES_PATH) -> dict[str, Guide]: + data = tomllib.loads(path.read_text(encoding="utf-8")) + guides: dict[str, Guide] = {} + for g in data["guide"]: + steps = {} + for s in g.get("step", []): + steps[s["id"]] = Step( + id=s["id"], type=s["type"], text=s.get("text", ""), + options=tuple(Option(o["label"], o["next"]) for o in s.get("options", ())), + route=dict(s.get("route", {})), routes=dict(s.get("routes", {})), + default=s.get("default"), next=s.get("next"), tool_action=s.get("tool_action"), + until=re.compile(s["until"]) if "until" in s else None, + until_milestone=s.get("until_milestone"), + fail_on=re.compile(s["fail_on"]) if "fail_on" in s else None, + timeout_s=float(s.get("timeout_s", 60)), pass_on_timeout=bool(s.get("pass_on_timeout", False)), + on_ok=s.get("on_ok"), on_fail=s.get("on_fail"), on_timeout=s.get("on_timeout"), + skip_if_finding=s.get("skip_if_finding"), skip_unless_feature=s.get("skip_unless_feature"), + skip_to=s.get("skip_to"), show_qr=bool(s.get("show_qr", False)), + ) + ends = {e["id"]: End(e["id"], e["severity"], e["title"], e["text"], tuple(e.get("actions", ()))) + for e in g.get("end", [])} + guides[g["id"]] = Guide(g["id"], g["title"], g["summary"], tuple(g.get("recommend", ())), + g["start"], steps, ends) + _validate(guides) + return guides + + +def _validate(guides: dict[str, Guide]) -> None: + for g in guides.values(): + if g.start not in g.steps: + raise ValueError(f"guide {g.id}: start step {g.start!r} missing") + for s in g.steps.values(): + if s.type not in STEP_TYPES: + raise ValueError(f"{g.id}.{s.id}: unknown step type {s.type!r}") + if s.type == "led" and set(s.route) - set(LED_ANSWERS): + raise ValueError(f"{g.id}.{s.id}: unknown LED colours {set(s.route) - set(LED_ANSWERS)}") + if s.type == "wait" and not (s.until or s.until_milestone or s.pass_on_timeout): + raise ValueError(f"{g.id}.{s.id}: wait step needs until/until_milestone/pass_on_timeout") + if s.tool_action not in (None, "reset"): + raise ValueError(f"{g.id}.{s.id}: unknown tool_action {s.tool_action!r}") + for t in s.targets(): + kind, _, ref = t.partition(":") + if kind == "end" and ref not in g.ends: + raise ValueError(f"{g.id}.{s.id}: unknown end {ref!r}") + elif kind == "guide" and ref not in guides: + raise ValueError(f"{g.id}.{s.id}: unknown guide {ref!r}") + elif not ref and t not in g.steps: + raise ValueError(f"{g.id}.{s.id}: unknown step {t!r}") + for e in g.ends.values(): + if e.severity not in END_SEVERITY: + raise ValueError(f"{g.id}.end.{e.id}: unknown severity {e.severity!r}") + + +def recommended(guides: dict[str, Guide], engine: DiagnosticsEngine) -> list[Guide]: + """Guides matching the current root findings (in root order), then the LED.""" + report = engine.report() + keys: list[str] = [] + for rc in report.roots: + keys.append(rc.finding.rule_id) + keys += [c.rule_id for c in rc.consequences] + if report.led not in ("UNKNOWN",): + keys.append(f"led:{report.led}") + out: list[Guide] = [g for g in guides.values() if "always" in g.recommend] + for k in keys: + for g in guides.values(): + if k in g.recommend and g not in out: + out.append(g) + return out + + +@dataclass +class View: + """What the UI should show right now.""" + guide: Guide | None = None + step: Step | None = None + text: str = "" + options: list[tuple[str, str]] = field(default_factory=list) # (key, label) + waiting: bool = False + remaining_s: float = 0.0 + note: str = "" + end: End | None = None + can_back: bool = False + + +class GuideRunner: + def __init__(self, engine: DiagnosticsEngine, guides: dict[str, Guide] | None = None, + on_action: Callable[[str], bool] | None = None, + on_log: Callable[[str], None] | None = None, + clock: Callable[[], float] = time.monotonic): + self.engine = engine + self.guides = guides if guides is not None else load_guides() + self._on_action = on_action or (lambda _a: False) + self._on_log = on_log or (lambda _t: None) + self._clock = clock + self.cancel() + + # ------------------------------------------------------------ control + def cancel(self) -> None: + self.guide: Guide | None = None + self.step: Step | None = None + self.end: End | None = None + self.note = "" + self._stack: list[tuple[str, str]] = [] # (guide id, step id) for Back + self._wait_started = 0.0 + self._wait_boot = 0 + self._wait_milestones: set[str] = set() + + def start(self, guide_id: str) -> None: + self.cancel() + self._enter_guide(guide_id) + + def answer(self, key: str) -> None: + """Pick an option: LED colour name for led steps, option index otherwise.""" + s = self.step + if s is None: + return + if s.type == "led": + label = LED_ANSWER_LABELS.get(key, key) + self._record(s, label) + self._check_led(key) + target = s.route.get(key) + elif s.type == "question": + opt = s.options[int(key)] + self._record(s, opt.label) + target = opt.next + else: + return + if target: + self._push() + self._go(target) + + def proceed(self) -> None: + """Continue on info steps; 'skip waiting' on wait steps.""" + s = self.step + if s is None: + return + if s.type == "info" and s.next: + self._push() + self._go(s.next) + elif s.type == "wait": + self._record(s, "technician skipped the wait") + self._finish_wait("timeout") + + def back(self) -> None: + # Never step back into a wait: re-entering it could restart the box. + while self._stack: + gid, sid = self._stack.pop() + if self.guides[gid].steps[sid].type != "wait": + self.guide, self.end, self.note = self.guides[gid], None, "" + self._enter_step(sid) + return + + # --------------------------------------------------------------- feed + def feed(self, line: LogLine) -> None: + s = self.step + if s is None or s.type != "wait": + return + if s.fail_on and s.fail_on.search(line.text): + self._record(s, f"log: {line.text}") + self._finish_wait("fail") + elif s.until and s.until.search(line.text): + self._record(s, f"log: {line.text}") + self._finish_wait("ok") + elif s.until_milestone and self._milestone_new(s.until_milestone): + self._record(s, f"milestone reached: {s.until_milestone}") + self._finish_wait("ok") + + def tick(self) -> None: + s = self.step + if s is not None and s.type == "wait" and self._clock() - self._wait_started >= s.timeout_s: + self._finish_wait("ok" if s.pass_on_timeout else "timeout") + + # --------------------------------------------------------------- view + def view(self) -> View: + v = View(guide=self.guide, step=self.step, end=self.end, note=self.note, can_back=bool(self._stack)) + s = self.step + if s is None: + return v + v.text = self._render(s.text) + if s.type == "led": + v.options = [(k, LED_ANSWER_LABELS[k]) for k in LED_ANSWERS if k in s.route] + elif s.type == "question": + v.options = [(str(i), o.label) for i, o in enumerate(s.options)] + elif s.type == "wait": + v.waiting = True + v.remaining_s = max(0.0, s.timeout_s - (self._clock() - self._wait_started)) + return v + + # ---------------------------------------------------------- internals + def _push(self) -> None: + if self.guide and self.step: + self._stack.append((self.guide.id, self.step.id)) + + def _enter_guide(self, guide_id: str) -> None: + self.guide = self.guides[guide_id] + self._on_log(f"technician: started guide '{self.guide.title}'") + self._enter_step(self.guide.start) + + def _go(self, target: str) -> None: + kind, _, ref = target.partition(":") + if kind == "end" and ref: + self._conclude(self.guide.ends[ref]) + elif kind == "guide" and ref: + self._enter_guide(ref) + else: + self._enter_step(target) + + def _enter_step(self, step_id: str) -> None: + s = self.guide.steps[step_id] + self.step, self.note = s, "" + if s.skip_if_finding and self._finding_active(s.skip_if_finding): + self._auto(s, f"skipped - log already shows {s.skip_if_finding}", s.skip_to) + return + if s.skip_unless_feature and not self._feature(s.skip_unless_feature): + self._auto(s, f"skipped - firmware has no {s.skip_unless_feature}", s.skip_to) + return + if s.type == "route": + target = self._route(s) + self._auto(s, f"routed by log findings -> {target}", target) + return + if s.type == "wait": + self._wait_started = self._clock() + self._wait_boot = self.engine.boot.index + self._wait_milestones = set(self.engine.milestones) + if s.tool_action and not self._on_action(s.tool_action): + self.note = "Could not restart the box from the tool - connect to it first, or switch it off and on." + # A milestone that is already reached satisfies the wait at once + # unless the step first restarts the box. + if s.until_milestone and not s.tool_action and s.until_milestone in self.engine.milestones: + self._record(s, f"already reached: {s.until_milestone}") + self._finish_wait("ok") + + def _auto(self, s: Step, why: str, target: str | None) -> None: + self._on_log(f"technician guide: {s.id}: {why}") + if target: + self._go(target) + + def _finish_wait(self, outcome: str) -> None: + s = self.step + target = {"ok": s.on_ok, "fail": s.on_fail or s.on_timeout, "timeout": s.on_timeout or s.on_fail}[outcome] + if target is None: + return + self._push() + self._go(target) + + def _milestone_new(self, mid: str) -> bool: + if mid not in self.engine.milestones: + return False + return self.engine.boot.index != self._wait_boot or mid not in self._wait_milestones + + def _route(self, s: Step) -> str: + report = self.engine.report() + for rc in report.roots: + for f in (rc.finding, *rc.consequences): + if f.rule_id in s.routes: + return s.routes[f.rule_id] + return s.default or "end:" + next(iter(self.guide.ends)) + + def _check_led(self, seen: str) -> None: + expected = self.engine.led + if expected in ("UNKNOWN", "OFF") or seen == "CYCLING" or seen == expected: + return + exp_label, see_label = LED[expected][0], LED_ANSWER_LABELS[seen] + self.engine.add_confirmed( + "tech.led_mismatch", Severity.WARNING, + f"LED shows {see_label}, but the log implies {exp_label}", + f"The firmware state in the log corresponds to {exp_label}, while the technician sees {see_label}. " + "Either the LED just changed (check again in a few seconds), the LED/its wiring is faulty, " + "or the box runs different firmware. Trust the log findings.", + ("Look at the LED again for 10 seconds.", "If it stays different: note it for repair (LED board).")) + + def _conclude(self, end: End) -> None: + self.step, self.end = None, end + self._on_log(f"technician guide result: {end.title}") + sev = END_SEVERITY[end.severity] + self.engine.note_technician(f"{self.guide.title}: {end.title}") + if sev is not None: + self.engine.add_confirmed(f"tech.{self.guide.id}.{end.id}", sev, end.title, end.text, end.actions) + + def _record(self, s: Step, answer: str) -> None: + q = self._render(s.text) + self.engine.note_technician(f"{q} -> {answer}") + self._on_log(f"technician: {q[:80]} -> {answer}") + + def _finding_active(self, rule_id: str) -> bool: + return any(f.rule_id == rule_id and f.active for f in self.engine.findings.values()) + + def _feature(self, name: str) -> bool: + fw = self.engine.firmware + if name == "sd_wifi": + return fw.supports_sd_wifi is not False + if name == "setup_portal": + if fw.known is not None: + return fw.known.setup_portal + if "setup_portal" in fw.evidence: + return True + return "legacy_v1" not in fw.evidence # unknown firmware: ask + return False + + def _render(self, text: str) -> str: + report = self.engine.report() + sensors = sorted({f.title.removesuffix(" failed to start") for f in self.engine.findings.values() + if f.rule_id == "sensor.init_failed" and f.active}) + ctx = { + "expected_led": LED[report.led][0], + "failing_sensors": ", ".join(sensors) or "see the Diagnosis tab", + "top_finding": report.roots[0].finding.title if report.roots else "none", + } + return text.format_map(_Fmt(ctx)) + + +class _Fmt(dict): + def __missing__(self, key: str) -> str: + return "{" + key + "}" diff --git a/kota_tech/diag/guides.toml b/kota_tech/diag/guides.toml index 4237587..0189ce6 100644 --- a/kota_tech/diag/guides.toml +++ b/kota_tech/diag/guides.toml @@ -1,740 +1,740 @@ -# Guided troubleshooting: decision trees that combine technician answers -# with live log evidence (diag.guides.GuideRunner executes them). -# -# Guide: id, title, summary, recommend (rule ids / "led:" / "always"), -# start (first step id), [[guide.step]], [[guide.end]] -# Step types: -# led technician picks the LED colour; `route` maps colour -> target. -# Colours: GREEN PURPLE RED BLUE YELLOW ORANGE WHITE OFF CYCLING -# The answer is compared with the colour the firmware log implies. -# question `options` = [{label, next}] -# info instruction + Continue (`next`) -# wait optional `tool_action` ("reset"), then watch the log: -# `until` (regex) / `until_milestone`, `fail_on` (regex), -# `timeout_s`; go to on_ok / on_fail / on_timeout. -# `pass_on_timeout = true`: reaching the timeout without fail_on -# counts as ok (used for "watch for N seconds"). -# route automatic: first current root finding whose rule id is in -# `routes` decides; otherwise `default`. -# Targets: "", "end:", "guide:". -# Optional on any step: skip_if_finding / skip_unless_feature (+ skip_to). -# Text placeholders: {expected_led} {failing_sensors} {top_finding} -# End severity: ok | info | warning | error | critical - -# ================================================================= quick -[[guide]] -id = "quick" -title = "Quick check - start here" -summary = "Tell the tool what the LED shows; it picks the right checks." -recommend = ["always"] -start = "led" - -[[guide.step]] -id = "led" -type = "led" -text = "Look at the RGB status LED on the box (no need to open it). Which colour is it?" -route = { GREEN = "heartbeat", PURPLE = "guide:network", RED = "guide:sensors", BLUE = "guide:rtc_i2c", WHITE = "guide:white", YELLOW = "guide:update", ORANGE = "guide:update", CYAN = "guide:network", OFF = "guide:power", CYCLING = "guide:reboot_loop" } - -[[guide.step]] -id = "heartbeat" -type = "question" -text = "Is the small LED next to the RGB LED blinking about once per second?" -options = [ - { label = "Yes, blinking", next = "green_route" }, - { label = "No - steady on or off", next = "end:hung" }, -] - -[[guide.step]] -id = "green_route" -type = "route" -routes = { "rtc.clock_not_running" = "guide:clock", "mic.silent" = "guide:mic", "mic.disabled" = "guide:mic", "matter.not_commissioned" = "guide:network", "net.dns_fail" = "guide:network" } -default = "end:healthy" - -[[guide.end]] -id = "healthy" -severity = "ok" -title = "Box looks healthy" -text = "Green LED, firmware running and no problems in the log." -actions = ["If the customer still reports missing data, keep the tool connected for 5 minutes and check that 'Data uploaded to InfluxDB' gets ticked."] - -[[guide.end]] -id = "hung" -severity = "error" -title = "Firmware is not running normally" -text = "The heartbeat LED should blink once per second whenever the firmware runs. A steady heartbeat LED means the firmware has frozen." -actions = [ - "Switch the box off and on with the power switch.", - "If it happens again: save the session log and re-flash the official firmware (Flash firmware tab, App only).", -] - -# =============================================================== network -[[guide]] -id = "network" -title = "Purple LED / no Internet" -summary = "Pairing in Matter, site Wi-Fi and Internet checks, with live confirmation." -recommend = ["led:PURPLE", "led:CYAN", "matter.not_commissioned", "net.dns_fail", "influx.buffer_fail", "ota.registration_failed", - "status.conn", "status.conn.1", "kota.wifi_down", "kota.no_upload", "kota.weak_wifi", "kota.ota_unregistered"] -start = "paired" - -[[guide.step]] -id = "paired" -type = "question" -skip_if_finding = "matter.not_commissioned" -skip_to = "pair_intro" -text = "Was this box already added to the customer's Matter app (Apple Home, Google Home, ...) at this site?" -options = [ - { label = "Yes, it was paired before", next = "internet" }, - { label = "No / not sure", next = "pair_intro" }, -] - -[[guide.step]] -id = "internet" -type = "question" -text = "Does the site Wi-Fi have working Internet? Check with a phone connected to the same Wi-Fi (open any website)." -options = [ - { label = "Yes, Internet works", next = "changed" }, - { label = "No Internet on the site Wi-Fi", next = "end:site_offline" }, -] - -[[guide.step]] -id = "changed" -type = "question" -text = "Was the Wi-Fi name or password changed, or the router replaced, since the box was installed?" -options = [ - { label = "Yes / possibly", next = "end:repair_needed" }, - { label = "No", next = "end:offline_unknown" }, -] - -[[guide.step]] -id = "pair_intro" -type = "info" -text = "The box is not on any Wi-Fi yet: it must be paired in Matter. You need: a phone with Bluetooth on, the customer's Matter app, the 2.4 GHz Wi-Fi password, and the QR sticker of this box. Keep the tool connected - it follows the pairing live." -next = "pair_start" - -[[guide.step]] -id = "pair_start" -type = "wait" -text = "In the Matter app choose 'Add device' and scan the QR code shown here (the same as the sticker), or type the 11-digit manual code. Keep the phone within 1 m of the box." -show_qr = true -until = 'MAIN: Commissioning session started' -timeout_s = 300 -on_ok = "pair_wifi" -on_timeout = "not_found" - -[[guide.step]] -id = "not_found" -type = "question" -text = "The box has not seen the phone connect yet. What does the app show?" -options = [ - { label = "Still searching / let me try again", next = "pair_start" }, - { label = "'Device not found' or similar", next = "end:ble_not_found" }, -] - -[[guide.step]] -id = "pair_wifi" -type = "wait" -text = "The phone is connected to the box. Follow the app: choose the 2.4 GHz Wi-Fi network and enter its password." -until = 'MAIN: Commissioning complete|MAIN: Fabric is committed' -fail_on = 'Commissioning failed, fail safe timer expired' -timeout_s = 300 -on_ok = "pair_time" -on_fail = "end:pair_failed" -on_timeout = "end:pair_failed" - -[[guide.step]] -id = "pair_time" -type = "wait" -text = "Paired! The box is joining the Wi-Fi and getting the time from the Internet..." -until_milestone = "time_synced" -timeout_s = 120 -on_ok = "pair_upload" -on_timeout = "end:joined_no_internet" - -[[guide.step]] -id = "pair_upload" -type = "wait" -text = "Online. Waiting for the first measurements to reach the database (up to 4 minutes)..." -until = 'INFLUX: HTTP request performed, status code: 2\d\d' -fail_on = 'INFLUX: Failed to perform HTTP request: (401|403|404)' -timeout_s = 240 -on_ok = "end:online" -on_fail = "end:influx_rejected" -on_timeout = "end:no_upload" - -[[guide.end]] -id = "online" -severity = "ok" -title = "Box is paired and uploading data" -text = "Pairing, Internet time and the first upload all worked." -actions = [ - "The box may now download a firmware update (yellow, then orange LED, a few minutes). Do not switch it off.", - "It should end green.", -] - -[[guide.end]] -id = "site_offline" -severity = "warning" -title = "Site network has no Internet" -text = "The box cannot work until the site Wi-Fi reaches the Internet. This is not a box fault." -actions = ["Ask the customer to fix the Internet connection, then power-cycle the box."] - -[[guide.end]] -id = "repair_needed" -severity = "warning" -title = "Box must be paired again" -text = "The box still remembers the old Wi-Fi. It needs a factory reset and new pairing." -actions = [ - "Hold the Matter button for 5 seconds (LED turns white) - this erases the old pairing.", - "Remove the box from the customer's Matter app, then run this guide again and choose 'No / not sure'.", -] - -[[guide.end]] -id = "offline_unknown" -severity = "error" -title = "Box is paired but cannot reach the Internet - cause unclear" -text = "The network works for other devices, but not for this box." -actions = [ - "Check the router does not block new devices (guest isolation, MAC filter). Box MAC is shown in the port list.", - "Move the box closer to the router to rule out weak signal.", - "If nothing helps: factory reset (hold the Matter button 5 s) and pair again.", -] - -[[guide.end]] -id = "ble_not_found" -severity = "error" -title = "Phone cannot find the box over Bluetooth" -text = "The box only accepts pairing for a limited time after start-up." -actions = [ - "Switch the box off and on to reopen the pairing window, then try again within 10 minutes.", - "Check Bluetooth and location permission are on for the Matter app.", - "If the box was paired to another app before: hold the Matter button 5 s (factory reset) first.", -] - -[[guide.end]] -id = "pair_failed" -severity = "error" -title = "Pairing did not finish" -text = "The phone connected but the box did not join the Wi-Fi." -actions = [ - "Use a 2.4 GHz network (many routers have a separate 2.4 GHz name).", - "Re-enter the Wi-Fi password carefully.", - "Switch the box off and on, then pair again.", -] - -[[guide.end]] -id = "joined_no_internet" -severity = "error" -title = "Box joined the Wi-Fi but has no Internet" -text = "Pairing worked, but the box cannot reach time or data servers." -actions = [ - "Check the network allows outgoing connections (HTTPS, NTP) for new devices.", - "Try a phone hotspot to rule out the site network.", -] - -[[guide.end]] -id = "influx_rejected" -severity = "critical" -title = "Database rejected the box (token / bucket)" -text = "The box is online but InfluxDB refused the upload. The SD card settings are wrong." -actions = ["Re-provision the SD card with the current ENV.TXT (SD card is inside the case)."] - -[[guide.end]] -id = "no_upload" -severity = "warning" -title = "Online, but no upload seen yet" -text = "Uploads happen every minute; the first one can be delayed while the air sensor warms up." -actions = ["Keep the tool connected a few more minutes and watch the Diagnosis tab."] - -# ================================================================= RTC / I2C -[[guide]] -id = "rtc_i2c" -title = "Blue LED - clock / I2C cable" -summary = "Re-seat the I2C cable and verify the start-up live." -recommend = ["led:BLUE", "rtc.init_failed", "i2c.errors", "kota.i2c_stuck", "kota.rtc_read_failed", "status.rtc.1", "status.rtc.2"] -start = "explain" - -[[guide.step]] -id = "explain" -type = "info" -text = "Blue means the clock chip did not answer. It is the first chip on the shared I2C cable between the boards, so a loose cable is the most common cause. This check needs the case opened." -next = "reseat" - -[[guide.step]] -id = "reseat" -type = "info" -text = "Switch the power OFF. Open the case. Unplug and firmly re-plug the I2C cable between the boards at both ends. Close the case and switch the power ON. Then press Continue." -next = "verify" - -[[guide.step]] -id = "verify" -type = "wait" -text = "Watching the start-up (up to 60 s)..." -until_milestone = "sensors_ready" -fail_on = 'MAIN: Failed to initialize RTC' -timeout_s = 60 -on_ok = "end:i2c_fixed" -on_fail = "damaged" -on_timeout = "damaged" - -[[guide.step]] -id = "damaged" -type = "question" -text = "The clock still does not answer. Is the I2C cable or its connector visibly damaged (pinched, broken wire, bent pin)?" -options = [ - { label = "Yes, damaged", next = "end:replace_cable" }, - { label = "No, looks fine", next = "end:rtc_module" }, -] - -[[guide.end]] -id = "i2c_fixed" -severity = "ok" -title = "Fixed - all sensors start again" -text = "After re-seating the cable the clock and all sensors started." -actions = ["Make sure the cable is routed without strain before closing the case."] - -[[guide.end]] -id = "replace_cable" -severity = "error" -title = "Replace the I2C cable" -text = "A damaged I2C cable stops the clock and all sensors." -actions = ["Replace the cable, then run this check again."] - -[[guide.end]] -id = "rtc_module" -severity = "error" -title = "Clock (RTC) module is probably faulty" -text = "The cable is fine but the clock chip does not answer." -actions = ["Replace the RTC module, or send the box for repair with the session log."] - -# ==================================================================== white -[[guide]] -id = "white" -title = "White LED - SD card / reset / setup mode" -summary = "Tell apart the three white states and check the SD card." -recommend = ["led:WHITE", "sd.card_fail", "sd.mount_fail", "sd.env_open_fail", "influx.env_missing", "influx.init_fail", - "kota.sd_unmounted", "status.sd", "status.portal", "status.reset"] -start = "button" - -[[guide.step]] -id = "button" -type = "question" -text = "Did anyone hold the Matter button (outside the case) for about 5 seconds just before the LED turned white?" -options = [ - { label = "Yes", next = "end:factory_reset" }, - { label = "No / don't know", next = "portal" }, -] - -[[guide.step]] -id = "portal" -type = "question" -skip_unless_feature = "setup_portal" -skip_to = "sd_reseat" -text = "On a phone, look at the Wi-Fi list: is there a network called 'KotaSensorBox-....'?" -options = [ - { label = "Yes", next = "end:setup_mode" }, - { label = "No", next = "sd_reseat" }, -] - -[[guide.step]] -id = "sd_reseat" -type = "info" -text = "The SD card is probably not readable. This needs the case opened: switch the power OFF, open the case, push the SD card in until it clicks (or remove and re-insert it). Close the case, switch the power ON, then press Continue." -next = "sd_verify" - -[[guide.step]] -id = "sd_verify" -type = "wait" -text = "Watching the SD card start-up (up to 60 s)..." -until = 'Read INFLUX_URL from \.env file' -fail_on = 'Failed to initialize the card|Failed to mount filesystem|Failed to read INFLUX_(URL|TOKEN)|Failed to open \.env file' -timeout_s = 60 -on_ok = "end:sd_fixed" -on_fail = "end:sd_reprovision" -on_timeout = "end:sd_reprovision" - -[[guide.end]] -id = "factory_reset" -severity = "info" -title = "White = factory reset in progress" -text = "The box erased its Matter pairing and restarts." -actions = ["Pair it again in the Matter app (Purple LED guide)."] - -[[guide.end]] -id = "setup_mode" -severity = "info" -title = "White = Wi-Fi setup mode" -text = "The box is waiting for Wi-Fi settings on its own network." -actions = [ - "Connect a phone to KotaSensorBox-.... and open http://sensorbox.local/ to enter the Wi-Fi,", - "or short-press the Matter button (< 2 s) to leave setup mode.", -] - -[[guide.end]] -id = "sd_fixed" -severity = "ok" -title = "Fixed - SD card is read again" -text = "The box found the SD card and read ENV.TXT." -actions = ["Close the case and watch the Diagnosis tab for a minute."] - -[[guide.end]] -id = "sd_reprovision" -severity = "critical" -title = "SD card is unreadable or has wrong settings" -text = "Even after re-seating, the card or its ENV.TXT cannot be used." -actions = [ - "Re-provision the card with the tool (SD card tab) or use a new card.", - "The Diagnosis tab shows which step failed (card, file system or ENV.TXT keys).", -] - -# ================================================================== sensors -[[guide]] -id = "sensors" -title = "Red LED - sensor or hardware fault" -summary = "Find the failing sensor and verify after re-seating its cable." -recommend = ["led:RED", "sensor.init_failed", "sensor.selfcheck_failed", "sys.out_of_resources", "ota.selfcheck_failed", - "kota.sensor_missing", "status.error", "status.error.1", "status.error.2", "status.error.3", "status.error.4", - "status.error.5", "status.error.6", "status.oom"] -start = "which" - -[[guide.step]] -id = "which" -type = "route" -routes = { "sensor.init_failed" = "reseat", "rtc.init_failed" = "guide:rtc_i2c", "ota.selfcheck_failed" = "end:selfcheck", "sensor.selfcheck_failed" = "end:selfcheck", "sys.out_of_resources" = "end:oom" } -default = "capture" - -[[guide.step]] -id = "capture" -type = "wait" -text = "The log does not show the cause yet. The tool restarts the box and records its start-up (45 s)..." -tool_action = "reset" -timeout_s = 45 -pass_on_timeout = true -on_ok = "which_again" - -[[guide.step]] -id = "which_again" -type = "route" -routes = { "sensor.init_failed" = "reseat", "rtc.init_failed" = "guide:rtc_i2c", "sys.out_of_resources" = "end:oom" } -default = "end:red_unclear" - -[[guide.step]] -id = "reseat" -type = "info" -text = "Failing: {failing_sensors}. This needs the case opened: switch the power OFF, open the case and re-seat the cable of that sensor. Close the case, switch the power ON, then press Continue." -next = "verify" - -[[guide.step]] -id = "verify" -type = "wait" -text = "Watching the sensor start-up (up to 60 s)..." -until_milestone = "sensors_ready" -fail_on = 'MAIN: Failed to initialize' -timeout_s = 60 -on_ok = "end:sensor_fixed" -on_fail = "end:sensor_replace" -on_timeout = "end:sensor_replace" - -[[guide.end]] -id = "sensor_fixed" -severity = "ok" -title = "Fixed - all sensors start again" -text = "After re-seating, every sensor started." -actions = ["Close the case and watch the Diagnosis tab for a minute."] - -[[guide.end]] -id = "sensor_replace" -severity = "error" -title = "Sensor still fails - replace it" -text = "The sensor does not start even with a re-seated cable." -actions = ["Replace the named sensor (or its cable) and run this check again."] - -[[guide.end]] -id = "selfcheck" -severity = "error" -title = "A firmware update was rejected by its self-check" -text = "The box rolled back to its previous firmware because a sensor failed the test." -actions = ["Check the sensor named in the Diagnosis tab (inside the case), then power-cycle."] - -[[guide.end]] -id = "oom" -severity = "error" -title = "Firmware ran out of memory" -text = "Not a hardware fault; the firmware restarts itself." -actions = ["Power-cycle. If it repeats, send the session log to the firmware team."] - -[[guide.end]] -id = "red_unclear" -severity = "warning" -title = "Red LED without a clear cause in the log" -text = "The start-up did not show a failing sensor." -actions = ["Save the session log and send it to the firmware team."] - -# ============================================================= reboot loop -[[guide]] -id = "reboot_loop" -title = "LED keeps changing / box restarts" -summary = "Record several start-ups and find the error just before each restart." -recommend = ["led:CYCLING", "reboot.loop", "sys.crash", "reboot.unexpected", "reboot.brownout", "reboot.watchdog", - "reboot.crash", "reboot.status_watchdog", "reboot.status_oom_watchdog"] -start = "record" - -[[guide.step]] -id = "record" -type = "wait" -text = "The tool restarts the box and records 45 seconds of start-up..." -tool_action = "reset" -timeout_s = 45 -pass_on_timeout = true -on_ok = "cause" - -[[guide.step]] -id = "cause" -type = "route" -routes = { "rtc.init_failed" = "guide:rtc_i2c", "sd.card_fail" = "guide:white", "sd.mount_fail" = "guide:white", "sd.env_open_fail" = "guide:white", "influx.env_missing" = "guide:white", "influx.init_fail" = "guide:white", "sensor.init_failed" = "guide:sensors", "reboot.brownout" = "end:power_supply", "matter.not_commissioned" = "guide:network", "net.dns_fail" = "guide:network", "influx.buffer_fail" = "guide:network" } -default = "end:loop_unclear" - -[[guide.end]] -id = "power_supply" -severity = "error" -title = "Power supply is too weak or unstable" -text = "The chip reset itself because the voltage dropped." -actions = ["Check the power supply, cable and connectors; try another supply."] - -[[guide.end]] -id = "loop_unclear" -severity = "warning" -title = "No clear restart cause in 45 s" -text = "The box did not show a known start-up error during the recording." -actions = ["Keep the tool connected for 5 minutes, then save the session log for the firmware team."] - -# ================================================================== update -[[guide]] -id = "update" -title = "Yellow / orange LED - firmware update" -summary = "Wait for the update and its self-check to finish." -recommend = ["led:YELLOW", "led:ORANGE", "ota.updating", "ota.selfcheck"] -start = "wait" - -[[guide.step]] -id = "wait" -type = "wait" -text = "A firmware update or its self-check is running. Do NOT switch the box off. Waiting up to 10 minutes..." -until = 'Passing selfcheck|OTA update successful' -fail_on = 'OTA update failed|Failing selfcheck' -timeout_s = 600 -on_ok = "end:update_ok" -on_fail = "end:update_failed" -on_timeout = "end:update_slow" - -[[guide.end]] -id = "update_ok" -severity = "ok" -title = "Update finished" -text = "The new firmware was installed or accepted." -actions = ["Wait for the green LED."] - -[[guide.end]] -id = "update_failed" -severity = "error" -title = "Update failed" -text = "The box keeps its current firmware and retries later." -actions = ["Check the Wi-Fi is stable. The Diagnosis tab shows why it failed."] - -[[guide.end]] -id = "update_slow" -severity = "warning" -title = "Update is taking very long" -text = "No end of the update was seen in 10 minutes." -actions = ["Check the Wi-Fi signal; leave the box powered and check again later."] - -# =================================================================== power -[[guide]] -id = "power" -title = "LED off" -summary = "Power switch, heartbeat LED and USB connection." -recommend = ["led:OFF"] -start = "switch" - -[[guide.step]] -id = "switch" -type = "question" -text = "Is the power switch on the outside of the case set to ON?" -options = [ - { label = "It was OFF - I switched it on", next = "boot" }, - { label = "It is ON", next = "heartbeat" }, -] - -[[guide.step]] -id = "boot" -type = "wait" -text = "Waiting for the box to start (up to 30 s)..." -until_milestone = "app_started" -timeout_s = 30 -on_ok = "end:was_off" -on_timeout = "heartbeat" - -[[guide.step]] -id = "heartbeat" -type = "question" -text = "Is the small LED next to the RGB LED blinking?" -options = [ - { label = "Yes, blinking", next = "end:rgb_led_fault" }, - { label = "No", next = "end:no_power" }, -] - -[[guide.end]] -id = "was_off" -severity = "ok" -title = "Box was switched off" -text = "It starts normally after switching on." -actions = ["Run the Quick check again once the LED shows a colour."] - -[[guide.end]] -id = "rgb_led_fault" -severity = "warning" -title = "Firmware runs, but the RGB LED stays dark" -text = "The heartbeat LED shows the firmware is running; the RGB LED or its wiring is faulty." -actions = ["Use the Diagnosis tab instead of the LED; replace the LED board when convenient."] - -[[guide.end]] -id = "no_power" -severity = "error" -title = "Box does not start" -text = "Neither LED is lit: no power or the firmware does not start." -actions = [ - "Check the power supply and cable.", - "If the USB port shows up in this tool, re-flash the official firmware (Flash firmware tab, App only).", -] - -# =================================================================== clock -[[guide]] -id = "clock" -title = "Clock not set" -summary = "Why readings are on hold and when the coin cell matters." -recommend = ["rtc.clock_not_running", "rtc.lost_after_sync", "kota.rtc_stopped", "kota.rtc_not_ticking", "kota.time_invalid"] -start = "internet" - -[[guide.step]] -id = "internet" -type = "question" -text = "The box clock is not set, so readings are on hold. Will the box have Internet at its installation site?" -options = [ - { label = "Yes", next = "end:clock_auto" }, - { label = "No / not yet", next = "end:clock_offline" }, -] - -[[guide.end]] -id = "clock_auto" -severity = "info" -title = "Clock will be set automatically" -text = "On its first Internet connection the box sets its clock and starts recording." -actions = ["If the clock is lost again after every power-off, replace the RTC coin cell (inside the case)."] - -[[guide.end]] -id = "clock_offline" -severity = "error" -title = "Box will not record data without Internet" -text = "Without the Internet the clock cannot be set, and the firmware holds all readings until it is." -actions = [ - "Give the box Internet access (pair it in Matter).", - "Replace the RTC coin cell (inside the case) so the time survives power-offs.", -] - -# ===================================================================== mic -[[guide]] -id = "mic" -title = "Microphone check" -summary = "Switched-off or silent microphone." -recommend = ["mic.disabled", "mic.silent", "mic.read_fail"] -start = "which" - -[[guide.step]] -id = "which" -type = "route" -routes = { "mic.disabled" = "switch", "mic.silent" = "clap", "mic.read_fail" = "end:mic_faulty" } -default = "clap" - -[[guide.step]] -id = "switch" -type = "question" -text = "The box reads its microphone switch as OFF. The switch is on the back of the case, just above the power switch (no need to open the box). Should this box measure noise?" -options = [ - { label = "Yes - I flipped the microphone switch", next = "switch_verify" }, - { label = "No - it is meant to be off", next = "end:mic_off_ok" }, -] - -[[guide.step]] -id = "switch_verify" -type = "wait" -# The firmware re-reads the switch before every reading (~10 s): no restart needed. -text = "Waiting for the next microphone reading (up to 30 s)..." -until = 'MICROPHONE: Read \d+ bytes' -fail_on = 'Microphone is in shutdown state' -timeout_s = 30 -on_ok = "end:mic_on" -on_fail = "flip_back" -on_timeout = "flip_back" - -[[guide.step]] -id = "flip_back" -type = "question" -text = "The box still reads the switch as OFF. Flip the microphone switch to the other position (it may have been ON already)." -options = [ - { label = "Done - I flipped it to the other position", next = "switch_verify2" }, - { label = "Leave it off", next = "end:mic_off_ok" }, -] - -[[guide.step]] -id = "switch_verify2" -type = "wait" -text = "Waiting for the next microphone reading (up to 30 s)..." -until = 'MICROPHONE: Read \d+ bytes' -fail_on = 'Microphone is in shutdown state' -timeout_s = 30 -on_ok = "end:mic_on" -on_fail = "end:mic_switch_faulty" -on_timeout = "end:mic_switch_faulty" - -[[guide.step]] -id = "clap" -type = "wait" -text = "Check the microphone switch (back of the case, above the power switch) is ON. Then clap your hands or speak loudly right next to the box for about 20 seconds..." -until = 'dB peak: (?!-inf)' -timeout_s = 45 -on_ok = "end:mic_ok" -on_timeout = "end:mic_faulty" - -[[guide.end]] -id = "mic_on" -severity = "ok" -title = "Microphone is on and recording" -text = "The firmware reads audio samples again." -actions = ["Leave the microphone switch in this position."] - -[[guide.end]] -id = "mic_off_ok" -severity = "info" -title = "Microphone intentionally off" -text = "No noise data is recorded for this box; everything else works." -actions = [] - -[[guide.end]] -id = "mic_switch_faulty" -severity = "warning" -title = "Microphone switch has no effect" -text = "The box reads the microphone as OFF in both switch positions (enable line GPIO3)." -actions = ["Check the switch and its cable to the main board (inside the case); replace the switch if needed."] - -[[guide.end]] -id = "mic_ok" -severity = "ok" -title = "Microphone works" -text = "It picked up sound - the earlier silence was just a very quiet room." -actions = [] - -[[guide.end]] -id = "mic_faulty" -severity = "error" -title = "Microphone records nothing" -text = "Even loud noise next to the box gives no signal, although the switch is on." -actions = ["Check the microphone board/cable (inside the case); replace the microphone if needed."] +# Guided troubleshooting: decision trees that combine technician answers +# with live log evidence (diag.guides.GuideRunner executes them). +# +# Guide: id, title, summary, recommend (rule ids / "led:" / "always"), +# start (first step id), [[guide.step]], [[guide.end]] +# Step types: +# led technician picks the LED colour; `route` maps colour -> target. +# Colours: GREEN PURPLE RED BLUE YELLOW ORANGE WHITE OFF CYCLING +# The answer is compared with the colour the firmware log implies. +# question `options` = [{label, next}] +# info instruction + Continue (`next`) +# wait optional `tool_action` ("reset"), then watch the log: +# `until` (regex) / `until_milestone`, `fail_on` (regex), +# `timeout_s`; go to on_ok / on_fail / on_timeout. +# `pass_on_timeout = true`: reaching the timeout without fail_on +# counts as ok (used for "watch for N seconds"). +# route automatic: first current root finding whose rule id is in +# `routes` decides; otherwise `default`. +# Targets: "", "end:", "guide:". +# Optional on any step: skip_if_finding / skip_unless_feature (+ skip_to). +# Text placeholders: {expected_led} {failing_sensors} {top_finding} +# End severity: ok | info | warning | error | critical + +# ================================================================= quick +[[guide]] +id = "quick" +title = "Quick check - start here" +summary = "Tell the tool what the LED shows; it picks the right checks." +recommend = ["always"] +start = "led" + +[[guide.step]] +id = "led" +type = "led" +text = "Look at the RGB status LED on the box (no need to open it). Which colour is it?" +route = { GREEN = "heartbeat", PURPLE = "guide:network", RED = "guide:sensors", BLUE = "guide:rtc_i2c", WHITE = "guide:white", YELLOW = "guide:update", ORANGE = "guide:update", CYAN = "guide:network", OFF = "guide:power", CYCLING = "guide:reboot_loop" } + +[[guide.step]] +id = "heartbeat" +type = "question" +text = "Is the small LED next to the RGB LED blinking about once per second?" +options = [ + { label = "Yes, blinking", next = "green_route" }, + { label = "No - steady on or off", next = "end:hung" }, +] + +[[guide.step]] +id = "green_route" +type = "route" +routes = { "rtc.clock_not_running" = "guide:clock", "mic.silent" = "guide:mic", "mic.disabled" = "guide:mic", "matter.not_commissioned" = "guide:network", "net.dns_fail" = "guide:network" } +default = "end:healthy" + +[[guide.end]] +id = "healthy" +severity = "ok" +title = "Box looks healthy" +text = "Green LED, firmware running and no problems in the log." +actions = ["If the customer still reports missing data, keep the tool connected for 5 minutes and check that 'Data uploaded to InfluxDB' gets ticked."] + +[[guide.end]] +id = "hung" +severity = "error" +title = "Firmware is not running normally" +text = "The heartbeat LED should blink once per second whenever the firmware runs. A steady heartbeat LED means the firmware has frozen." +actions = [ + "Switch the box off and on with the power switch.", + "If it happens again: save the session log and re-flash the official firmware (Flash firmware tab, App only).", +] + +# =============================================================== network +[[guide]] +id = "network" +title = "Purple LED / no Internet" +summary = "Pairing in Matter, site Wi-Fi and Internet checks, with live confirmation." +recommend = ["led:PURPLE", "led:CYAN", "matter.not_commissioned", "net.dns_fail", "influx.buffer_fail", "ota.registration_failed", + "status.conn", "status.conn.1", "kota.wifi_down", "kota.no_upload", "kota.weak_wifi", "kota.ota_unregistered"] +start = "paired" + +[[guide.step]] +id = "paired" +type = "question" +skip_if_finding = "matter.not_commissioned" +skip_to = "pair_intro" +text = "Was this box already added to the customer's Matter app (Apple Home, Google Home, ...) at this site?" +options = [ + { label = "Yes, it was paired before", next = "internet" }, + { label = "No / not sure", next = "pair_intro" }, +] + +[[guide.step]] +id = "internet" +type = "question" +text = "Does the site Wi-Fi have working Internet? Check with a phone connected to the same Wi-Fi (open any website)." +options = [ + { label = "Yes, Internet works", next = "changed" }, + { label = "No Internet on the site Wi-Fi", next = "end:site_offline" }, +] + +[[guide.step]] +id = "changed" +type = "question" +text = "Was the Wi-Fi name or password changed, or the router replaced, since the box was installed?" +options = [ + { label = "Yes / possibly", next = "end:repair_needed" }, + { label = "No", next = "end:offline_unknown" }, +] + +[[guide.step]] +id = "pair_intro" +type = "info" +text = "The box is not on any Wi-Fi yet: it must be paired in Matter. You need: a phone with Bluetooth on, the customer's Matter app, the 2.4 GHz Wi-Fi password, and the QR sticker of this box. Keep the tool connected - it follows the pairing live." +next = "pair_start" + +[[guide.step]] +id = "pair_start" +type = "wait" +text = "In the Matter app choose 'Add device' and scan the QR code shown here (the same as the sticker), or type the 11-digit manual code. Keep the phone within 1 m of the box." +show_qr = true +until = 'MAIN: Commissioning session started' +timeout_s = 300 +on_ok = "pair_wifi" +on_timeout = "not_found" + +[[guide.step]] +id = "not_found" +type = "question" +text = "The box has not seen the phone connect yet. What does the app show?" +options = [ + { label = "Still searching / let me try again", next = "pair_start" }, + { label = "'Device not found' or similar", next = "end:ble_not_found" }, +] + +[[guide.step]] +id = "pair_wifi" +type = "wait" +text = "The phone is connected to the box. Follow the app: choose the 2.4 GHz Wi-Fi network and enter its password." +until = 'MAIN: Commissioning complete|MAIN: Fabric is committed' +fail_on = 'Commissioning failed, fail safe timer expired' +timeout_s = 300 +on_ok = "pair_time" +on_fail = "end:pair_failed" +on_timeout = "end:pair_failed" + +[[guide.step]] +id = "pair_time" +type = "wait" +text = "Paired! The box is joining the Wi-Fi and getting the time from the Internet..." +until_milestone = "time_synced" +timeout_s = 120 +on_ok = "pair_upload" +on_timeout = "end:joined_no_internet" + +[[guide.step]] +id = "pair_upload" +type = "wait" +text = "Online. Waiting for the first measurements to reach the database (up to 4 minutes)..." +until = 'INFLUX: HTTP request performed, status code: 2\d\d' +fail_on = 'INFLUX: Failed to perform HTTP request: (401|403|404)' +timeout_s = 240 +on_ok = "end:online" +on_fail = "end:influx_rejected" +on_timeout = "end:no_upload" + +[[guide.end]] +id = "online" +severity = "ok" +title = "Box is paired and uploading data" +text = "Pairing, Internet time and the first upload all worked." +actions = [ + "The box may now download a firmware update (yellow, then orange LED, a few minutes). Do not switch it off.", + "It should end green.", +] + +[[guide.end]] +id = "site_offline" +severity = "warning" +title = "Site network has no Internet" +text = "The box cannot work until the site Wi-Fi reaches the Internet. This is not a box fault." +actions = ["Ask the customer to fix the Internet connection, then power-cycle the box."] + +[[guide.end]] +id = "repair_needed" +severity = "warning" +title = "Box must be paired again" +text = "The box still remembers the old Wi-Fi. It needs a factory reset and new pairing." +actions = [ + "Hold the Matter button for 5 seconds (LED turns white) - this erases the old pairing.", + "Remove the box from the customer's Matter app, then run this guide again and choose 'No / not sure'.", +] + +[[guide.end]] +id = "offline_unknown" +severity = "error" +title = "Box is paired but cannot reach the Internet - cause unclear" +text = "The network works for other devices, but not for this box." +actions = [ + "Check the router does not block new devices (guest isolation, MAC filter). Box MAC is shown in the port list.", + "Move the box closer to the router to rule out weak signal.", + "If nothing helps: factory reset (hold the Matter button 5 s) and pair again.", +] + +[[guide.end]] +id = "ble_not_found" +severity = "error" +title = "Phone cannot find the box over Bluetooth" +text = "The box only accepts pairing for a limited time after start-up." +actions = [ + "Switch the box off and on to reopen the pairing window, then try again within 10 minutes.", + "Check Bluetooth and location permission are on for the Matter app.", + "If the box was paired to another app before: hold the Matter button 5 s (factory reset) first.", +] + +[[guide.end]] +id = "pair_failed" +severity = "error" +title = "Pairing did not finish" +text = "The phone connected but the box did not join the Wi-Fi." +actions = [ + "Use a 2.4 GHz network (many routers have a separate 2.4 GHz name).", + "Re-enter the Wi-Fi password carefully.", + "Switch the box off and on, then pair again.", +] + +[[guide.end]] +id = "joined_no_internet" +severity = "error" +title = "Box joined the Wi-Fi but has no Internet" +text = "Pairing worked, but the box cannot reach time or data servers." +actions = [ + "Check the network allows outgoing connections (HTTPS, NTP) for new devices.", + "Try a phone hotspot to rule out the site network.", +] + +[[guide.end]] +id = "influx_rejected" +severity = "critical" +title = "Database rejected the box (token / bucket)" +text = "The box is online but InfluxDB refused the upload. The SD card settings are wrong." +actions = ["Re-provision the SD card with the current ENV.TXT (SD card is inside the case)."] + +[[guide.end]] +id = "no_upload" +severity = "warning" +title = "Online, but no upload seen yet" +text = "Uploads happen every minute; the first one can be delayed while the air sensor warms up." +actions = ["Keep the tool connected a few more minutes and watch the Diagnosis tab."] + +# ================================================================= RTC / I2C +[[guide]] +id = "rtc_i2c" +title = "Blue LED - clock / I2C cable" +summary = "Re-seat the I2C cable and verify the start-up live." +recommend = ["led:BLUE", "rtc.init_failed", "i2c.errors", "kota.i2c_stuck", "kota.rtc_read_failed", "status.rtc.1", "status.rtc.2"] +start = "explain" + +[[guide.step]] +id = "explain" +type = "info" +text = "Blue means the clock chip did not answer. It is the first chip on the shared I2C cable between the boards, so a loose cable is the most common cause. This check needs the case opened." +next = "reseat" + +[[guide.step]] +id = "reseat" +type = "info" +text = "Switch the power OFF. Open the case. Unplug and firmly re-plug the I2C cable between the boards at both ends. Close the case and switch the power ON. Then press Continue." +next = "verify" + +[[guide.step]] +id = "verify" +type = "wait" +text = "Watching the start-up (up to 60 s)..." +until_milestone = "sensors_ready" +fail_on = 'MAIN: Failed to initialize RTC' +timeout_s = 60 +on_ok = "end:i2c_fixed" +on_fail = "damaged" +on_timeout = "damaged" + +[[guide.step]] +id = "damaged" +type = "question" +text = "The clock still does not answer. Is the I2C cable or its connector visibly damaged (pinched, broken wire, bent pin)?" +options = [ + { label = "Yes, damaged", next = "end:replace_cable" }, + { label = "No, looks fine", next = "end:rtc_module" }, +] + +[[guide.end]] +id = "i2c_fixed" +severity = "ok" +title = "Fixed - all sensors start again" +text = "After re-seating the cable the clock and all sensors started." +actions = ["Make sure the cable is routed without strain before closing the case."] + +[[guide.end]] +id = "replace_cable" +severity = "error" +title = "Replace the I2C cable" +text = "A damaged I2C cable stops the clock and all sensors." +actions = ["Replace the cable, then run this check again."] + +[[guide.end]] +id = "rtc_module" +severity = "error" +title = "Clock (RTC) module is probably faulty" +text = "The cable is fine but the clock chip does not answer." +actions = ["Replace the RTC module, or send the box for repair with the session log."] + +# ==================================================================== white +[[guide]] +id = "white" +title = "White LED - SD card / reset / setup mode" +summary = "Tell apart the three white states and check the SD card." +recommend = ["led:WHITE", "sd.card_fail", "sd.mount_fail", "sd.env_open_fail", "influx.env_missing", "influx.init_fail", + "kota.sd_unmounted", "status.sd", "status.portal", "status.reset"] +start = "button" + +[[guide.step]] +id = "button" +type = "question" +text = "Did anyone hold the Matter button (outside the case) for about 5 seconds just before the LED turned white?" +options = [ + { label = "Yes", next = "end:factory_reset" }, + { label = "No / don't know", next = "portal" }, +] + +[[guide.step]] +id = "portal" +type = "question" +skip_unless_feature = "setup_portal" +skip_to = "sd_reseat" +text = "On a phone, look at the Wi-Fi list: is there a network called 'KotaSensorBox-....'?" +options = [ + { label = "Yes", next = "end:setup_mode" }, + { label = "No", next = "sd_reseat" }, +] + +[[guide.step]] +id = "sd_reseat" +type = "info" +text = "The SD card is probably not readable. This needs the case opened: switch the power OFF, open the case, push the SD card in until it clicks (or remove and re-insert it). Close the case, switch the power ON, then press Continue." +next = "sd_verify" + +[[guide.step]] +id = "sd_verify" +type = "wait" +text = "Watching the SD card start-up (up to 60 s)..." +until = 'Read INFLUX_URL from \.env file' +fail_on = 'Failed to initialize the card|Failed to mount filesystem|Failed to read INFLUX_(URL|TOKEN)|Failed to open \.env file' +timeout_s = 60 +on_ok = "end:sd_fixed" +on_fail = "end:sd_reprovision" +on_timeout = "end:sd_reprovision" + +[[guide.end]] +id = "factory_reset" +severity = "info" +title = "White = factory reset in progress" +text = "The box erased its Matter pairing and restarts." +actions = ["Pair it again in the Matter app (Purple LED guide)."] + +[[guide.end]] +id = "setup_mode" +severity = "info" +title = "White = Wi-Fi setup mode" +text = "The box is waiting for Wi-Fi settings on its own network." +actions = [ + "Connect a phone to KotaSensorBox-.... and open http://sensorbox.local/ to enter the Wi-Fi,", + "or short-press the Matter button (< 2 s) to leave setup mode.", +] + +[[guide.end]] +id = "sd_fixed" +severity = "ok" +title = "Fixed - SD card is read again" +text = "The box found the SD card and read ENV.TXT." +actions = ["Close the case and watch the Diagnosis tab for a minute."] + +[[guide.end]] +id = "sd_reprovision" +severity = "critical" +title = "SD card is unreadable or has wrong settings" +text = "Even after re-seating, the card or its ENV.TXT cannot be used." +actions = [ + "Re-provision the card with the tool (SD card tab) or use a new card.", + "The Diagnosis tab shows which step failed (card, file system or ENV.TXT keys).", +] + +# ================================================================== sensors +[[guide]] +id = "sensors" +title = "Red LED - sensor or hardware fault" +summary = "Find the failing sensor and verify after re-seating its cable." +recommend = ["led:RED", "sensor.init_failed", "sensor.selfcheck_failed", "sys.out_of_resources", "ota.selfcheck_failed", + "kota.sensor_missing", "status.error", "status.error.1", "status.error.2", "status.error.3", "status.error.4", + "status.error.5", "status.error.6", "status.oom"] +start = "which" + +[[guide.step]] +id = "which" +type = "route" +routes = { "sensor.init_failed" = "reseat", "rtc.init_failed" = "guide:rtc_i2c", "ota.selfcheck_failed" = "end:selfcheck", "sensor.selfcheck_failed" = "end:selfcheck", "sys.out_of_resources" = "end:oom" } +default = "capture" + +[[guide.step]] +id = "capture" +type = "wait" +text = "The log does not show the cause yet. The tool restarts the box and records its start-up (45 s)..." +tool_action = "reset" +timeout_s = 45 +pass_on_timeout = true +on_ok = "which_again" + +[[guide.step]] +id = "which_again" +type = "route" +routes = { "sensor.init_failed" = "reseat", "rtc.init_failed" = "guide:rtc_i2c", "sys.out_of_resources" = "end:oom" } +default = "end:red_unclear" + +[[guide.step]] +id = "reseat" +type = "info" +text = "Failing: {failing_sensors}. This needs the case opened: switch the power OFF, open the case and re-seat the cable of that sensor. Close the case, switch the power ON, then press Continue." +next = "verify" + +[[guide.step]] +id = "verify" +type = "wait" +text = "Watching the sensor start-up (up to 60 s)..." +until_milestone = "sensors_ready" +fail_on = 'MAIN: Failed to initialize' +timeout_s = 60 +on_ok = "end:sensor_fixed" +on_fail = "end:sensor_replace" +on_timeout = "end:sensor_replace" + +[[guide.end]] +id = "sensor_fixed" +severity = "ok" +title = "Fixed - all sensors start again" +text = "After re-seating, every sensor started." +actions = ["Close the case and watch the Diagnosis tab for a minute."] + +[[guide.end]] +id = "sensor_replace" +severity = "error" +title = "Sensor still fails - replace it" +text = "The sensor does not start even with a re-seated cable." +actions = ["Replace the named sensor (or its cable) and run this check again."] + +[[guide.end]] +id = "selfcheck" +severity = "error" +title = "A firmware update was rejected by its self-check" +text = "The box rolled back to its previous firmware because a sensor failed the test." +actions = ["Check the sensor named in the Diagnosis tab (inside the case), then power-cycle."] + +[[guide.end]] +id = "oom" +severity = "error" +title = "Firmware ran out of memory" +text = "Not a hardware fault; the firmware restarts itself." +actions = ["Power-cycle. If it repeats, send the session log to the firmware team."] + +[[guide.end]] +id = "red_unclear" +severity = "warning" +title = "Red LED without a clear cause in the log" +text = "The start-up did not show a failing sensor." +actions = ["Save the session log and send it to the firmware team."] + +# ============================================================= reboot loop +[[guide]] +id = "reboot_loop" +title = "LED keeps changing / box restarts" +summary = "Record several start-ups and find the error just before each restart." +recommend = ["led:CYCLING", "reboot.loop", "sys.crash", "reboot.unexpected", "reboot.brownout", "reboot.watchdog", + "reboot.crash", "reboot.status_watchdog", "reboot.status_oom_watchdog"] +start = "record" + +[[guide.step]] +id = "record" +type = "wait" +text = "The tool restarts the box and records 45 seconds of start-up..." +tool_action = "reset" +timeout_s = 45 +pass_on_timeout = true +on_ok = "cause" + +[[guide.step]] +id = "cause" +type = "route" +routes = { "rtc.init_failed" = "guide:rtc_i2c", "sd.card_fail" = "guide:white", "sd.mount_fail" = "guide:white", "sd.env_open_fail" = "guide:white", "influx.env_missing" = "guide:white", "influx.init_fail" = "guide:white", "sensor.init_failed" = "guide:sensors", "reboot.brownout" = "end:power_supply", "matter.not_commissioned" = "guide:network", "net.dns_fail" = "guide:network", "influx.buffer_fail" = "guide:network" } +default = "end:loop_unclear" + +[[guide.end]] +id = "power_supply" +severity = "error" +title = "Power supply is too weak or unstable" +text = "The chip reset itself because the voltage dropped." +actions = ["Check the power supply, cable and connectors; try another supply."] + +[[guide.end]] +id = "loop_unclear" +severity = "warning" +title = "No clear restart cause in 45 s" +text = "The box did not show a known start-up error during the recording." +actions = ["Keep the tool connected for 5 minutes, then save the session log for the firmware team."] + +# ================================================================== update +[[guide]] +id = "update" +title = "Yellow / orange LED - firmware update" +summary = "Wait for the update and its self-check to finish." +recommend = ["led:YELLOW", "led:ORANGE", "ota.updating", "ota.selfcheck"] +start = "wait" + +[[guide.step]] +id = "wait" +type = "wait" +text = "A firmware update or its self-check is running. Do NOT switch the box off. Waiting up to 10 minutes..." +until = 'Passing selfcheck|OTA update successful' +fail_on = 'OTA update failed|Failing selfcheck' +timeout_s = 600 +on_ok = "end:update_ok" +on_fail = "end:update_failed" +on_timeout = "end:update_slow" + +[[guide.end]] +id = "update_ok" +severity = "ok" +title = "Update finished" +text = "The new firmware was installed or accepted." +actions = ["Wait for the green LED."] + +[[guide.end]] +id = "update_failed" +severity = "error" +title = "Update failed" +text = "The box keeps its current firmware and retries later." +actions = ["Check the Wi-Fi is stable. The Diagnosis tab shows why it failed."] + +[[guide.end]] +id = "update_slow" +severity = "warning" +title = "Update is taking very long" +text = "No end of the update was seen in 10 minutes." +actions = ["Check the Wi-Fi signal; leave the box powered and check again later."] + +# =================================================================== power +[[guide]] +id = "power" +title = "LED off" +summary = "Power switch, heartbeat LED and USB connection." +recommend = ["led:OFF"] +start = "switch" + +[[guide.step]] +id = "switch" +type = "question" +text = "Is the power switch on the outside of the case set to ON?" +options = [ + { label = "It was OFF - I switched it on", next = "boot" }, + { label = "It is ON", next = "heartbeat" }, +] + +[[guide.step]] +id = "boot" +type = "wait" +text = "Waiting for the box to start (up to 30 s)..." +until_milestone = "app_started" +timeout_s = 30 +on_ok = "end:was_off" +on_timeout = "heartbeat" + +[[guide.step]] +id = "heartbeat" +type = "question" +text = "Is the small LED next to the RGB LED blinking?" +options = [ + { label = "Yes, blinking", next = "end:rgb_led_fault" }, + { label = "No", next = "end:no_power" }, +] + +[[guide.end]] +id = "was_off" +severity = "ok" +title = "Box was switched off" +text = "It starts normally after switching on." +actions = ["Run the Quick check again once the LED shows a colour."] + +[[guide.end]] +id = "rgb_led_fault" +severity = "warning" +title = "Firmware runs, but the RGB LED stays dark" +text = "The heartbeat LED shows the firmware is running; the RGB LED or its wiring is faulty." +actions = ["Use the Diagnosis tab instead of the LED; replace the LED board when convenient."] + +[[guide.end]] +id = "no_power" +severity = "error" +title = "Box does not start" +text = "Neither LED is lit: no power or the firmware does not start." +actions = [ + "Check the power supply and cable.", + "If the USB port shows up in this tool, re-flash the official firmware (Flash firmware tab, App only).", +] + +# =================================================================== clock +[[guide]] +id = "clock" +title = "Clock not set" +summary = "Why readings are on hold and when the coin cell matters." +recommend = ["rtc.clock_not_running", "rtc.lost_after_sync", "kota.rtc_stopped", "kota.rtc_not_ticking", "kota.time_invalid"] +start = "internet" + +[[guide.step]] +id = "internet" +type = "question" +text = "The box clock is not set, so readings are on hold. Will the box have Internet at its installation site?" +options = [ + { label = "Yes", next = "end:clock_auto" }, + { label = "No / not yet", next = "end:clock_offline" }, +] + +[[guide.end]] +id = "clock_auto" +severity = "info" +title = "Clock will be set automatically" +text = "On its first Internet connection the box sets its clock and starts recording." +actions = ["If the clock is lost again after every power-off, replace the RTC coin cell (inside the case)."] + +[[guide.end]] +id = "clock_offline" +severity = "error" +title = "Box will not record data without Internet" +text = "Without the Internet the clock cannot be set, and the firmware holds all readings until it is." +actions = [ + "Give the box Internet access (pair it in Matter).", + "Replace the RTC coin cell (inside the case) so the time survives power-offs.", +] + +# ===================================================================== mic +[[guide]] +id = "mic" +title = "Microphone check" +summary = "Switched-off or silent microphone." +recommend = ["mic.disabled", "mic.silent", "mic.read_fail"] +start = "which" + +[[guide.step]] +id = "which" +type = "route" +routes = { "mic.disabled" = "switch", "mic.silent" = "clap", "mic.read_fail" = "end:mic_faulty" } +default = "clap" + +[[guide.step]] +id = "switch" +type = "question" +text = "The box reads its microphone switch as OFF. The switch is on the back of the case, just above the power switch (no need to open the box). Should this box measure noise?" +options = [ + { label = "Yes - I flipped the microphone switch", next = "switch_verify" }, + { label = "No - it is meant to be off", next = "end:mic_off_ok" }, +] + +[[guide.step]] +id = "switch_verify" +type = "wait" +# The firmware re-reads the switch before every reading (~10 s): no restart needed. +text = "Waiting for the next microphone reading (up to 30 s)..." +until = 'MICROPHONE: Read \d+ bytes' +fail_on = 'Microphone is in shutdown state' +timeout_s = 30 +on_ok = "end:mic_on" +on_fail = "flip_back" +on_timeout = "flip_back" + +[[guide.step]] +id = "flip_back" +type = "question" +text = "The box still reads the switch as OFF. Flip the microphone switch to the other position (it may have been ON already)." +options = [ + { label = "Done - I flipped it to the other position", next = "switch_verify2" }, + { label = "Leave it off", next = "end:mic_off_ok" }, +] + +[[guide.step]] +id = "switch_verify2" +type = "wait" +text = "Waiting for the next microphone reading (up to 30 s)..." +until = 'MICROPHONE: Read \d+ bytes' +fail_on = 'Microphone is in shutdown state' +timeout_s = 30 +on_ok = "end:mic_on" +on_fail = "end:mic_switch_faulty" +on_timeout = "end:mic_switch_faulty" + +[[guide.step]] +id = "clap" +type = "wait" +text = "Check the microphone switch (back of the case, above the power switch) is ON. Then clap your hands or speak loudly right next to the box for about 20 seconds..." +until = 'dB peak: (?!-inf)' +timeout_s = 45 +on_ok = "end:mic_ok" +on_timeout = "end:mic_faulty" + +[[guide.end]] +id = "mic_on" +severity = "ok" +title = "Microphone is on and recording" +text = "The firmware reads audio samples again." +actions = ["Leave the microphone switch in this position."] + +[[guide.end]] +id = "mic_off_ok" +severity = "info" +title = "Microphone intentionally off" +text = "No noise data is recorded for this box; everything else works." +actions = [] + +[[guide.end]] +id = "mic_switch_faulty" +severity = "warning" +title = "Microphone switch has no effect" +text = "The box reads the microphone as OFF in both switch positions (enable line GPIO3)." +actions = ["Check the switch and its cable to the main board (inside the case); replace the switch if needed."] + +[[guide.end]] +id = "mic_ok" +severity = "ok" +title = "Microphone works" +text = "It picked up sound - the earlier silence was just a very quiet room." +actions = [] + +[[guide.end]] +id = "mic_faulty" +severity = "error" +title = "Microphone records nothing" +text = "Even loud noise next to the box gives no signal, although the switch is on." +actions = ["Check the microphone board/cable (inside the case); replace the microphone if needed."] diff --git a/kota_tech/diag/rules.toml b/kota_tech/diag/rules.toml index 0e70f1a..8bfb504 100644 --- a/kota_tech/diag/rules.toml +++ b/kota_tech/diag/rules.toml @@ -1,732 +1,732 @@ -# Log signatures for the KOTA sensorbox firmware. -# -# Derived from the firmware source (log strings, Status::set_status calls) and -# checked against real captures (tests/fixtures). Patterns match the cleaned -# line text, not the tag: the deployed v1.0 build logs some messages under -# different tags than the source (e.g. SD mount under INFLUX_BUFFER). -# -# Fields: -# id unique, "." -# pattern Python regex (search). Named groups can be used in `title`. -# Matched against the line with its error ID ("[E-SD-03] ") -# removed. Rules without a pattern ("kota.*") are raised by -# the engine from the structured KOTA: lines (diag/kota.py). -# severity critical | error | warning | info | ignore -# ignore = never shown (benign ESP_LOGE lines, LED-only rules) -# title short technician-facing headline -# meaning what it means, in plain words -# actions what to do, in order (prefer no-unscrewing actions first) -# explained_by list of rule ids; while one of them is active this finding -# is shown as a consequence of it instead of a root cause -# milestone boot milestone reached when the line is seen -# clears finding ids resolved when this line is seen -# led status LED colour this line sets (firmware Status::set_status) -# led_from only change the LED if it currently has this colour -# (Status::set_status_conditional) -# split_by named group; one finding per distinct value (e.g. per sensor) -# min_count finding becomes active only after this many occurrences -# min_uptime_ms ignore occurrences earlier than this uptime - -# ---------------------------------------------------------------- milestones - -[[rule]] -id = "boot.app_started" -pattern = 'app_init: Project name:\s+kota-sensorboard' -severity = "ignore" -milestone = "app_started" -led = "GREEN" # Status::init() -> STATUS_OK - -[[rule]] -id = "boot.sd_mounted" -pattern = 'SD card mounted successfully' -severity = "ignore" -milestone = "sd_mounted" - -[[rule]] -id = "boot.env_loaded" -pattern = 'Read INFLUX_URL from \.env file' -severity = "ignore" -milestone = "env_loaded" - -[[rule]] -id = "boot.matter_started" -pattern = 'ESP-Matter started successfully' -severity = "ignore" -milestone = "matter_started" - -[[rule]] -id = "boot.local_wifi_mode" -pattern = 'SSID/PASSWORD found in \.env' -severity = "ignore" -milestone = "local_wifi_mode" - -[[rule]] -id = "boot.sensors_ready" -# DeviceStatus::init installs the temperature sensor; it is the last init -# step, and any earlier init failure aborts before reaching it. -pattern = 'temperature_sensor: Range \[' -severity = "ignore" -milestone = "sensors_ready" - -[[rule]] -id = "net.wifi_connected" -pattern = 'sta ip: \d|got ip:|WIFI_SERVICE: Got IP address|IPv4 address changed|Interface IP Address changed' -severity = "ignore" -milestone = "wifi_connected" -clears = ["matter.not_commissioned", "wifi.disconnected"] - -[[rule]] -id = "matter.commissioned" -pattern = 'MAIN: Commissioning complete|MAIN: Fabric is committed' -severity = "ignore" -milestone = "matter_commissioned" -clears = ["matter.not_commissioned", "matter.commissioning_failed"] - -[[rule]] -id = "net.time_synced" -pattern = 'SNTP: Time synchronized' -severity = "ignore" -milestone = "time_synced" -clears = ["rtc.clock_not_running", "net.dns_fail"] - -[[rule]] -id = "influx.upload_ok" -pattern = 'INFLUX: HTTP request performed, status code: 2\d\d' -severity = "ignore" -milestone = "data_uploaded" -clears = ["influx.buffer_fail", "net.dns_fail", "net.tls_fail"] - -[[rule]] -id = "influx.write_ok" -pattern = 'Successfully wrote to Influx' -severity = "ignore" -milestone = "data_uploaded" -led = "GREEN" -led_from = "PURPLE" - -[[rule]] -id = "ota.registered" -pattern = 'Device registered successfully|OTA token loaded from NVS' -severity = "ignore" -milestone = "ota_registered" -clears = ["ota.registration_failed"] - -[[rule]] -id = "ota.checked" -pattern = 'OTA_CLIENT: No update available' -severity = "ignore" -milestone = "ota_checked" -clears = ["ota.check_failed"] -led = "GREEN" -led_from = "PURPLE" - -# ----------------------------------------------------------- benign E lines - -[[rule]] -id = "benign.mac" -pattern = 'Device MAC address' -severity = "ignore" - -[[rule]] -id = "benign.attr_cb" -pattern = 'MAIN: Attribute update callback' -severity = "ignore" - -[[rule]] -id = "benign.zcl" -pattern = 'chip\[ZCL\]: WRITE ERR|data_model: Cluster cannot be NULL' -severity = "ignore" - -[[rule]] -id = "benign.wifi_start" -# Harmless Wi-Fi noise seen on every 1.0.16 boot: Matter asks for a connect -# while the station is already connecting, and an unpaired box polls a -# network it does not have yet (matter.not_commissioned covers that case). -pattern = 'wifi:sta is connecting, return error|esp_wifi_connect\(\) failed: ESP_ERR_WIFI_CONN|Failed to get configured network when updating network status' -severity = "ignore" - -[[rule]] -id = "benign.connect_chain" -# Transport-level echo of a failure that a more specific rule reports. -pattern = 'esp-tls: Failed to open new connection|transport_base: Failed to open a new connection|HTTP_CLIENT: Connection failed, sock < 0|INFLUX: Failed to perform HTTP request: 0,|INFLUX_BUFFER: Failed to write chunk to Influx|OTA_CLIENT: Error ESP_ERR_HTTP_CONNECT at' -severity = "ignore" - -# ------------------------------------------------------------------ network - -[[rule]] -id = "matter.not_commissioned" -pattern = 'CHIPoBLE advertising started|MAIN: Commissioning window opened' -severity = "warning" -title = "Box is not connected to Wi-Fi (not commissioned in Matter)" -meaning = "The box is waiting to be added to a Matter network over Bluetooth. Until then it has no Wi-Fi, so it cannot upload data or receive firmware updates." -actions = [ - "Add the box in the customer's Matter app (Apple Home, Google Home, ...) by scanning the QR sticker, or enter the manual code printed under it.", - "Use a 2.4 GHz Wi-Fi network; keep the phone close to the box during pairing.", - "If pairing keeps failing: hold the Matter button 5 s (factory reset) and try again.", -] -milestone = "ble_advertising" - -[[rule]] -id = "matter.commissioning_failed" -pattern = 'Commissioning failed, fail safe timer expired' -severity = "error" -title = "Matter pairing attempt failed" -meaning = "A phone started adding the box to Matter but did not finish in time." -actions = [ - "Retry pairing from the Matter app with the phone next to the box.", - "Check the Wi-Fi is 2.4 GHz and the password entered in the app is correct.", -] - -[[rule]] -id = "net.dns_fail" -pattern = '''couldn't get hostname for :(?P[\w.-]+)''' -severity = "error" -title = "No Internet: server names cannot be resolved" -meaning = "The box could not look up a server name. Either it has no Wi-Fi connection, or the network has no working Internet/DNS." -actions = [ - "Check the site Wi-Fi has Internet (open a website on a phone connected to the same network).", - "If the box was never paired: commission it in Matter first.", - "Check the router does not block new devices (guest isolation, MAC filtering).", -] -explained_by = ["matter.not_commissioned", "wifi.disconnected", "wifi.setup_portal"] - -[[rule]] -id = "net.tls_fail" -pattern = 'mbedtls_ssl_handshake returned|Failed to verify peer certificate|esp-tls-mbedtls: .*[Hh]andshake' -severity = "error" -title = "Secure connection to the server failed" -meaning = "The box reached the server but the encrypted (HTTPS) connection failed. Common causes: wrong clock on the box, or a network that intercepts HTTPS (corporate proxy/firewall)." -actions = [ - "Try another network (e.g. phone hotspot) to rule out a firewall.", - "Check the clock finding below: HTTPS needs the correct date.", -] -explained_by = ["rtc.clock_not_running"] - -[[rule]] -id = "wifi.disconnected" -pattern = 'WiFi disconnected, retrying in' -severity = "error" -min_count = 3 -title = "Box cannot connect to the configured Wi-Fi" -meaning = "The Wi-Fi name/password from the SD card does not work: wrong password, network out of range, or not 2.4 GHz." -actions = [ - "Check SSID and PASSWORD in ENV.TXT (re-provision the SD card with the tool).", - "Make sure the network is 2.4 GHz and in range.", -] - -[[rule]] -id = "wifi.setup_portal" -pattern = 'WiFi provisioning portal ready: connect to "(?P[^"]+)"' -severity = "warning" -title = "Box is in Wi-Fi setup mode (network {ssid})" -meaning = "The box is hosting its own Wi-Fi network for configuration instead of connecting to the site Wi-Fi." -actions = [ - "Connect a phone to {ssid} and open http://sensorbox.local/ to enter the Wi-Fi details,", - "or short-press the Matter button (< 2 s) to leave setup mode.", -] -led = "WHITE" - -# ------------------------------------------------------------------- InfluxDB - -[[rule]] -id = "influx.buffer_fail" -pattern = 'Failed to write chunk with \d+ retries to Influx|MAIN: Failed to flush Influx buffer' -severity = "error" -title = "Measurements cannot be uploaded to InfluxDB" -meaning = "Uploads keep failing, so readings are stored on the SD card and sent later. The LED turns purple (firmware v1.0 also restarts itself after about 2.5 minutes in this state)." -actions = [ - "Fix the network problem first (see the finding this is grouped under).", - "Buffered data is uploaded automatically once the connection works.", -] -explained_by = ["net.dns_fail", "net.tls_fail", "matter.not_commissioned", "wifi.disconnected", "wifi.setup_portal"] - -[[rule]] -id = "influx.purple" -pattern = 'Failed to write chunk with \d+ retries to Influx|Failed to flush buffer before writing|Failed to append new entry to buffer' -severity = "ignore" -led = "PURPLE" - -[[rule]] -id = "influx.auth" -pattern = 'INFLUX: Failed to perform HTTP request: (?P401|403)' -severity = "critical" -title = "InfluxDB rejected the token (HTTP {code})" -meaning = "The box reaches the database but the INFLUX_TOKEN on the SD card is wrong or revoked." -actions = [ - "Re-provision the SD card with the current ENV.TXT (SD card is inside the case).", - "If all boxes show this: the shared token was rotated - update ENV.TXT for everyone.", -] - -[[rule]] -id = "influx.not_found" -pattern = 'INFLUX: Failed to perform HTTP request: (?P404)' -severity = "critical" -title = "InfluxDB bucket or organisation not found (HTTP 404)" -meaning = "INFLUX_URL on the SD card points to a bucket/org that does not exist." -actions = ["Re-provision the SD card with the correct ENV.TXT."] - -[[rule]] -id = "influx.server_error" -pattern = 'INFLUX: Failed to perform HTTP request: (?P5\d\d)' -severity = "warning" -title = "InfluxDB server error (HTTP {code})" -meaning = "The database server has a problem. The box is fine; data is buffered on the SD card." -actions = ["Nothing to fix on the box. Report to the backend team if it persists."] - -[[rule]] -id = "influx.bad_request" -pattern = 'INFLUX: Bad request, check line protocol format' -severity = "warning" -title = "InfluxDB refused some measurements (HTTP 400)" -meaning = "A measurement was malformed and dropped (firmware issue, not a hardware fault)." -actions = ["Save the session log and send it to the firmware team."] - -[[rule]] -id = "influx.env_missing" -split_by = "key" -pattern = 'Failed to read (?PINFLUX_URL|INFLUX_TOKEN) from \.env file' -severity = "critical" -title = "ENV.TXT on the SD card is missing {key}" -meaning = "The box cannot start without the InfluxDB settings; it shows white and restarts." -actions = ["Re-provision the SD card with the tool (SD card is inside the case)."] - -[[rule]] -id = "influx.init_fail" -pattern = 'MAIN: Failed to initialize Influx buffer' -severity = "critical" -title = "Data upload could not be initialised" -meaning = "Start-up stopped because the InfluxDB settings could not be read from the SD card. The box restarts." -actions = ["Re-provision the SD card with the tool (SD card is inside the case)."] -explained_by = ["influx.env_missing", "sd.mount_fail", "sd.card_fail", "sd.env_open_fail"] -led = "WHITE" - -# ----------------------------------------------------------------- SD card - -[[rule]] -id = "sd.card_fail" -pattern = 'Failed to initialize the card \((?P[^)]+)\)' -severity = "critical" -title = "SD card not detected ({err})" -meaning = "The SD card does not respond: missing, not seated, or damaged. The box shows white and restarts." -actions = [ - "Open the case and re-seat the SD card.", - "Try a freshly provisioned card.", -] -led = "WHITE" - -[[rule]] -id = "sd.mount_fail" -pattern = 'Failed to mount filesystem' -severity = "critical" -title = "SD card is not formatted correctly" -meaning = "The card responds but has no readable FAT32 file system." -actions = ["Re-provision the card with the tool (FAT32, MBR) - SD card is inside the case."] -led = "WHITE" - -[[rule]] -id = "sd.env_open_fail" -pattern = 'Failed to open \.env file for reading' -severity = "critical" -title = "ENV.TXT not found on the SD card" -meaning = "The card is readable but the configuration file is missing." -actions = ["Re-provision the card with the tool (SD card is inside the case)."] - -[[rule]] -id = "sd.write_fail" -pattern = 'Failed to (create buffer file|open buffer file for appending)' -severity = "error" -title = "Cannot write to the SD card" -meaning = "Offline data cannot be stored: the card is full, write-protected or failing." -actions = ["Replace / re-provision the SD card (inside the case)."] - -# --------------------------------------------------------------- RTC / I2C - -[[rule]] -id = "rtc.init_failed" -pattern = 'MAIN: Failed to initialize RTC: (?P\S+)' -severity = "critical" -title = "Clock chip (RTC) or I2C bus not responding ({err})" -meaning = "The RTC is the first chip the firmware talks to over I2C, so this also appears when the I2C cable between the boards is loose or broken. LED blue, the box restarts." -actions = [ - "Open the case and check the I2C cable/connector between the boards.", - "If the cable is fine: the RTC module is faulty.", -] -explained_by = ["kota.i2c_stuck"] -led = "BLUE" - -[[rule]] -id = "rtc.clock_not_running" -pattern = 'RTC is uninitialized, using current time as timestamp' -severity = "warning" -min_count = 5 -title = "Clock is not set - sensor readings are on hold" -meaning = "The clock chip answers but its clock is not running (it reads 1 Jan 2000; typical after its coin cell was removed or is empty), and there is no Internet time yet. Readings are held until the time is set, so no data is recorded." -actions = [ - "Give the box Internet access: the clock is set automatically on the first connection.", - "If this comes back after every power-off: replace the RTC coin cell (inside the case).", -] -explained_by = ["kota.rtc_stopped"] - -[[rule]] -id = "i2c.errors" -pattern = 'I2C (read|write) failed: (?P\S+)|Failed to add I2C device' -severity = "error" -min_count = 2 -title = "I2C bus communication errors" -meaning = "Sensors are not answering reliably on the I2C bus: loose connector, damaged cable, or a failing sensor." -actions = ["Open the case and check the I2C cable/connectors between the boards."] -explained_by = ["kota.i2c_stuck", "kota.sensor_missing"] - -# ------------------------------------------------------------------ sensors - -[[rule]] -id = "sensor.init_failed" -split_by = "sensor" -pattern = 'MAIN: Failed to initialize (?PBME688|SEN66|AS7341|Microphone|Flicker sensor|DeviceStatus): (?P.+)' -severity = "critical" -title = "{sensor} failed to start" -meaning = "Start-up stopped at the {sensor} sensor ({err}). LED red, the box restarts." -actions = [ - "Open the case and check the {sensor} connector/cable.", - "If several sensors fail in turn: check the shared I2C cable.", -] -explained_by = ["kota.i2c_stuck", "kota.sensor_missing"] -led = "RED" - -[[rule]] -id = "sensor.selfcheck_failed" -split_by = "sensor" -pattern = '(?PBME688|SEN66|AS7341|Microphone|Flicker sensor) self check failed' -severity = "error" -title = "{sensor} failed the post-update self-check" -meaning = "After a firmware update the {sensor} did not work; the box rolls back to the previous firmware." -actions = ["Check the {sensor} connector (inside the case), then power-cycle."] - -[[rule]] -id = "sen66.not_ready" -pattern = 'SEN66: (Failed while awaiting sensor ready|Self-check failed: Sensor not ready|Failed to read measurement)' -severity = "error" -title = "Air-quality sensor (SEN66) not delivering data" -meaning = "The SEN66 answers but never reports a measurement." -actions = ["Check the SEN66 cable (inside the case); the sensor may be faulty."] - -[[rule]] -id = "bme688.read_fail" -pattern = 'Failed to get (BME688 )?data from BME688|No new data available from BME688' -severity = "warning" -min_count = 3 -title = "Temperature/humidity sensor (BME688) read errors" -meaning = "The BME688 repeatedly fails to return a reading." -actions = ["Check the BME688 connection (inside the case)."] - -[[rule]] -id = "as7341.read_fail" -pattern = 'Failed to read (spectral|basic counts) data' -severity = "warning" -min_count = 2 -title = "Light sensor (AS7341) read errors" -meaning = "The AS7341 repeatedly fails to return a reading." -actions = ["Check the AS7341 connection (inside the case)."] - -[[rule]] -id = "mic.silent" -pattern = 'MICROPHONE: .*dB peak: -inf' -severity = "warning" -min_count = 2 -title = "Microphone records pure silence" -meaning = "Every audio sample is zero. A real room is never perfectly silent, so the microphone or its wiring is likely faulty." -actions = [ - "Make some noise near the box and watch whether this finding keeps growing.", - "If it does: check the microphone board/cable (inside the case).", -] - -[[rule]] -id = "mic.read_fail" -pattern = 'Failed to read samples' -severity = "error" -title = "Microphone read error" -meaning = "The audio interface returned an error." -actions = ["Check the microphone board/cable (inside the case)."] - -[[rule]] -id = "gather.stalled" -pattern = 'tasks still running, waiting' -severity = "warning" -min_uptime_ms = 180000 -min_count = 5 -title = "Sensor readings are stuck" -meaning = "A sensor has not finished its reading for minutes (the first 2 minutes of warm-up are normal)." -actions = ["See the grouped cause, or note which sensor stopped logging and check its cable."] -explained_by = ["rtc.clock_not_running", "sen66.not_ready"] - -# ---------------------------------------------------------- OTA / firmware - -[[rule]] -id = "ota.registration_failed" -pattern = 'Device registration failed' -severity = "warning" -title = "Box is not registered for firmware updates" -meaning = "The box could not reach the update server (bin.kota-app.com) to register, so it will not receive new firmware." -actions = ["Fix the network problem; registration is retried automatically every few seconds."] -explained_by = ["net.dns_fail", "net.tls_fail", "matter.not_commissioned", "wifi.disconnected", "wifi.setup_portal"] - -[[rule]] -id = "ota.check_failed" -pattern = 'Failed to check for updates|OTA Update check failed with status code: (?P\d+)' -severity = "error" -title = "Firmware update check failed" -meaning = "The update server could not be reached or refused the request. LED purple." -actions = ["Fix the network problem; the check is retried every 15 minutes."] -explained_by = ["net.dns_fail", "net.tls_fail", "matter.not_commissioned", "wifi.disconnected"] -led = "PURPLE" - -[[rule]] -id = "ota.unauthorized" -pattern = 'Unauthorized, clearing saved token' -severity = "warning" -title = "Update server did not accept the box - it will re-register" -meaning = "The saved update token was rejected; the box registers again automatically." -actions = ["Nothing to do unless it repeats. Then report the box MAC to the backend team."] - -[[rule]] -id = "ota.updating" -pattern = 'New update available, starting OTA update' -severity = "info" -title = "Firmware update is downloading - do not switch off" -meaning = "The box found new firmware and is installing it (LED yellow). It restarts when done." -actions = ["Wait. Do not switch the power off."] -milestone = "ota_update_started" -led = "YELLOW" - -[[rule]] -id = "ota.update_failed" -pattern = 'OTA update failed: (?P\S+)' -severity = "error" -title = "Firmware update failed ({err})" -meaning = "The download or installation was interrupted; the box keeps its current firmware and retries later." -actions = ["Check the network is stable; the update is retried automatically."] - -[[rule]] -id = "ota.update_ok" -pattern = 'OTA update successful, rebooting' -severity = "info" -title = "Firmware update installed - restarting" -meaning = "New firmware was installed. After the restart it runs a self-check (LED orange, ~2.5 min)." -actions = ["Wait for green."] -milestone = "ota_update_done" - -[[rule]] -id = "ota.selfcheck" -pattern = 'Device is in selfcheck mode' -severity = "info" -title = "Post-update self-check running (~2.5 min)" -meaning = "The new firmware is testing all sensors before it is accepted (LED orange)." -actions = ["Wait. Do not switch off."] -led = "ORANGE" - -[[rule]] -id = "ota.selfcheck_failed" -pattern = 'Failing selfcheck: (?P.+)' -severity = "error" -title = "Update self-check failed: {reason}" -meaning = "The new firmware rejected itself; the box rolls back to the previous version." -actions = ["Check the named sensor (inside the case), then power-cycle."] -led = "RED" - -[[rule]] -id = "ota.selfcheck_passed" -pattern = 'Passing selfcheck' -severity = "ignore" -milestone = "ota_update_accepted" -led = "GREEN" - -[[rule]] -id = "ota.reset_during_update" -pattern = 'Device was reset during update' -severity = "warning" -title = "Box was switched off during a firmware update" -meaning = "An update was interrupted; the box reports it and retries." -actions = ["Leave the box powered while the LED is yellow or orange."] - -# --------------------------------------------------------------- system - -[[rule]] -id = "sys.out_of_resources" -pattern = 'Failed to create (task for sensor|gather task|main gather task)' -severity = "error" -title = "Firmware ran out of memory" -meaning = "The firmware could not start an internal task (LED red); it restarts itself after 30 s." -actions = ["Power-cycle. If it repeats, save the session log for the firmware team."] -led = "RED" - -[[rule]] -id = "sys.factory_reset" -pattern = 'Factory reset button pressed|Factory resetting device' -severity = "warning" -title = "Factory reset triggered with the Matter button" -meaning = "The box forgets its Matter network and must be paired again with the QR sticker." -actions = ["Re-commission the box in the Matter app."] -led = "WHITE" - -[[rule]] -id = "mic.disabled" -pattern = 'MICROPHONE: Microphone is in shutdown state' -severity = "info" -title = "Microphone is switched off - no noise measurements" -meaning = "The microphone switch on the back of the case (just above the power switch) is OFF, so noise levels are not recorded." -actions = ["If this box should measure noise: flip the microphone switch (back of the case, above the power switch). No restart needed - it is picked up within about 10 s."] - -[[rule]] -id = "gather.empty_reading" -pattern = 'SENSOR_GATHERER: Failed to get line protocol from sensor' -severity = "warning" -min_count = 2 -title = "A sensor returned no data" -meaning = "One of the sensors skipped its reading; the preceding log lines name the sensor." -actions = ["Check the log lines just before this message for the sensor name."] -explained_by = ["mic.disabled", "mic.read_fail", "bme688.read_fail", "as7341.read_fail", "rtc.clock_not_running"] - -# ------------------------------------------------ structured KOTA: lines -# Firmware 1.0.16+ reports its state as "KOTA: key=value ..." lines. -# These rules have no pattern: the engine raises them from the parsed fields -# (see diag/kota.py for the line formats). Named values in {braces} come -# from the line's fields. - -[[rule]] -id = "kota.i2c_stuck" -severity = "critical" -title = "I2C bus stuck (SDA={sda}, SCL={scl})" -meaning = "A data line of the sensor bus is held low before any sensor is used: a shorted or broken sensor cable, or a faulty sensor. LED blue, 2 pulses." -actions = [ - "Open the case and check the I2C cable between the boards.", - "Unplug the sensors one by one to find the one holding the bus.", -] - -[[rule]] -id = "kota.sensor_missing" -split_by = "sensor" -severity = "error" -title = "{sensor} does not answer on the I2C bus" -meaning = "The start-up scan did not find the {sensor} at address {addr}. The other sensors answer, so the bus itself works: the {sensor} cable, connector or the sensor is faulty." -actions = [ - "Open the case and re-seat the {sensor} cable/connector.", - "If it is still missing after a restart: replace the {sensor}.", -] -explained_by = ["kota.i2c_stuck"] - -[[rule]] -id = "kota.rtc_read_failed" -severity = "critical" -title = "Clock chip (RTC) cannot be read ({err})" -meaning = "The DS1307 clock does not answer. It is on the shared I2C bus, so a loose cable also causes this." -actions = [ - "Open the case and check the I2C cable/connector between the boards.", - "If the cable is fine: replace the RTC module.", -] -explained_by = ["kota.i2c_stuck", "kota.sensor_missing"] - -[[rule]] -id = "kota.rtc_stopped" -severity = "warning" -title = "Clock chip was stopped at start-up (time {time})" -meaning = "The RTC answers but its clock was not running - typical after its coin cell was removed or is empty. It is set again from the Internet once the box is online; until then readings are held." -actions = [ - "Give the box Internet access: the clock is set automatically.", - "If this comes back after every power-off: replace the RTC coin cell (inside the case).", -] - -[[rule]] -id = "kota.rtc_not_ticking" -severity = "error" -title = "Clock chip does not keep time" -meaning = "The clock was set from the Internet, but 2 s later it had not advanced correctly (running={running}, delta={delta}). The RTC module or its crystal is faulty." -actions = ["Replace the RTC module (inside the case)."] - -[[rule]] -id = "kota.sd_unmounted" -severity = "critical" -title = "SD card not mounted ({err})" -meaning = "The firmware cannot use the SD card: missing, not seated, not FAT32 or damaged. Without it the box cannot read ENV.TXT. LED white, fast blinking." -actions = [ - "Open the case and re-seat the SD card.", - "Re-provision the card with the tool (FAT32, MBR) or try a freshly provisioned card.", -] - -[[rule]] -id = "kota.sd_not_fat32" -severity = "warning" -title = "SD card file system is {fs}, not FAT32" -meaning = "The card works, but the provisioning standard is FAT32." -actions = ["Re-provision the card with the tool when convenient."] - -[[rule]] -id = "kota.sd_full" -severity = "warning" -title = "SD card is almost full ({free} free)" -meaning = "Offline readings may soon not be stored." -actions = ["Check the buffered readings upload (Internet access), or replace the card."] - -[[rule]] -id = "kota.buffer_backlog" -severity = "warning" -title = "{lines} readings are waiting on the SD card to be uploaded" -meaning = "The box stored readings while it could not upload them. They are sent automatically once the connection works." -actions = ["Fix the connection problem if there is one; watch the health line: the buffer should shrink."] -explained_by = ["kota.wifi_down", "matter.not_commissioned", "status.conn", "status.conn.1", "net.dns_fail", "influx.auth"] - -[[rule]] -id = "kota.wifi_down" -severity = "error" -min_count = 2 -title = "Box has no Wi-Fi connection" -meaning = "The periodic health report says Wi-Fi is disconnected, so the box cannot upload data or receive updates." -actions = [ - "Check the site Wi-Fi is on, 2.4 GHz and in range.", - "Matter boxes: check the box is still paired in the Matter app. Wi-Fi-from-SD boxes: check SSID/PASSWORD in ENV.TXT.", -] -explained_by = ["matter.not_commissioned", "wifi.setup_portal", "wifi.disconnected"] - -[[rule]] -id = "kota.weak_wifi" -severity = "warning" -title = "Weak Wi-Fi signal ({rssi} dBm)" -meaning = "The connection works but the signal is weak; uploads may fail intermittently. Below about -75 dBm is unreliable." -actions = ["Move the box or the access point closer, or add a repeater."] - -[[rule]] -id = "kota.time_invalid" -severity = "warning" -title = "Box has no valid time - readings are on hold" -meaning = "Readings are only stored with a valid timestamp. The time comes from the RTC or from the Internet." -actions = [ - "Give the box Internet access so it can set its clock.", - "If it is online and this persists: check the RTC findings.", -] -explained_by = ["kota.wifi_down", "matter.not_commissioned", "kota.rtc_not_ticking", "kota.rtc_read_failed", "kota.rtc_stopped", "rtc.clock_not_running", "net.dns_fail"] - -[[rule]] -id = "kota.no_upload" -severity = "error" -title = "Connected to Wi-Fi, but no data uploaded yet (5+ min)" -meaning = "The box has Wi-Fi but no upload to InfluxDB has succeeded yet." -actions = [ - "Check the network has Internet (open a website on a phone on the same Wi-Fi).", - "Check INFLUX_URL / INFLUX_TOKEN in ENV.TXT (re-provision the SD card).", -] -explained_by = ["kota.time_invalid", "influx.auth", "influx.not_found", "status.conn.1", "net.dns_fail", "net.tls_fail", "influx.server_error"] - -[[rule]] -id = "kota.low_heap" -severity = "warning" -title = "Firmware memory is low ({heap} bytes free)" -meaning = "Internal memory is running low; the firmware may fail to start tasks and restart." -actions = ["Save the session log for the firmware team."] - -[[rule]] -id = "kota.ota_unregistered" -severity = "warning" -min_count = 3 -title = "Online, but not registered for firmware updates" -meaning = "The box has Wi-Fi but is still not registered with the update server, so it will not receive new firmware." -actions = ["Check the network allows HTTPS to bin.kota-app.com; registration is retried automatically."] -explained_by = ["ota.registration_failed", "net.dns_fail", "net.tls_fail"] +# Log signatures for the KOTA sensorbox firmware. +# +# Derived from the firmware source (log strings, Status::set_status calls) and +# checked against real captures (tests/fixtures). Patterns match the cleaned +# line text, not the tag: the deployed v1.0 build logs some messages under +# different tags than the source (e.g. SD mount under INFLUX_BUFFER). +# +# Fields: +# id unique, "." +# pattern Python regex (search). Named groups can be used in `title`. +# Matched against the line with its error ID ("[E-SD-03] ") +# removed. Rules without a pattern ("kota.*") are raised by +# the engine from the structured KOTA: lines (diag/kota.py). +# severity critical | error | warning | info | ignore +# ignore = never shown (benign ESP_LOGE lines, LED-only rules) +# title short technician-facing headline +# meaning what it means, in plain words +# actions what to do, in order (prefer no-unscrewing actions first) +# explained_by list of rule ids; while one of them is active this finding +# is shown as a consequence of it instead of a root cause +# milestone boot milestone reached when the line is seen +# clears finding ids resolved when this line is seen +# led status LED colour this line sets (firmware Status::set_status) +# led_from only change the LED if it currently has this colour +# (Status::set_status_conditional) +# split_by named group; one finding per distinct value (e.g. per sensor) +# min_count finding becomes active only after this many occurrences +# min_uptime_ms ignore occurrences earlier than this uptime + +# ---------------------------------------------------------------- milestones + +[[rule]] +id = "boot.app_started" +pattern = 'app_init: Project name:\s+kota-sensorboard' +severity = "ignore" +milestone = "app_started" +led = "GREEN" # Status::init() -> STATUS_OK + +[[rule]] +id = "boot.sd_mounted" +pattern = 'SD card mounted successfully' +severity = "ignore" +milestone = "sd_mounted" + +[[rule]] +id = "boot.env_loaded" +pattern = 'Read INFLUX_URL from \.env file' +severity = "ignore" +milestone = "env_loaded" + +[[rule]] +id = "boot.matter_started" +pattern = 'ESP-Matter started successfully' +severity = "ignore" +milestone = "matter_started" + +[[rule]] +id = "boot.local_wifi_mode" +pattern = 'SSID/PASSWORD found in \.env' +severity = "ignore" +milestone = "local_wifi_mode" + +[[rule]] +id = "boot.sensors_ready" +# DeviceStatus::init installs the temperature sensor; it is the last init +# step, and any earlier init failure aborts before reaching it. +pattern = 'temperature_sensor: Range \[' +severity = "ignore" +milestone = "sensors_ready" + +[[rule]] +id = "net.wifi_connected" +pattern = 'sta ip: \d|got ip:|WIFI_SERVICE: Got IP address|IPv4 address changed|Interface IP Address changed' +severity = "ignore" +milestone = "wifi_connected" +clears = ["matter.not_commissioned", "wifi.disconnected"] + +[[rule]] +id = "matter.commissioned" +pattern = 'MAIN: Commissioning complete|MAIN: Fabric is committed' +severity = "ignore" +milestone = "matter_commissioned" +clears = ["matter.not_commissioned", "matter.commissioning_failed"] + +[[rule]] +id = "net.time_synced" +pattern = 'SNTP: Time synchronized' +severity = "ignore" +milestone = "time_synced" +clears = ["rtc.clock_not_running", "net.dns_fail"] + +[[rule]] +id = "influx.upload_ok" +pattern = 'INFLUX: HTTP request performed, status code: 2\d\d' +severity = "ignore" +milestone = "data_uploaded" +clears = ["influx.buffer_fail", "net.dns_fail", "net.tls_fail"] + +[[rule]] +id = "influx.write_ok" +pattern = 'Successfully wrote to Influx' +severity = "ignore" +milestone = "data_uploaded" +led = "GREEN" +led_from = "PURPLE" + +[[rule]] +id = "ota.registered" +pattern = 'Device registered successfully|OTA token loaded from NVS' +severity = "ignore" +milestone = "ota_registered" +clears = ["ota.registration_failed"] + +[[rule]] +id = "ota.checked" +pattern = 'OTA_CLIENT: No update available' +severity = "ignore" +milestone = "ota_checked" +clears = ["ota.check_failed"] +led = "GREEN" +led_from = "PURPLE" + +# ----------------------------------------------------------- benign E lines + +[[rule]] +id = "benign.mac" +pattern = 'Device MAC address' +severity = "ignore" + +[[rule]] +id = "benign.attr_cb" +pattern = 'MAIN: Attribute update callback' +severity = "ignore" + +[[rule]] +id = "benign.zcl" +pattern = 'chip\[ZCL\]: WRITE ERR|data_model: Cluster cannot be NULL' +severity = "ignore" + +[[rule]] +id = "benign.wifi_start" +# Harmless Wi-Fi noise seen on every 1.0.16 boot: Matter asks for a connect +# while the station is already connecting, and an unpaired box polls a +# network it does not have yet (matter.not_commissioned covers that case). +pattern = 'wifi:sta is connecting, return error|esp_wifi_connect\(\) failed: ESP_ERR_WIFI_CONN|Failed to get configured network when updating network status' +severity = "ignore" + +[[rule]] +id = "benign.connect_chain" +# Transport-level echo of a failure that a more specific rule reports. +pattern = 'esp-tls: Failed to open new connection|transport_base: Failed to open a new connection|HTTP_CLIENT: Connection failed, sock < 0|INFLUX: Failed to perform HTTP request: 0,|INFLUX_BUFFER: Failed to write chunk to Influx|OTA_CLIENT: Error ESP_ERR_HTTP_CONNECT at' +severity = "ignore" + +# ------------------------------------------------------------------ network + +[[rule]] +id = "matter.not_commissioned" +pattern = 'CHIPoBLE advertising started|MAIN: Commissioning window opened' +severity = "warning" +title = "Box is not connected to Wi-Fi (not commissioned in Matter)" +meaning = "The box is waiting to be added to a Matter network over Bluetooth. Until then it has no Wi-Fi, so it cannot upload data or receive firmware updates." +actions = [ + "Add the box in the customer's Matter app (Apple Home, Google Home, ...) by scanning the QR sticker, or enter the manual code printed under it.", + "Use a 2.4 GHz Wi-Fi network; keep the phone close to the box during pairing.", + "If pairing keeps failing: hold the Matter button 5 s (factory reset) and try again.", +] +milestone = "ble_advertising" + +[[rule]] +id = "matter.commissioning_failed" +pattern = 'Commissioning failed, fail safe timer expired' +severity = "error" +title = "Matter pairing attempt failed" +meaning = "A phone started adding the box to Matter but did not finish in time." +actions = [ + "Retry pairing from the Matter app with the phone next to the box.", + "Check the Wi-Fi is 2.4 GHz and the password entered in the app is correct.", +] + +[[rule]] +id = "net.dns_fail" +pattern = '''couldn't get hostname for :(?P[\w.-]+)''' +severity = "error" +title = "No Internet: server names cannot be resolved" +meaning = "The box could not look up a server name. Either it has no Wi-Fi connection, or the network has no working Internet/DNS." +actions = [ + "Check the site Wi-Fi has Internet (open a website on a phone connected to the same network).", + "If the box was never paired: commission it in Matter first.", + "Check the router does not block new devices (guest isolation, MAC filtering).", +] +explained_by = ["matter.not_commissioned", "wifi.disconnected", "wifi.setup_portal"] + +[[rule]] +id = "net.tls_fail" +pattern = 'mbedtls_ssl_handshake returned|Failed to verify peer certificate|esp-tls-mbedtls: .*[Hh]andshake' +severity = "error" +title = "Secure connection to the server failed" +meaning = "The box reached the server but the encrypted (HTTPS) connection failed. Common causes: wrong clock on the box, or a network that intercepts HTTPS (corporate proxy/firewall)." +actions = [ + "Try another network (e.g. phone hotspot) to rule out a firewall.", + "Check the clock finding below: HTTPS needs the correct date.", +] +explained_by = ["rtc.clock_not_running"] + +[[rule]] +id = "wifi.disconnected" +pattern = 'WiFi disconnected, retrying in' +severity = "error" +min_count = 3 +title = "Box cannot connect to the configured Wi-Fi" +meaning = "The Wi-Fi name/password from the SD card does not work: wrong password, network out of range, or not 2.4 GHz." +actions = [ + "Check SSID and PASSWORD in ENV.TXT (re-provision the SD card with the tool).", + "Make sure the network is 2.4 GHz and in range.", +] + +[[rule]] +id = "wifi.setup_portal" +pattern = 'WiFi provisioning portal ready: connect to "(?P[^"]+)"' +severity = "warning" +title = "Box is in Wi-Fi setup mode (network {ssid})" +meaning = "The box is hosting its own Wi-Fi network for configuration instead of connecting to the site Wi-Fi." +actions = [ + "Connect a phone to {ssid} and open http://sensorbox.local/ to enter the Wi-Fi details,", + "or short-press the Matter button (< 2 s) to leave setup mode.", +] +led = "WHITE" + +# ------------------------------------------------------------------- InfluxDB + +[[rule]] +id = "influx.buffer_fail" +pattern = 'Failed to write chunk with \d+ retries to Influx|MAIN: Failed to flush Influx buffer' +severity = "error" +title = "Measurements cannot be uploaded to InfluxDB" +meaning = "Uploads keep failing, so readings are stored on the SD card and sent later. The LED turns purple (firmware v1.0 also restarts itself after about 2.5 minutes in this state)." +actions = [ + "Fix the network problem first (see the finding this is grouped under).", + "Buffered data is uploaded automatically once the connection works.", +] +explained_by = ["net.dns_fail", "net.tls_fail", "matter.not_commissioned", "wifi.disconnected", "wifi.setup_portal"] + +[[rule]] +id = "influx.purple" +pattern = 'Failed to write chunk with \d+ retries to Influx|Failed to flush buffer before writing|Failed to append new entry to buffer' +severity = "ignore" +led = "PURPLE" + +[[rule]] +id = "influx.auth" +pattern = 'INFLUX: Failed to perform HTTP request: (?P401|403)' +severity = "critical" +title = "InfluxDB rejected the token (HTTP {code})" +meaning = "The box reaches the database but the INFLUX_TOKEN on the SD card is wrong or revoked." +actions = [ + "Re-provision the SD card with the current ENV.TXT (SD card is inside the case).", + "If all boxes show this: the shared token was rotated - update ENV.TXT for everyone.", +] + +[[rule]] +id = "influx.not_found" +pattern = 'INFLUX: Failed to perform HTTP request: (?P404)' +severity = "critical" +title = "InfluxDB bucket or organisation not found (HTTP 404)" +meaning = "INFLUX_URL on the SD card points to a bucket/org that does not exist." +actions = ["Re-provision the SD card with the correct ENV.TXT."] + +[[rule]] +id = "influx.server_error" +pattern = 'INFLUX: Failed to perform HTTP request: (?P5\d\d)' +severity = "warning" +title = "InfluxDB server error (HTTP {code})" +meaning = "The database server has a problem. The box is fine; data is buffered on the SD card." +actions = ["Nothing to fix on the box. Report to the backend team if it persists."] + +[[rule]] +id = "influx.bad_request" +pattern = 'INFLUX: Bad request, check line protocol format' +severity = "warning" +title = "InfluxDB refused some measurements (HTTP 400)" +meaning = "A measurement was malformed and dropped (firmware issue, not a hardware fault)." +actions = ["Save the session log and send it to the firmware team."] + +[[rule]] +id = "influx.env_missing" +split_by = "key" +pattern = 'Failed to read (?PINFLUX_URL|INFLUX_TOKEN) from \.env file' +severity = "critical" +title = "ENV.TXT on the SD card is missing {key}" +meaning = "The box cannot start without the InfluxDB settings; it shows white and restarts." +actions = ["Re-provision the SD card with the tool (SD card is inside the case)."] + +[[rule]] +id = "influx.init_fail" +pattern = 'MAIN: Failed to initialize Influx buffer' +severity = "critical" +title = "Data upload could not be initialised" +meaning = "Start-up stopped because the InfluxDB settings could not be read from the SD card. The box restarts." +actions = ["Re-provision the SD card with the tool (SD card is inside the case)."] +explained_by = ["influx.env_missing", "sd.mount_fail", "sd.card_fail", "sd.env_open_fail"] +led = "WHITE" + +# ----------------------------------------------------------------- SD card + +[[rule]] +id = "sd.card_fail" +pattern = 'Failed to initialize the card \((?P[^)]+)\)' +severity = "critical" +title = "SD card not detected ({err})" +meaning = "The SD card does not respond: missing, not seated, or damaged. The box shows white and restarts." +actions = [ + "Open the case and re-seat the SD card.", + "Try a freshly provisioned card.", +] +led = "WHITE" + +[[rule]] +id = "sd.mount_fail" +pattern = 'Failed to mount filesystem' +severity = "critical" +title = "SD card is not formatted correctly" +meaning = "The card responds but has no readable FAT32 file system." +actions = ["Re-provision the card with the tool (FAT32, MBR) - SD card is inside the case."] +led = "WHITE" + +[[rule]] +id = "sd.env_open_fail" +pattern = 'Failed to open \.env file for reading' +severity = "critical" +title = "ENV.TXT not found on the SD card" +meaning = "The card is readable but the configuration file is missing." +actions = ["Re-provision the card with the tool (SD card is inside the case)."] + +[[rule]] +id = "sd.write_fail" +pattern = 'Failed to (create buffer file|open buffer file for appending)' +severity = "error" +title = "Cannot write to the SD card" +meaning = "Offline data cannot be stored: the card is full, write-protected or failing." +actions = ["Replace / re-provision the SD card (inside the case)."] + +# --------------------------------------------------------------- RTC / I2C + +[[rule]] +id = "rtc.init_failed" +pattern = 'MAIN: Failed to initialize RTC: (?P\S+)' +severity = "critical" +title = "Clock chip (RTC) or I2C bus not responding ({err})" +meaning = "The RTC is the first chip the firmware talks to over I2C, so this also appears when the I2C cable between the boards is loose or broken. LED blue, the box restarts." +actions = [ + "Open the case and check the I2C cable/connector between the boards.", + "If the cable is fine: the RTC module is faulty.", +] +explained_by = ["kota.i2c_stuck"] +led = "BLUE" + +[[rule]] +id = "rtc.clock_not_running" +pattern = 'RTC is uninitialized, using current time as timestamp' +severity = "warning" +min_count = 5 +title = "Clock is not set - sensor readings are on hold" +meaning = "The clock chip answers but its clock is not running (it reads 1 Jan 2000; typical after its coin cell was removed or is empty), and there is no Internet time yet. Readings are held until the time is set, so no data is recorded." +actions = [ + "Give the box Internet access: the clock is set automatically on the first connection.", + "If this comes back after every power-off: replace the RTC coin cell (inside the case).", +] +explained_by = ["kota.rtc_stopped"] + +[[rule]] +id = "i2c.errors" +pattern = 'I2C (read|write) failed: (?P\S+)|Failed to add I2C device' +severity = "error" +min_count = 2 +title = "I2C bus communication errors" +meaning = "Sensors are not answering reliably on the I2C bus: loose connector, damaged cable, or a failing sensor." +actions = ["Open the case and check the I2C cable/connectors between the boards."] +explained_by = ["kota.i2c_stuck", "kota.sensor_missing"] + +# ------------------------------------------------------------------ sensors + +[[rule]] +id = "sensor.init_failed" +split_by = "sensor" +pattern = 'MAIN: Failed to initialize (?PBME688|SEN66|AS7341|Microphone|Flicker sensor|DeviceStatus): (?P.+)' +severity = "critical" +title = "{sensor} failed to start" +meaning = "Start-up stopped at the {sensor} sensor ({err}). LED red, the box restarts." +actions = [ + "Open the case and check the {sensor} connector/cable.", + "If several sensors fail in turn: check the shared I2C cable.", +] +explained_by = ["kota.i2c_stuck", "kota.sensor_missing"] +led = "RED" + +[[rule]] +id = "sensor.selfcheck_failed" +split_by = "sensor" +pattern = '(?PBME688|SEN66|AS7341|Microphone|Flicker sensor) self check failed' +severity = "error" +title = "{sensor} failed the post-update self-check" +meaning = "After a firmware update the {sensor} did not work; the box rolls back to the previous firmware." +actions = ["Check the {sensor} connector (inside the case), then power-cycle."] + +[[rule]] +id = "sen66.not_ready" +pattern = 'SEN66: (Failed while awaiting sensor ready|Self-check failed: Sensor not ready|Failed to read measurement)' +severity = "error" +title = "Air-quality sensor (SEN66) not delivering data" +meaning = "The SEN66 answers but never reports a measurement." +actions = ["Check the SEN66 cable (inside the case); the sensor may be faulty."] + +[[rule]] +id = "bme688.read_fail" +pattern = 'Failed to get (BME688 )?data from BME688|No new data available from BME688' +severity = "warning" +min_count = 3 +title = "Temperature/humidity sensor (BME688) read errors" +meaning = "The BME688 repeatedly fails to return a reading." +actions = ["Check the BME688 connection (inside the case)."] + +[[rule]] +id = "as7341.read_fail" +pattern = 'Failed to read (spectral|basic counts) data' +severity = "warning" +min_count = 2 +title = "Light sensor (AS7341) read errors" +meaning = "The AS7341 repeatedly fails to return a reading." +actions = ["Check the AS7341 connection (inside the case)."] + +[[rule]] +id = "mic.silent" +pattern = 'MICROPHONE: .*dB peak: -inf' +severity = "warning" +min_count = 2 +title = "Microphone records pure silence" +meaning = "Every audio sample is zero. A real room is never perfectly silent, so the microphone or its wiring is likely faulty." +actions = [ + "Make some noise near the box and watch whether this finding keeps growing.", + "If it does: check the microphone board/cable (inside the case).", +] + +[[rule]] +id = "mic.read_fail" +pattern = 'Failed to read samples' +severity = "error" +title = "Microphone read error" +meaning = "The audio interface returned an error." +actions = ["Check the microphone board/cable (inside the case)."] + +[[rule]] +id = "gather.stalled" +pattern = 'tasks still running, waiting' +severity = "warning" +min_uptime_ms = 180000 +min_count = 5 +title = "Sensor readings are stuck" +meaning = "A sensor has not finished its reading for minutes (the first 2 minutes of warm-up are normal)." +actions = ["See the grouped cause, or note which sensor stopped logging and check its cable."] +explained_by = ["rtc.clock_not_running", "sen66.not_ready"] + +# ---------------------------------------------------------- OTA / firmware + +[[rule]] +id = "ota.registration_failed" +pattern = 'Device registration failed' +severity = "warning" +title = "Box is not registered for firmware updates" +meaning = "The box could not reach the update server (bin.kota-app.com) to register, so it will not receive new firmware." +actions = ["Fix the network problem; registration is retried automatically every few seconds."] +explained_by = ["net.dns_fail", "net.tls_fail", "matter.not_commissioned", "wifi.disconnected", "wifi.setup_portal"] + +[[rule]] +id = "ota.check_failed" +pattern = 'Failed to check for updates|OTA Update check failed with status code: (?P\d+)' +severity = "error" +title = "Firmware update check failed" +meaning = "The update server could not be reached or refused the request. LED purple." +actions = ["Fix the network problem; the check is retried every 15 minutes."] +explained_by = ["net.dns_fail", "net.tls_fail", "matter.not_commissioned", "wifi.disconnected"] +led = "PURPLE" + +[[rule]] +id = "ota.unauthorized" +pattern = 'Unauthorized, clearing saved token' +severity = "warning" +title = "Update server did not accept the box - it will re-register" +meaning = "The saved update token was rejected; the box registers again automatically." +actions = ["Nothing to do unless it repeats. Then report the box MAC to the backend team."] + +[[rule]] +id = "ota.updating" +pattern = 'New update available, starting OTA update' +severity = "info" +title = "Firmware update is downloading - do not switch off" +meaning = "The box found new firmware and is installing it (LED yellow). It restarts when done." +actions = ["Wait. Do not switch the power off."] +milestone = "ota_update_started" +led = "YELLOW" + +[[rule]] +id = "ota.update_failed" +pattern = 'OTA update failed: (?P\S+)' +severity = "error" +title = "Firmware update failed ({err})" +meaning = "The download or installation was interrupted; the box keeps its current firmware and retries later." +actions = ["Check the network is stable; the update is retried automatically."] + +[[rule]] +id = "ota.update_ok" +pattern = 'OTA update successful, rebooting' +severity = "info" +title = "Firmware update installed - restarting" +meaning = "New firmware was installed. After the restart it runs a self-check (LED orange, ~2.5 min)." +actions = ["Wait for green."] +milestone = "ota_update_done" + +[[rule]] +id = "ota.selfcheck" +pattern = 'Device is in selfcheck mode' +severity = "info" +title = "Post-update self-check running (~2.5 min)" +meaning = "The new firmware is testing all sensors before it is accepted (LED orange)." +actions = ["Wait. Do not switch off."] +led = "ORANGE" + +[[rule]] +id = "ota.selfcheck_failed" +pattern = 'Failing selfcheck: (?P.+)' +severity = "error" +title = "Update self-check failed: {reason}" +meaning = "The new firmware rejected itself; the box rolls back to the previous version." +actions = ["Check the named sensor (inside the case), then power-cycle."] +led = "RED" + +[[rule]] +id = "ota.selfcheck_passed" +pattern = 'Passing selfcheck' +severity = "ignore" +milestone = "ota_update_accepted" +led = "GREEN" + +[[rule]] +id = "ota.reset_during_update" +pattern = 'Device was reset during update' +severity = "warning" +title = "Box was switched off during a firmware update" +meaning = "An update was interrupted; the box reports it and retries." +actions = ["Leave the box powered while the LED is yellow or orange."] + +# --------------------------------------------------------------- system + +[[rule]] +id = "sys.out_of_resources" +pattern = 'Failed to create (task for sensor|gather task|main gather task)' +severity = "error" +title = "Firmware ran out of memory" +meaning = "The firmware could not start an internal task (LED red); it restarts itself after 30 s." +actions = ["Power-cycle. If it repeats, save the session log for the firmware team."] +led = "RED" + +[[rule]] +id = "sys.factory_reset" +pattern = 'Factory reset button pressed|Factory resetting device' +severity = "warning" +title = "Factory reset triggered with the Matter button" +meaning = "The box forgets its Matter network and must be paired again with the QR sticker." +actions = ["Re-commission the box in the Matter app."] +led = "WHITE" + +[[rule]] +id = "mic.disabled" +pattern = 'MICROPHONE: Microphone is in shutdown state' +severity = "info" +title = "Microphone is switched off - no noise measurements" +meaning = "The microphone switch on the back of the case (just above the power switch) is OFF, so noise levels are not recorded." +actions = ["If this box should measure noise: flip the microphone switch (back of the case, above the power switch). No restart needed - it is picked up within about 10 s."] + +[[rule]] +id = "gather.empty_reading" +pattern = 'SENSOR_GATHERER: Failed to get line protocol from sensor' +severity = "warning" +min_count = 2 +title = "A sensor returned no data" +meaning = "One of the sensors skipped its reading; the preceding log lines name the sensor." +actions = ["Check the log lines just before this message for the sensor name."] +explained_by = ["mic.disabled", "mic.read_fail", "bme688.read_fail", "as7341.read_fail", "rtc.clock_not_running"] + +# ------------------------------------------------ structured KOTA: lines +# Firmware 1.0.16+ reports its state as "KOTA: key=value ..." lines. +# These rules have no pattern: the engine raises them from the parsed fields +# (see diag/kota.py for the line formats). Named values in {braces} come +# from the line's fields. + +[[rule]] +id = "kota.i2c_stuck" +severity = "critical" +title = "I2C bus stuck (SDA={sda}, SCL={scl})" +meaning = "A data line of the sensor bus is held low before any sensor is used: a shorted or broken sensor cable, or a faulty sensor. LED blue, 2 pulses." +actions = [ + "Open the case and check the I2C cable between the boards.", + "Unplug the sensors one by one to find the one holding the bus.", +] + +[[rule]] +id = "kota.sensor_missing" +split_by = "sensor" +severity = "error" +title = "{sensor} does not answer on the I2C bus" +meaning = "The start-up scan did not find the {sensor} at address {addr}. The other sensors answer, so the bus itself works: the {sensor} cable, connector or the sensor is faulty." +actions = [ + "Open the case and re-seat the {sensor} cable/connector.", + "If it is still missing after a restart: replace the {sensor}.", +] +explained_by = ["kota.i2c_stuck"] + +[[rule]] +id = "kota.rtc_read_failed" +severity = "critical" +title = "Clock chip (RTC) cannot be read ({err})" +meaning = "The DS1307 clock does not answer. It is on the shared I2C bus, so a loose cable also causes this." +actions = [ + "Open the case and check the I2C cable/connector between the boards.", + "If the cable is fine: replace the RTC module.", +] +explained_by = ["kota.i2c_stuck", "kota.sensor_missing"] + +[[rule]] +id = "kota.rtc_stopped" +severity = "warning" +title = "Clock chip was stopped at start-up (time {time})" +meaning = "The RTC answers but its clock was not running - typical after its coin cell was removed or is empty. It is set again from the Internet once the box is online; until then readings are held." +actions = [ + "Give the box Internet access: the clock is set automatically.", + "If this comes back after every power-off: replace the RTC coin cell (inside the case).", +] + +[[rule]] +id = "kota.rtc_not_ticking" +severity = "error" +title = "Clock chip does not keep time" +meaning = "The clock was set from the Internet, but 2 s later it had not advanced correctly (running={running}, delta={delta}). The RTC module or its crystal is faulty." +actions = ["Replace the RTC module (inside the case)."] + +[[rule]] +id = "kota.sd_unmounted" +severity = "critical" +title = "SD card not mounted ({err})" +meaning = "The firmware cannot use the SD card: missing, not seated, not FAT32 or damaged. Without it the box cannot read ENV.TXT. LED white, fast blinking." +actions = [ + "Open the case and re-seat the SD card.", + "Re-provision the card with the tool (FAT32, MBR) or try a freshly provisioned card.", +] + +[[rule]] +id = "kota.sd_not_fat32" +severity = "warning" +title = "SD card file system is {fs}, not FAT32" +meaning = "The card works, but the provisioning standard is FAT32." +actions = ["Re-provision the card with the tool when convenient."] + +[[rule]] +id = "kota.sd_full" +severity = "warning" +title = "SD card is almost full ({free} free)" +meaning = "Offline readings may soon not be stored." +actions = ["Check the buffered readings upload (Internet access), or replace the card."] + +[[rule]] +id = "kota.buffer_backlog" +severity = "warning" +title = "{lines} readings are waiting on the SD card to be uploaded" +meaning = "The box stored readings while it could not upload them. They are sent automatically once the connection works." +actions = ["Fix the connection problem if there is one; watch the health line: the buffer should shrink."] +explained_by = ["kota.wifi_down", "matter.not_commissioned", "status.conn", "status.conn.1", "net.dns_fail", "influx.auth"] + +[[rule]] +id = "kota.wifi_down" +severity = "error" +min_count = 2 +title = "Box has no Wi-Fi connection" +meaning = "The periodic health report says Wi-Fi is disconnected, so the box cannot upload data or receive updates." +actions = [ + "Check the site Wi-Fi is on, 2.4 GHz and in range.", + "Matter boxes: check the box is still paired in the Matter app. Wi-Fi-from-SD boxes: check SSID/PASSWORD in ENV.TXT.", +] +explained_by = ["matter.not_commissioned", "wifi.setup_portal", "wifi.disconnected"] + +[[rule]] +id = "kota.weak_wifi" +severity = "warning" +title = "Weak Wi-Fi signal ({rssi} dBm)" +meaning = "The connection works but the signal is weak; uploads may fail intermittently. Below about -75 dBm is unreliable." +actions = ["Move the box or the access point closer, or add a repeater."] + +[[rule]] +id = "kota.time_invalid" +severity = "warning" +title = "Box has no valid time - readings are on hold" +meaning = "Readings are only stored with a valid timestamp. The time comes from the RTC or from the Internet." +actions = [ + "Give the box Internet access so it can set its clock.", + "If it is online and this persists: check the RTC findings.", +] +explained_by = ["kota.wifi_down", "matter.not_commissioned", "kota.rtc_not_ticking", "kota.rtc_read_failed", "kota.rtc_stopped", "rtc.clock_not_running", "net.dns_fail"] + +[[rule]] +id = "kota.no_upload" +severity = "error" +title = "Connected to Wi-Fi, but no data uploaded yet (5+ min)" +meaning = "The box has Wi-Fi but no upload to InfluxDB has succeeded yet." +actions = [ + "Check the network has Internet (open a website on a phone on the same Wi-Fi).", + "Check INFLUX_URL / INFLUX_TOKEN in ENV.TXT (re-provision the SD card).", +] +explained_by = ["kota.time_invalid", "influx.auth", "influx.not_found", "status.conn.1", "net.dns_fail", "net.tls_fail", "influx.server_error"] + +[[rule]] +id = "kota.low_heap" +severity = "warning" +title = "Firmware memory is low ({heap} bytes free)" +meaning = "Internal memory is running low; the firmware may fail to start tasks and restart." +actions = ["Save the session log for the firmware team."] + +[[rule]] +id = "kota.ota_unregistered" +severity = "warning" +min_count = 3 +title = "Online, but not registered for firmware updates" +meaning = "The box has Wi-Fi but is still not registered with the update server, so it will not receive new firmware." +actions = ["Check the network allows HTTPS to bin.kota-app.com; registration is retried automatically."] +explained_by = ["ota.registration_failed", "net.dns_fail", "net.tls_fail"] diff --git a/kota_tech/flash/image.py b/kota_tech/flash/image.py index aa3ab5e..befc2f9 100644 --- a/kota_tech/flash/image.py +++ b/kota_tech/flash/image.py @@ -1,260 +1,260 @@ -"""Firmware images: validation, integrity, layout and the firmware folder. - -The tool flashes ONLY the .bin files in its own firmware/ folder (next to -kota_tech/). There is no import and no per-user library: to offer a build, -the firmware team puts the file into that folder. Images are listed by the -compile date stored in the firmware itself (ESP-IDF app descriptor), newest -first, so the file name does not matter. - -Two kinds: - * merged - bootloader at 0x0 + partition table + app (merged-binary.bin); - App-only or Full flashing; - * app - the application binary only (build/kota-sensorboard.bin); - App-only flashing, laid out like a merged image in the folder. - -Every image is checked structurally (ESP32-S3, kota-sensorboard app -descriptor, segment checksum and the appended SHA-256) so a truncated or -corrupted file is refused before anything is written. -""" - -from __future__ import annotations - -import datetime as dt -import hashlib -import struct -from dataclasses import dataclass -from pathlib import Path - -FIRMWARE_DIR = Path(__file__).resolve().parents[2] / "firmware" - -PT_OFFSET = 0x8000 -PT_SIZE = 0xC00 -ESP32S3_CHIP_ID = 9 -IMAGE_MAGIC = 0xE9 -APP_DESC_MAGIC = 0xABCD5432 -FLASH_SIZES = {0: "1MB", 1: "2MB", 2: "4MB", 3: "8MB", 4: "16MB", 5: "32MB"} -_MONTHS = {m: i for i, m in enumerate( - ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 1)} - - -class ImageError(ValueError): - pass - - -@dataclass(frozen=True) -class Partition: - label: str - type: int - subtype: int - offset: int - size: int - - -@dataclass(frozen=True) -class AppInfo: - project: str - version: str - built: str # "Sep 28 2026 15:07:57" as in the app descriptor - idf: str - elf_sha: str # hex - - @property - def built_at(self) -> dt.datetime | None: - try: - mon, day, year, hms = self.built.split() - h, m, s = (int(x) for x in hms.split(":")) - return dt.datetime(int(year), _MONTHS[mon], int(day), h, m, s) - except (ValueError, KeyError): - return None - - @property - def compiled(self) -> str: - """'2026-09-28 15:07' (ISO-like, sortable) or the raw descriptor text.""" - t = self.built_at - return t.strftime("%Y-%m-%d %H:%M") if t else self.built - - -@dataclass(frozen=True) -class FirmwareImage: - path: Path - kind: str # "merged" | "app" - sha256: str - flash_size: str - partitions: tuple[Partition, ...] - partition_table: bytes # expected table on the box - app_offset: int # where the app starts inside the file - app_length: int # exact app image length (from its structure) - app: AppInfo - - @property - def name(self) -> str: - return self.path.name - - @property - def data(self) -> bytes: - return self.path.read_bytes() - - def partition(self, label: str) -> Partition: - for p in self.partitions: - if p.label == label: - return p - raise ImageError(f"partition {label!r} not in image") - - def app_bytes(self) -> bytes: - return self.data[self.app_offset:self.app_offset + self.app_length] - - def blank_otadata(self) -> bytes: - """Erased otadata: the bootloader then boots ota_0.""" - return b"\xff" * self.partition("otadata").size - - def label(self) -> str: - """Choice text: compile date first (that is what the technician picks by).""" - kind = "" if self.kind == "merged" else ", app only" - return f"Compiled {self.app.compiled} - v{self.app.version} [{self.app.elf_sha[:8]}]{kind} ({self.name})" - - -# ---------------------------------------------------------------- parsing - -def parse_partition_table(raw: bytes) -> tuple[Partition, ...]: - parts = [] - for i in range(0, len(raw) - 31, 32): - entry = raw[i:i + 32] - magic = entry[:2] - if magic == b"\xaa\x50": - _, ptype, sub, off, size = struct.unpack_from(" int: - """Validate an ESP app image at `offset`; return its exact length. - - Checks the segment checksum and, when the header says so, the appended - SHA-256 - the same checks the bootloader performs before booting it. - """ - if len(data) < offset + 24 or data[offset] != IMAGE_MAGIC: - raise ImageError(f"no firmware image at 0x{offset:x}") - segments = data[offset + 1] - chip_id = struct.unpack_from(" len(data): - raise ImageError("image is truncated (segment header missing)") - _, seg_len = struct.unpack_from(" len(data): - raise ImageError("image is truncated (segment data missing)") - for b in data[pos:pos + seg_len]: - checksum ^= b - pos += seg_len - pos += 15 - ((pos - offset) % 16) # pad so the checksum ends a 16-byte block - if pos >= len(data) or data[pos] != checksum: - raise ImageError("image checksum is wrong - the file is corrupted") - pos += 1 - if hash_appended: - if pos + 32 > len(data): - raise ImageError("image is truncated (SHA-256 missing)") - if hashlib.sha256(data[offset:pos]).digest() != data[pos:pos + 32]: - raise ImageError("image SHA-256 does not match its content - the file is corrupted") - pos += 32 - return pos - offset - - -def _app_info(data: bytes, offset: int) -> AppInfo: - d = data[offset + 0x20:offset + 0x20 + 256] - if len(d) < 256 or struct.unpack_from(" FirmwareImage: - """Validate a merged or app-only image. App-only images need a `reference` - (a merged image) that provides the flash layout.""" - data = path.read_bytes() - if len(data) >= PT_OFFSET + PT_SIZE and data[0] == IMAGE_MAGIC and data[PT_OFFSET:PT_OFFSET + 2] == b"\xaa\x50": - app_image_length(data, 0) # bootloader must be intact - table = data[PT_OFFSET:PT_OFFSET + PT_SIZE] - parts = parse_partition_table(table) - labels = {p.label for p in parts} - for need in ("nvs", "otadata", "ota_0"): - if need not in labels: - raise ImageError(f"partition table has no {need!r} partition") - ota0 = next(p for p in parts if p.label == "ota_0") - kind, app_off, flash_size = "merged", ota0.offset, FLASH_SIZES.get(data[3] >> 4, "?") - elif data[:1] == bytes([IMAGE_MAGIC]): - if reference is None: - raise ImageError("app-only file, but there is no complete (merged) image in the folder " - "to take the flash layout from") - kind, app_off = "app", 0 - parts, table, flash_size = reference.partitions, reference.partition_table, reference.flash_size - ota0 = next(p for p in parts if p.label == "ota_0") - else: - raise ImageError("not an ESP32 firmware file") - length = app_image_length(data, app_off) - if length > ota0.size: - raise ImageError(f"firmware ({length} bytes) does not fit the ota_0 partition ({ota0.size} bytes)") - app = _app_info(data, app_off) - return FirmwareImage(path, kind, hashlib.sha256(data).hexdigest(), - flash_size, parts, table, app_off, length, app) - - -# ------------------------------------------------------------- catalogue - -def folder_images(folder: Path = FIRMWARE_DIR) -> tuple[list[FirmwareImage], list[str]]: - """(valid images newest-compiled first, problems) for the .bin files in `folder`. - - A broken or foreign file is reported, never offered. The same build - under two file names is offered once. - """ - if not folder.is_dir(): - return [], [f"firmware folder not found: {folder}"] - files = sorted(p for p in folder.iterdir() if p.is_file() and p.suffix.lower() == ".bin") - merged: list[FirmwareImage] = [] - app_only: list[Path] = [] - problems: list[str] = [] - for f in files: - try: - merged.append(inspect_image(f)) - except ImageError as exc: - if "app-only" in str(exc): - app_only.append(f) - else: - problems.append(f"{f.name}: {exc}") - except OSError as exc: - problems.append(f"{f.name}: cannot read ({exc})") - merged.sort(key=_newest_first) - reference = merged[0] if merged else None - images = list(merged) - for f in app_only: - try: - images.append(inspect_image(f, reference)) - except (ImageError, OSError) as exc: - problems.append(f"{f.name}: {exc}") - images.sort(key=_newest_first) - unique: dict[str, FirmwareImage] = {} - for img in images: - if img.sha256 in unique: - problems.append(f"{img.name}: same file as {unique[img.sha256].name} (listed once)") - else: - unique[img.sha256] = img - return list(unique.values()), problems - - -def _newest_first(img: FirmwareImage) -> tuple: - # Unknown compile dates last; merged before app-only for the same date. - t = img.app.built_at - return (t is None, -t.timestamp() if t else 0.0, img.kind != "merged", img.name) +"""Firmware images: validation, integrity, layout and the firmware folder. + +The tool flashes ONLY the .bin files in its own firmware/ folder (next to +kota_tech/). There is no import and no per-user library: to offer a build, +the firmware team puts the file into that folder. Images are listed by the +compile date stored in the firmware itself (ESP-IDF app descriptor), newest +first, so the file name does not matter. + +Two kinds: + * merged - bootloader at 0x0 + partition table + app (merged-binary.bin); + App-only or Full flashing; + * app - the application binary only (build/kota-sensorboard.bin); + App-only flashing, laid out like a merged image in the folder. + +Every image is checked structurally (ESP32-S3, kota-sensorboard app +descriptor, segment checksum and the appended SHA-256) so a truncated or +corrupted file is refused before anything is written. +""" + +from __future__ import annotations + +import datetime as dt +import hashlib +import struct +from dataclasses import dataclass +from pathlib import Path + +FIRMWARE_DIR = Path(__file__).resolve().parents[2] / "firmware" + +PT_OFFSET = 0x8000 +PT_SIZE = 0xC00 +ESP32S3_CHIP_ID = 9 +IMAGE_MAGIC = 0xE9 +APP_DESC_MAGIC = 0xABCD5432 +FLASH_SIZES = {0: "1MB", 1: "2MB", 2: "4MB", 3: "8MB", 4: "16MB", 5: "32MB"} +_MONTHS = {m: i for i, m in enumerate( + ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 1)} + + +class ImageError(ValueError): + pass + + +@dataclass(frozen=True) +class Partition: + label: str + type: int + subtype: int + offset: int + size: int + + +@dataclass(frozen=True) +class AppInfo: + project: str + version: str + built: str # "Sep 28 2026 15:07:57" as in the app descriptor + idf: str + elf_sha: str # hex + + @property + def built_at(self) -> dt.datetime | None: + try: + mon, day, year, hms = self.built.split() + h, m, s = (int(x) for x in hms.split(":")) + return dt.datetime(int(year), _MONTHS[mon], int(day), h, m, s) + except (ValueError, KeyError): + return None + + @property + def compiled(self) -> str: + """'2026-09-28 15:07' (ISO-like, sortable) or the raw descriptor text.""" + t = self.built_at + return t.strftime("%Y-%m-%d %H:%M") if t else self.built + + +@dataclass(frozen=True) +class FirmwareImage: + path: Path + kind: str # "merged" | "app" + sha256: str + flash_size: str + partitions: tuple[Partition, ...] + partition_table: bytes # expected table on the box + app_offset: int # where the app starts inside the file + app_length: int # exact app image length (from its structure) + app: AppInfo + + @property + def name(self) -> str: + return self.path.name + + @property + def data(self) -> bytes: + return self.path.read_bytes() + + def partition(self, label: str) -> Partition: + for p in self.partitions: + if p.label == label: + return p + raise ImageError(f"partition {label!r} not in image") + + def app_bytes(self) -> bytes: + return self.data[self.app_offset:self.app_offset + self.app_length] + + def blank_otadata(self) -> bytes: + """Erased otadata: the bootloader then boots ota_0.""" + return b"\xff" * self.partition("otadata").size + + def label(self) -> str: + """Choice text: compile date first (that is what the technician picks by).""" + kind = "" if self.kind == "merged" else ", app only" + return f"Compiled {self.app.compiled} - v{self.app.version} [{self.app.elf_sha[:8]}]{kind} ({self.name})" + + +# ---------------------------------------------------------------- parsing + +def parse_partition_table(raw: bytes) -> tuple[Partition, ...]: + parts = [] + for i in range(0, len(raw) - 31, 32): + entry = raw[i:i + 32] + magic = entry[:2] + if magic == b"\xaa\x50": + _, ptype, sub, off, size = struct.unpack_from(" int: + """Validate an ESP app image at `offset`; return its exact length. + + Checks the segment checksum and, when the header says so, the appended + SHA-256 - the same checks the bootloader performs before booting it. + """ + if len(data) < offset + 24 or data[offset] != IMAGE_MAGIC: + raise ImageError(f"no firmware image at 0x{offset:x}") + segments = data[offset + 1] + chip_id = struct.unpack_from(" len(data): + raise ImageError("image is truncated (segment header missing)") + _, seg_len = struct.unpack_from(" len(data): + raise ImageError("image is truncated (segment data missing)") + for b in data[pos:pos + seg_len]: + checksum ^= b + pos += seg_len + pos += 15 - ((pos - offset) % 16) # pad so the checksum ends a 16-byte block + if pos >= len(data) or data[pos] != checksum: + raise ImageError("image checksum is wrong - the file is corrupted") + pos += 1 + if hash_appended: + if pos + 32 > len(data): + raise ImageError("image is truncated (SHA-256 missing)") + if hashlib.sha256(data[offset:pos]).digest() != data[pos:pos + 32]: + raise ImageError("image SHA-256 does not match its content - the file is corrupted") + pos += 32 + return pos - offset + + +def _app_info(data: bytes, offset: int) -> AppInfo: + d = data[offset + 0x20:offset + 0x20 + 256] + if len(d) < 256 or struct.unpack_from(" FirmwareImage: + """Validate a merged or app-only image. App-only images need a `reference` + (a merged image) that provides the flash layout.""" + data = path.read_bytes() + if len(data) >= PT_OFFSET + PT_SIZE and data[0] == IMAGE_MAGIC and data[PT_OFFSET:PT_OFFSET + 2] == b"\xaa\x50": + app_image_length(data, 0) # bootloader must be intact + table = data[PT_OFFSET:PT_OFFSET + PT_SIZE] + parts = parse_partition_table(table) + labels = {p.label for p in parts} + for need in ("nvs", "otadata", "ota_0"): + if need not in labels: + raise ImageError(f"partition table has no {need!r} partition") + ota0 = next(p for p in parts if p.label == "ota_0") + kind, app_off, flash_size = "merged", ota0.offset, FLASH_SIZES.get(data[3] >> 4, "?") + elif data[:1] == bytes([IMAGE_MAGIC]): + if reference is None: + raise ImageError("app-only file, but there is no complete (merged) image in the folder " + "to take the flash layout from") + kind, app_off = "app", 0 + parts, table, flash_size = reference.partitions, reference.partition_table, reference.flash_size + ota0 = next(p for p in parts if p.label == "ota_0") + else: + raise ImageError("not an ESP32 firmware file") + length = app_image_length(data, app_off) + if length > ota0.size: + raise ImageError(f"firmware ({length} bytes) does not fit the ota_0 partition ({ota0.size} bytes)") + app = _app_info(data, app_off) + return FirmwareImage(path, kind, hashlib.sha256(data).hexdigest(), + flash_size, parts, table, app_off, length, app) + + +# ------------------------------------------------------------- catalogue + +def folder_images(folder: Path = FIRMWARE_DIR) -> tuple[list[FirmwareImage], list[str]]: + """(valid images newest-compiled first, problems) for the .bin files in `folder`. + + A broken or foreign file is reported, never offered. The same build + under two file names is offered once. + """ + if not folder.is_dir(): + return [], [f"firmware folder not found: {folder}"] + files = sorted(p for p in folder.iterdir() if p.is_file() and p.suffix.lower() == ".bin") + merged: list[FirmwareImage] = [] + app_only: list[Path] = [] + problems: list[str] = [] + for f in files: + try: + merged.append(inspect_image(f)) + except ImageError as exc: + if "app-only" in str(exc): + app_only.append(f) + else: + problems.append(f"{f.name}: {exc}") + except OSError as exc: + problems.append(f"{f.name}: cannot read ({exc})") + merged.sort(key=_newest_first) + reference = merged[0] if merged else None + images = list(merged) + for f in app_only: + try: + images.append(inspect_image(f, reference)) + except (ImageError, OSError) as exc: + problems.append(f"{f.name}: {exc}") + images.sort(key=_newest_first) + unique: dict[str, FirmwareImage] = {} + for img in images: + if img.sha256 in unique: + problems.append(f"{img.name}: same file as {unique[img.sha256].name} (listed once)") + else: + unique[img.sha256] = img + return list(unique.values()), problems + + +def _newest_first(img: FirmwareImage) -> tuple: + # Unknown compile dates last; merged before app-only for the same date. + t = img.app.built_at + return (t is None, -t.timestamp() if t else 0.0, img.kind != "merged", img.name) diff --git a/kota_tech/matter/payload.py b/kota_tech/matter/payload.py index e95c3ed..2326165 100644 --- a/kota_tech/matter/payload.py +++ b/kota_tech/matter/payload.py @@ -1,113 +1,113 @@ -"""Matter onboarding payloads: the "MT:..." QR string and the 11-digit manual code. - -Implements the Matter spec (5.1.3 QR payload, 5.1.4 manual pairing code): -bit fields packed LSB-first, Base38 in 3-byte chunks, Verhoeff check digit. - -The sensorbox firmware uses the Matter SDK example commissionable data -(CONFIG_EXAMPLE_COMMISSIONABLE_DATA_PROVIDER): every v1.0 box advertises -VID 0xFFF1 / PID 0x8000 / discriminator 3840 with passcode 20202021, which is -exactly the sticker (QR "MT:Y.K9042C00KA0648G00", manual 3497-011-2332). -VID/PID/discriminator are taken from the box's own log when available; the -passcode is never logged, so the known default is used. -""" - -from __future__ import annotations - -import re -from dataclasses import dataclass - -BASE38 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-." -RENDEZVOUS_BLE = 1 << 1 -DEFAULT_PASSCODE = 20202021 # Matter SDK test passcode used by the v1.0 firmware -DEFAULT = dict(vendor_id=0xFFF1, product_id=0x8000, discriminator=3840) - -# chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 -_ADVERTISE = re.compile(r"vendorID=(\d+) productID=(\d+) discriminator=(\d+)") - - -@dataclass(frozen=True) -class SetupPayload: - vendor_id: int = DEFAULT["vendor_id"] - product_id: int = DEFAULT["product_id"] - discriminator: int = DEFAULT["discriminator"] - passcode: int = DEFAULT_PASSCODE - rendezvous: int = RENDEZVOUS_BLE - flow: int = 0 # standard commissioning flow - version: int = 0 - from_log: bool = False # VID/PID/discriminator read from this box's log - qr: str | None = None # exact payload reported by the box ("KOTA: matter qr=MT:...") - manual: str | None = None # exact 11-digit manual code reported by the box - - @property - def from_box(self) -> bool: - return self.qr is not None - - def qr_text(self) -> str: - if self.qr: - return self.qr - fields = [(self.version, 3), (self.vendor_id, 16), (self.product_id, 16), (self.flow, 2), - (self.rendezvous, 8), (self.discriminator, 12), (self.passcode, 27), (0, 4)] - value, shift = 0, 0 - for v, bits in fields: - if not 0 <= v < (1 << bits): - raise ValueError(f"field value {v} does not fit in {bits} bits") - value |= v << shift - shift += bits - return "MT:" + _base38(value.to_bytes(shift // 8, "little")) - - def manual_code(self) -> str: - if self.manual: - return self.manual - short = self.discriminator >> 8 # upper 4 bits - chunk1 = short >> 2 # vid_pid_present = 0 - chunk2 = ((short & 0x3) << 14) | (self.passcode & 0x3FFF) - chunk3 = self.passcode >> 14 - digits = f"{chunk1}{chunk2:05d}{chunk3:04d}" - return digits + str(verhoeff_check(digits)) - - def manual_code_pretty(self) -> str: - c = self.manual_code() - return f"{c[:4]}-{c[4:7]}-{c[7:]}" - - -def _base38(data: bytes) -> str: - out = [] - for i in range(0, len(data), 3): - chunk = data[i:i + 3] - value = int.from_bytes(chunk, "little") - for _ in range({3: 5, 2: 4, 1: 2}[len(chunk)]): - value, rem = divmod(value, 38) - out.append(BASE38[rem]) - return "".join(out) - - -_D = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 0, 6, 7, 8, 9, 5], [2, 3, 4, 0, 1, 7, 8, 9, 5, 6], - [3, 4, 0, 1, 2, 8, 9, 5, 6, 7], [4, 0, 1, 2, 3, 9, 5, 6, 7, 8], [5, 9, 8, 7, 6, 0, 4, 3, 2, 1], - [6, 5, 9, 8, 7, 1, 0, 4, 3, 2], [7, 6, 5, 9, 8, 2, 1, 0, 4, 3], [8, 7, 6, 5, 9, 3, 2, 1, 0, 4], - [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]] -_P = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 5, 7, 6, 2, 8, 3, 0, 9, 4], [5, 8, 0, 3, 7, 9, 6, 1, 4, 2], - [8, 9, 1, 6, 0, 4, 3, 5, 2, 7], [9, 4, 5, 3, 1, 2, 6, 8, 7, 0], [4, 2, 8, 6, 5, 7, 3, 9, 0, 1], - [2, 7, 9, 3, 8, 0, 6, 4, 1, 5], [7, 0, 4, 6, 9, 1, 3, 2, 5, 8]] -_INV = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9] - - -def verhoeff_check(digits: str) -> int: - c = 0 - for i, d in enumerate(reversed(digits)): - c = _D[c][_P[(i + 1) % 8][int(d)]] - return _INV[c] - - -def payload_from_log(lines: list[str], qr: str | None = None, manual: str | None = None) -> SetupPayload: - """Firmware 1.0.16+ reports its exact QR payload and manual code - ("KOTA: matter ... qr=MT:... manual=..."); older firmware: use the - advertised VID/PID/discriminator if seen; defaults otherwise.""" - base = SetupPayload() - for text in reversed(lines): - if m := _ADVERTISE.search(text): - base = SetupPayload(int(m.group(1)), int(m.group(2)), int(m.group(3)), from_log=True) - break - if qr: - return SetupPayload(base.vendor_id, base.product_id, base.discriminator, from_log=True, - qr=qr, manual=manual if manual and manual.isdigit() else None) - return base +"""Matter onboarding payloads: the "MT:..." QR string and the 11-digit manual code. + +Implements the Matter spec (5.1.3 QR payload, 5.1.4 manual pairing code): +bit fields packed LSB-first, Base38 in 3-byte chunks, Verhoeff check digit. + +The sensorbox firmware uses the Matter SDK example commissionable data +(CONFIG_EXAMPLE_COMMISSIONABLE_DATA_PROVIDER): every v1.0 box advertises +VID 0xFFF1 / PID 0x8000 / discriminator 3840 with passcode 20202021, which is +exactly the sticker (QR "MT:Y.K9042C00KA0648G00", manual 3497-011-2332). +VID/PID/discriminator are taken from the box's own log when available; the +passcode is never logged, so the known default is used. +""" + +from __future__ import annotations + +import re +from dataclasses import dataclass + +BASE38 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-." +RENDEZVOUS_BLE = 1 << 1 +DEFAULT_PASSCODE = 20202021 # Matter SDK test passcode used by the v1.0 firmware +DEFAULT = dict(vendor_id=0xFFF1, product_id=0x8000, discriminator=3840) + +# chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 +_ADVERTISE = re.compile(r"vendorID=(\d+) productID=(\d+) discriminator=(\d+)") + + +@dataclass(frozen=True) +class SetupPayload: + vendor_id: int = DEFAULT["vendor_id"] + product_id: int = DEFAULT["product_id"] + discriminator: int = DEFAULT["discriminator"] + passcode: int = DEFAULT_PASSCODE + rendezvous: int = RENDEZVOUS_BLE + flow: int = 0 # standard commissioning flow + version: int = 0 + from_log: bool = False # VID/PID/discriminator read from this box's log + qr: str | None = None # exact payload reported by the box ("KOTA: matter qr=MT:...") + manual: str | None = None # exact 11-digit manual code reported by the box + + @property + def from_box(self) -> bool: + return self.qr is not None + + def qr_text(self) -> str: + if self.qr: + return self.qr + fields = [(self.version, 3), (self.vendor_id, 16), (self.product_id, 16), (self.flow, 2), + (self.rendezvous, 8), (self.discriminator, 12), (self.passcode, 27), (0, 4)] + value, shift = 0, 0 + for v, bits in fields: + if not 0 <= v < (1 << bits): + raise ValueError(f"field value {v} does not fit in {bits} bits") + value |= v << shift + shift += bits + return "MT:" + _base38(value.to_bytes(shift // 8, "little")) + + def manual_code(self) -> str: + if self.manual: + return self.manual + short = self.discriminator >> 8 # upper 4 bits + chunk1 = short >> 2 # vid_pid_present = 0 + chunk2 = ((short & 0x3) << 14) | (self.passcode & 0x3FFF) + chunk3 = self.passcode >> 14 + digits = f"{chunk1}{chunk2:05d}{chunk3:04d}" + return digits + str(verhoeff_check(digits)) + + def manual_code_pretty(self) -> str: + c = self.manual_code() + return f"{c[:4]}-{c[4:7]}-{c[7:]}" + + +def _base38(data: bytes) -> str: + out = [] + for i in range(0, len(data), 3): + chunk = data[i:i + 3] + value = int.from_bytes(chunk, "little") + for _ in range({3: 5, 2: 4, 1: 2}[len(chunk)]): + value, rem = divmod(value, 38) + out.append(BASE38[rem]) + return "".join(out) + + +_D = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 0, 6, 7, 8, 9, 5], [2, 3, 4, 0, 1, 7, 8, 9, 5, 6], + [3, 4, 0, 1, 2, 8, 9, 5, 6, 7], [4, 0, 1, 2, 3, 9, 5, 6, 7, 8], [5, 9, 8, 7, 6, 0, 4, 3, 2, 1], + [6, 5, 9, 8, 7, 1, 0, 4, 3, 2], [7, 6, 5, 9, 8, 2, 1, 0, 4, 3], [8, 7, 6, 5, 9, 3, 2, 1, 0, 4], + [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]] +_P = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 5, 7, 6, 2, 8, 3, 0, 9, 4], [5, 8, 0, 3, 7, 9, 6, 1, 4, 2], + [8, 9, 1, 6, 0, 4, 3, 5, 2, 7], [9, 4, 5, 3, 1, 2, 6, 8, 7, 0], [4, 2, 8, 6, 5, 7, 3, 9, 0, 1], + [2, 7, 9, 3, 8, 0, 6, 4, 1, 5], [7, 0, 4, 6, 9, 1, 3, 2, 5, 8]] +_INV = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9] + + +def verhoeff_check(digits: str) -> int: + c = 0 + for i, d in enumerate(reversed(digits)): + c = _D[c][_P[(i + 1) % 8][int(d)]] + return _INV[c] + + +def payload_from_log(lines: list[str], qr: str | None = None, manual: str | None = None) -> SetupPayload: + """Firmware 1.0.16+ reports its exact QR payload and manual code + ("KOTA: matter ... qr=MT:... manual=..."); older firmware: use the + advertised VID/PID/discriminator if seen; defaults otherwise.""" + base = SetupPayload() + for text in reversed(lines): + if m := _ADVERTISE.search(text): + base = SetupPayload(int(m.group(1)), int(m.group(2)), int(m.group(3)), from_log=True) + break + if qr: + return SetupPayload(base.vendor_id, base.product_id, base.discriminator, from_log=True, + qr=qr, manual=manual if manual and manual.isdigit() else None) + return base diff --git a/kota_tech/sd/provision.py b/kota_tech/sd/provision.py index 422a3a2..01a6043 100644 --- a/kota_tech/sd/provision.py +++ b/kota_tech/sd/provision.py @@ -1,261 +1,261 @@ -"""Format an SD card for the sensorbox and write / check ENV.TXT. - -Card format required by the firmware: MBR partition table, FAT32 (its FAT -driver has no exFAT and no long file names), ENV.TXT in the root. - -Windows: formatting needs administrator rights, so kota_tech.sd.win_format -runs elevated (UAC prompt). It re-checks the disk (number, size, bus, not the -Windows drive) right before erasing it, so a card swapped after the -technician confirmed is not touched, and it writes MBR + FAT32 itself -(no Storage cmdlets, which stalled on empty card-reader slots). Larger cards -get a 32 GB FAT32 partition (Windows convention; plenty for the box). -macOS: `diskutil eraseDisk FAT32 KOTA MBRFormat` (same as format_sd.sh). -""" - -from __future__ import annotations - -import json -import os -import plistlib -import subprocess -import sys -import time -import uuid -from dataclasses import dataclass, field -from pathlib import Path - -from .disks import Disk -from .envfile import BUFFER_NAME, ENV_NAME, EnvCheck, EnvConfig, find_case_insensitive, parse_env - -VOLUME_LABEL = "KOTA" - - -@dataclass -class Outcome: - ok: bool - message: str - mount: str = "" - log: list[str] = field(default_factory=list) - - -@dataclass -class CardReport: - mount: str - filesystem: str - env: EnvCheck | None - buffered_lines: int | None # measurements waiting on the card for upload - problems: list[str] - - -# ----------------------------------------------------------------- Windows - -WORK_ROOT = Path.home() / "KotaTechnicianTool" / "tmp" - - -def _workdir() -> Path: - # Not tempfile.mkdtemp(): on Windows it creates an owner-only folder, and a - # file written there by the ELEVATED script is then unreadable for this - # (non-elevated) process. A normally inherited ACL works for both. - work = WORK_ROOT / f"sd_{uuid.uuid4().hex[:10]}" - work.mkdir(parents=True, exist_ok=False) - return work - - -def win_format_command(disk: Disk, result: Path) -> tuple[str, str, str]: - """(program, parameters, working dir) for the elevated helper.""" - root = Path(__file__).resolve().parents[2] # the version folder holding kota_tech/ - params = f'-m kota_tech.sd.win_format --disk {int(disk.id)} --size {int(disk.size)} ' \ - f'--label {VOLUME_LABEL} --result "{result}"' - return sys.executable, params, str(root) - - -def _run_elevated(program: str, params: str, cwd: str, timeout_s: int = 300) -> int | None: - """ShellExecuteEx 'runas' + wait. None if the technician refused the UAC prompt.""" - import ctypes - from ctypes import wintypes - - class SHELLEXECUTEINFOW(ctypes.Structure): - _fields_ = [("cbSize", wintypes.DWORD), ("fMask", ctypes.c_ulong), ("hwnd", wintypes.HWND), - ("lpVerb", wintypes.LPCWSTR), ("lpFile", wintypes.LPCWSTR), ("lpParameters", wintypes.LPCWSTR), - ("lpDirectory", wintypes.LPCWSTR), ("nShow", ctypes.c_int), ("hInstApp", wintypes.HINSTANCE), - ("lpIDList", ctypes.c_void_p), ("lpClass", wintypes.LPCWSTR), ("hkeyClass", wintypes.HKEY), - ("dwHotKey", wintypes.DWORD), ("hIconOrMonitor", wintypes.HANDLE), ("hProcess", wintypes.HANDLE)] - - SEE_MASK_NOCLOSEPROCESS, SW_HIDE = 0x40, 0 - info = SHELLEXECUTEINFOW(cbSize=ctypes.sizeof(SHELLEXECUTEINFOW), fMask=SEE_MASK_NOCLOSEPROCESS, - lpVerb="runas", lpFile=program, lpParameters=params, lpDirectory=cwd, nShow=SW_HIDE) - shell32 = ctypes.WinDLL("shell32", use_last_error=True) - k32 = ctypes.WinDLL("kernel32", use_last_error=True) - if not shell32.ShellExecuteExW(ctypes.byref(info)): - return None # ERROR_CANCELLED (1223) or failure - k32.WaitForSingleObject(info.hProcess, timeout_s * 1000) - code = wintypes.DWORD() - k32.GetExitCodeProcess(info.hProcess, ctypes.byref(code)) - k32.CloseHandle(info.hProcess) - return code.value - - -def _format_windows(disk: Disk, log: list[str]) -> Outcome: - result = _workdir() / "result.json" - log.append("Asking Windows for administrator rights to format the card (UAC prompt)...") - t = time.perf_counter() - code = _run_elevated(*win_format_command(disk, result)) - if code is None: - return Outcome(False, "Administrator permission was refused - the card was not formatted.", log=log) - try: - res = json.loads(result.read_text(encoding="utf-8")) - except (OSError, ValueError) as exc: - return Outcome(False, f"The formatter did not report back ({exc.__class__.__name__}, exit {code}). " - "Check the card with 'Check card'.", log=log) - steps = ", ".join(f"{k} {v:.1f} s" for k, v in res.get("timings", {}).items()) - log.append(f"{res.get('message', '')} ({time.perf_counter() - t:.1f} s incl. permission prompt; {steps})") - return Outcome(bool(res.get("ok")), res.get("message", ""), res.get("drive", ""), log) - - -def _eject_windows(mount: str) -> bool: - """Flush, lock, dismount and eject the card - what 'Safely Remove' does. - - Not Shell.Application InvokeVerb('Eject'): it fails silently because the - verb is named 'E&ject' (and is localised on non-English Windows). - """ - import ctypes - from ctypes import wintypes - - k32 = ctypes.WinDLL("kernel32", use_last_error=True) - k32.CreateFileW.restype = wintypes.HANDLE - k32.CreateFileW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, wintypes.LPVOID, - wintypes.DWORD, wintypes.DWORD, wintypes.HANDLE] - k32.DeviceIoControl.argtypes = [wintypes.HANDLE, wintypes.DWORD, wintypes.LPVOID, wintypes.DWORD, - wintypes.LPVOID, wintypes.DWORD, ctypes.POINTER(wintypes.DWORD), wintypes.LPVOID] - GENERIC_RW, SHARE_RW, OPEN_EXISTING = 0xC0000000, 0x3, 3 - FSCTL_LOCK_VOLUME, FSCTL_DISMOUNT_VOLUME = 0x00090018, 0x00090020 - IOCTL_STORAGE_MEDIA_REMOVAL, IOCTL_STORAGE_EJECT_MEDIA = 0x002D4804, 0x002D4808 - handle = k32.CreateFileW(f"\\\\.\\{mount[0]}:", GENERIC_RW, SHARE_RW, None, OPEN_EXISTING, 0, None) - if handle in (None, wintypes.HANDLE(-1).value): - return False - ret = wintypes.DWORD() - - def ioctl(code: int, buf=None, size: int = 0) -> bool: - return bool(k32.DeviceIoControl(handle, code, buf, size, None, 0, ctypes.byref(ret), None)) - - try: - k32.FlushFileBuffers(handle) - for _ in range(10): # Explorer/indexer may hold it briefly - if ioctl(FSCTL_LOCK_VOLUME): - break - time.sleep(0.5) - else: - return False - if not ioctl(FSCTL_DISMOUNT_VOLUME): - return False - allow = ctypes.c_bool(False) # PREVENT_MEDIA_REMOVAL{FALSE} - ioctl(IOCTL_STORAGE_MEDIA_REMOVAL, ctypes.byref(allow), 1) - ioctl(IOCTL_STORAGE_EJECT_MEDIA) # card readers may not support it; dismount suffices - return True - finally: - k32.CloseHandle(handle) - - -# ------------------------------------------------------------------- macOS - -def _format_macos(disk: Disk, log: list[str]) -> Outcome: - log.append(f"diskutil eraseDisk FAT32 {VOLUME_LABEL} MBRFormat {disk.id}") - proc = subprocess.run(["diskutil", "eraseDisk", "FAT32", VOLUME_LABEL, "MBRFormat", disk.id], - capture_output=True, text=True, timeout=600) - log += [l for l in proc.stdout.splitlines() if l.strip()] - if proc.returncode != 0: - return Outcome(False, "Formatting failed: " + (proc.stderr.strip() or proc.stdout.strip()[-200:]) - + " (is the card's write-protect switch on?)", log=log) - part = f"{disk.id}s1" - for _ in range(20): # wait for the volume to mount - info = plistlib.loads(subprocess.run(["diskutil", "info", "-plist", part], capture_output=True).stdout or b"") - if info.get("MountPoint"): - return Outcome(True, "Formatted FAT32 (MBR).", info["MountPoint"], log) - time.sleep(0.5) - return Outcome(False, "Formatted, but the card did not mount.", log=log) - - -def _eject_macos(disk_id: str) -> bool: - return subprocess.run(["diskutil", "eject", disk_id], capture_output=True, timeout=60).returncode == 0 - - -# ------------------------------------------------------------------ common - -def write_env(mount: str, env: EnvConfig) -> Outcome: - """Write ENV.TXT (8.3 name, LF, no BOM), keep other files (buffered data), read back.""" - problems = [i.text for i in env.problems() if i.level == "error"] - if problems: - return Outcome(False, "Settings are not valid: " + " ".join(problems)) - root = Path(mount) - if not root.is_dir(): - return Outcome(False, f"Card not found at {mount}.") - old = find_case_insensitive(root, ENV_NAME) - if old is not None and old.name != ENV_NAME: - old.unlink() # replace env.txt / Env.txt variants - target = root / ENV_NAME - data = env.render() - with open(target, "wb") as fh: - fh.write(data) - fh.flush() - os.fsync(fh.fileno()) - if target.read_bytes() != data: - return Outcome(False, "ENV.TXT read-back does not match - the card may be faulty.") - check = parse_env(target.read_bytes()) - if not check.ok: - return Outcome(False, "ENV.TXT was written but does not validate: " - + " ".join(i.text for i in check.issues if i.level == "error")) - return Outcome(True, f"ENV.TXT written and verified on {mount}.", mount) - - -def format_and_write(disk: Disk, env: EnvConfig, eject_card: bool = True) -> Outcome: - problems = [i.text for i in env.problems() if i.level == "error"] - if problems: - return Outcome(False, "Settings are not valid: " + " ".join(problems)) - log: list[str] = [] - if sys.platform == "win32": - fmt = _format_windows(disk, log) - elif sys.platform == "darwin": - fmt = _format_macos(disk, log) - else: - return Outcome(False, "SD provisioning is supported on Windows and macOS only.") - if not fmt.ok: - return fmt - wr = write_env(fmt.mount, env) - log.append(wr.message) - if not wr.ok: - return Outcome(False, wr.message, fmt.mount, log) - if eject_card: - log.append("Card ejected - you can remove it." if eject(fmt.mount, disk) else - "Could not eject the card automatically - use 'Safely remove' before pulling it out.") - return Outcome(True, "Card ready: formatted FAT32 and ENV.TXT verified.", fmt.mount, log) - - -def eject(mount: str, disk: Disk | None = None) -> bool: - """True if the card was dismounted and can be pulled out safely.""" - if sys.platform == "win32": - return _eject_windows(mount) - if sys.platform == "darwin" and disk is not None: - return _eject_macos(disk.id) - return False - - -def check_card(mount: str, filesystem: str = "") -> CardReport: - """What the box would find on this card (values masked in the UI).""" - root = Path(mount) - problems: list[str] = [] - # Windows says "FAT32"; macOS diskutil says "DOS_FAT_32" / "Windows_FAT_32" / "MS-DOS FAT32". - fs = filesystem.upper().replace("_", "").replace(" ", "") - if fs and "FAT32" not in fs and fs not in ("FAT", "MSDOS"): - problems.append(f"File system is {filesystem}; the box needs FAT32 - re-format the card.") - env_file = find_case_insensitive(root, ENV_NAME) - env = parse_env(env_file.read_bytes()) if env_file else None - if env_file is None: - problems.append("No ENV.TXT on the card - the box stops at start-up (white LED).") - elif len(env_file.name.split(".")[0]) > 8: - problems.append("ENV.TXT has a long name variant; use exactly ENV.TXT.") - buf = find_case_insensitive(root, BUFFER_NAME) - lines = None - if buf is not None: - with open(buf, "rb") as fh: - lines = sum(1 for ln in fh if ln.strip()) - return CardReport(mount, filesystem, env, lines, problems) +"""Format an SD card for the sensorbox and write / check ENV.TXT. + +Card format required by the firmware: MBR partition table, FAT32 (its FAT +driver has no exFAT and no long file names), ENV.TXT in the root. + +Windows: formatting needs administrator rights, so kota_tech.sd.win_format +runs elevated (UAC prompt). It re-checks the disk (number, size, bus, not the +Windows drive) right before erasing it, so a card swapped after the +technician confirmed is not touched, and it writes MBR + FAT32 itself +(no Storage cmdlets, which stalled on empty card-reader slots). Larger cards +get a 32 GB FAT32 partition (Windows convention; plenty for the box). +macOS: `diskutil eraseDisk FAT32 KOTA MBRFormat` (same as format_sd.sh). +""" + +from __future__ import annotations + +import json +import os +import plistlib +import subprocess +import sys +import time +import uuid +from dataclasses import dataclass, field +from pathlib import Path + +from .disks import Disk +from .envfile import BUFFER_NAME, ENV_NAME, EnvCheck, EnvConfig, find_case_insensitive, parse_env + +VOLUME_LABEL = "KOTA" + + +@dataclass +class Outcome: + ok: bool + message: str + mount: str = "" + log: list[str] = field(default_factory=list) + + +@dataclass +class CardReport: + mount: str + filesystem: str + env: EnvCheck | None + buffered_lines: int | None # measurements waiting on the card for upload + problems: list[str] + + +# ----------------------------------------------------------------- Windows + +WORK_ROOT = Path.home() / "KotaTechnicianTool" / "tmp" + + +def _workdir() -> Path: + # Not tempfile.mkdtemp(): on Windows it creates an owner-only folder, and a + # file written there by the ELEVATED script is then unreadable for this + # (non-elevated) process. A normally inherited ACL works for both. + work = WORK_ROOT / f"sd_{uuid.uuid4().hex[:10]}" + work.mkdir(parents=True, exist_ok=False) + return work + + +def win_format_command(disk: Disk, result: Path) -> tuple[str, str, str]: + """(program, parameters, working dir) for the elevated helper.""" + root = Path(__file__).resolve().parents[2] # the version folder holding kota_tech/ + params = f'-m kota_tech.sd.win_format --disk {int(disk.id)} --size {int(disk.size)} ' \ + f'--label {VOLUME_LABEL} --result "{result}"' + return sys.executable, params, str(root) + + +def _run_elevated(program: str, params: str, cwd: str, timeout_s: int = 300) -> int | None: + """ShellExecuteEx 'runas' + wait. None if the technician refused the UAC prompt.""" + import ctypes + from ctypes import wintypes + + class SHELLEXECUTEINFOW(ctypes.Structure): + _fields_ = [("cbSize", wintypes.DWORD), ("fMask", ctypes.c_ulong), ("hwnd", wintypes.HWND), + ("lpVerb", wintypes.LPCWSTR), ("lpFile", wintypes.LPCWSTR), ("lpParameters", wintypes.LPCWSTR), + ("lpDirectory", wintypes.LPCWSTR), ("nShow", ctypes.c_int), ("hInstApp", wintypes.HINSTANCE), + ("lpIDList", ctypes.c_void_p), ("lpClass", wintypes.LPCWSTR), ("hkeyClass", wintypes.HKEY), + ("dwHotKey", wintypes.DWORD), ("hIconOrMonitor", wintypes.HANDLE), ("hProcess", wintypes.HANDLE)] + + SEE_MASK_NOCLOSEPROCESS, SW_HIDE = 0x40, 0 + info = SHELLEXECUTEINFOW(cbSize=ctypes.sizeof(SHELLEXECUTEINFOW), fMask=SEE_MASK_NOCLOSEPROCESS, + lpVerb="runas", lpFile=program, lpParameters=params, lpDirectory=cwd, nShow=SW_HIDE) + shell32 = ctypes.WinDLL("shell32", use_last_error=True) + k32 = ctypes.WinDLL("kernel32", use_last_error=True) + if not shell32.ShellExecuteExW(ctypes.byref(info)): + return None # ERROR_CANCELLED (1223) or failure + k32.WaitForSingleObject(info.hProcess, timeout_s * 1000) + code = wintypes.DWORD() + k32.GetExitCodeProcess(info.hProcess, ctypes.byref(code)) + k32.CloseHandle(info.hProcess) + return code.value + + +def _format_windows(disk: Disk, log: list[str]) -> Outcome: + result = _workdir() / "result.json" + log.append("Asking Windows for administrator rights to format the card (UAC prompt)...") + t = time.perf_counter() + code = _run_elevated(*win_format_command(disk, result)) + if code is None: + return Outcome(False, "Administrator permission was refused - the card was not formatted.", log=log) + try: + res = json.loads(result.read_text(encoding="utf-8")) + except (OSError, ValueError) as exc: + return Outcome(False, f"The formatter did not report back ({exc.__class__.__name__}, exit {code}). " + "Check the card with 'Check card'.", log=log) + steps = ", ".join(f"{k} {v:.1f} s" for k, v in res.get("timings", {}).items()) + log.append(f"{res.get('message', '')} ({time.perf_counter() - t:.1f} s incl. permission prompt; {steps})") + return Outcome(bool(res.get("ok")), res.get("message", ""), res.get("drive", ""), log) + + +def _eject_windows(mount: str) -> bool: + """Flush, lock, dismount and eject the card - what 'Safely Remove' does. + + Not Shell.Application InvokeVerb('Eject'): it fails silently because the + verb is named 'E&ject' (and is localised on non-English Windows). + """ + import ctypes + from ctypes import wintypes + + k32 = ctypes.WinDLL("kernel32", use_last_error=True) + k32.CreateFileW.restype = wintypes.HANDLE + k32.CreateFileW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, wintypes.LPVOID, + wintypes.DWORD, wintypes.DWORD, wintypes.HANDLE] + k32.DeviceIoControl.argtypes = [wintypes.HANDLE, wintypes.DWORD, wintypes.LPVOID, wintypes.DWORD, + wintypes.LPVOID, wintypes.DWORD, ctypes.POINTER(wintypes.DWORD), wintypes.LPVOID] + GENERIC_RW, SHARE_RW, OPEN_EXISTING = 0xC0000000, 0x3, 3 + FSCTL_LOCK_VOLUME, FSCTL_DISMOUNT_VOLUME = 0x00090018, 0x00090020 + IOCTL_STORAGE_MEDIA_REMOVAL, IOCTL_STORAGE_EJECT_MEDIA = 0x002D4804, 0x002D4808 + handle = k32.CreateFileW(f"\\\\.\\{mount[0]}:", GENERIC_RW, SHARE_RW, None, OPEN_EXISTING, 0, None) + if handle in (None, wintypes.HANDLE(-1).value): + return False + ret = wintypes.DWORD() + + def ioctl(code: int, buf=None, size: int = 0) -> bool: + return bool(k32.DeviceIoControl(handle, code, buf, size, None, 0, ctypes.byref(ret), None)) + + try: + k32.FlushFileBuffers(handle) + for _ in range(10): # Explorer/indexer may hold it briefly + if ioctl(FSCTL_LOCK_VOLUME): + break + time.sleep(0.5) + else: + return False + if not ioctl(FSCTL_DISMOUNT_VOLUME): + return False + allow = ctypes.c_bool(False) # PREVENT_MEDIA_REMOVAL{FALSE} + ioctl(IOCTL_STORAGE_MEDIA_REMOVAL, ctypes.byref(allow), 1) + ioctl(IOCTL_STORAGE_EJECT_MEDIA) # card readers may not support it; dismount suffices + return True + finally: + k32.CloseHandle(handle) + + +# ------------------------------------------------------------------- macOS + +def _format_macos(disk: Disk, log: list[str]) -> Outcome: + log.append(f"diskutil eraseDisk FAT32 {VOLUME_LABEL} MBRFormat {disk.id}") + proc = subprocess.run(["diskutil", "eraseDisk", "FAT32", VOLUME_LABEL, "MBRFormat", disk.id], + capture_output=True, text=True, timeout=600) + log += [l for l in proc.stdout.splitlines() if l.strip()] + if proc.returncode != 0: + return Outcome(False, "Formatting failed: " + (proc.stderr.strip() or proc.stdout.strip()[-200:]) + + " (is the card's write-protect switch on?)", log=log) + part = f"{disk.id}s1" + for _ in range(20): # wait for the volume to mount + info = plistlib.loads(subprocess.run(["diskutil", "info", "-plist", part], capture_output=True).stdout or b"") + if info.get("MountPoint"): + return Outcome(True, "Formatted FAT32 (MBR).", info["MountPoint"], log) + time.sleep(0.5) + return Outcome(False, "Formatted, but the card did not mount.", log=log) + + +def _eject_macos(disk_id: str) -> bool: + return subprocess.run(["diskutil", "eject", disk_id], capture_output=True, timeout=60).returncode == 0 + + +# ------------------------------------------------------------------ common + +def write_env(mount: str, env: EnvConfig) -> Outcome: + """Write ENV.TXT (8.3 name, LF, no BOM), keep other files (buffered data), read back.""" + problems = [i.text for i in env.problems() if i.level == "error"] + if problems: + return Outcome(False, "Settings are not valid: " + " ".join(problems)) + root = Path(mount) + if not root.is_dir(): + return Outcome(False, f"Card not found at {mount}.") + old = find_case_insensitive(root, ENV_NAME) + if old is not None and old.name != ENV_NAME: + old.unlink() # replace env.txt / Env.txt variants + target = root / ENV_NAME + data = env.render() + with open(target, "wb") as fh: + fh.write(data) + fh.flush() + os.fsync(fh.fileno()) + if target.read_bytes() != data: + return Outcome(False, "ENV.TXT read-back does not match - the card may be faulty.") + check = parse_env(target.read_bytes()) + if not check.ok: + return Outcome(False, "ENV.TXT was written but does not validate: " + + " ".join(i.text for i in check.issues if i.level == "error")) + return Outcome(True, f"ENV.TXT written and verified on {mount}.", mount) + + +def format_and_write(disk: Disk, env: EnvConfig, eject_card: bool = True) -> Outcome: + problems = [i.text for i in env.problems() if i.level == "error"] + if problems: + return Outcome(False, "Settings are not valid: " + " ".join(problems)) + log: list[str] = [] + if sys.platform == "win32": + fmt = _format_windows(disk, log) + elif sys.platform == "darwin": + fmt = _format_macos(disk, log) + else: + return Outcome(False, "SD provisioning is supported on Windows and macOS only.") + if not fmt.ok: + return fmt + wr = write_env(fmt.mount, env) + log.append(wr.message) + if not wr.ok: + return Outcome(False, wr.message, fmt.mount, log) + if eject_card: + log.append("Card ejected - you can remove it." if eject(fmt.mount, disk) else + "Could not eject the card automatically - use 'Safely remove' before pulling it out.") + return Outcome(True, "Card ready: formatted FAT32 and ENV.TXT verified.", fmt.mount, log) + + +def eject(mount: str, disk: Disk | None = None) -> bool: + """True if the card was dismounted and can be pulled out safely.""" + if sys.platform == "win32": + return _eject_windows(mount) + if sys.platform == "darwin" and disk is not None: + return _eject_macos(disk.id) + return False + + +def check_card(mount: str, filesystem: str = "") -> CardReport: + """What the box would find on this card (values masked in the UI).""" + root = Path(mount) + problems: list[str] = [] + # Windows says "FAT32"; macOS diskutil says "DOS_FAT_32" / "Windows_FAT_32" / "MS-DOS FAT32". + fs = filesystem.upper().replace("_", "").replace(" ", "") + if fs and "FAT32" not in fs and fs not in ("FAT", "MSDOS"): + problems.append(f"File system is {filesystem}; the box needs FAT32 - re-format the card.") + env_file = find_case_insensitive(root, ENV_NAME) + env = parse_env(env_file.read_bytes()) if env_file else None + if env_file is None: + problems.append("No ENV.TXT on the card - the box stops at start-up (white LED).") + elif len(env_file.name.split(".")[0]) > 8: + problems.append("ENV.TXT has a long name variant; use exactly ENV.TXT.") + buf = find_case_insensitive(root, BUFFER_NAME) + lines = None + if buf is not None: + with open(buf, "rb") as fh: + lines = sum(1 for ln in fh if ln.strip()) + return CardReport(mount, filesystem, env, lines, problems) diff --git a/kota_tech/service/report.py b/kota_tech/service/report.py index 5fb9d80..2702ac7 100644 --- a/kota_tech/service/report.py +++ b/kota_tech/service/report.py @@ -1,155 +1,155 @@ -"""Hand-off of a diagnosis to the KOTA service system (Google Apps Script). - -The tool opens the box's service page in the technician's browser: - #kota-report= -The service page (v1.1.0+) reads the fragment and pre-fills status and note; -the technician checks it, adds the location and saves - so the report is -authored by their own Google login and the tool holds no credentials. -The fragment is never sent to any server (redirect host or Apps Script). -""" - -from __future__ import annotations - -import base64 -import json -import re - -from .. import __version__ -from ..diag.engine import Report, Severity - -DEFAULT_BASE = "https://service.kota-app.com/" -NOTE_LIMIT = 4800 # the service form and Code.gs accept 5000; leave room for the technician - -# Status names exactly as in the service system (Code.gs STATUSES). -OK = "Działa poprawnie" -CHECK = "Do sprawdzenia" -IN_SERVICE = "W trakcie serwisu" -HARDWARE = "Błąd hardware" -SOFTWARE = "Błąd software" -POWER = "Problem z zasilaniem" -CONNECTIVITY = "Problem z łącznością" - -# Root-finding rule id prefix -> service status (first match wins). -_STATUS_BY_RULE = [ - ("reboot.brownout", POWER), ("reboot.power", POWER), ("tech.power.", POWER), - ("sys.crash", SOFTWARE), ("reboot.watchdog", SOFTWARE), ("sys.out_of_resources", SOFTWARE), - ("influx.bad_request", SOFTWARE), ("ota.selfcheck_failed", SOFTWARE), ("tech.quick.hung", SOFTWARE), - ("rtc.init_failed", HARDWARE), ("rtc.lost_after_sync", HARDWARE), ("i2c.", HARDWARE), ("sensor.", HARDWARE), - ("sd.", HARDWARE), ("sen66.", HARDWARE), ("bme688.", HARDWARE), ("as7341.", HARDWARE), - ("mic.read_fail", HARDWARE), ("mic.silent", HARDWARE), ("tech.rtc_i2c.", HARDWARE), ("tech.sensors.", HARDWARE), - ("tech.mic.mic_faulty", HARDWARE), ("tech.mic.mic_switch_faulty", HARDWARE), ("tech.white.sd_", HARDWARE), - ("matter.", CONNECTIVITY), ("net.", CONNECTIVITY), ("wifi.", CONNECTIVITY), ("influx.", CONNECTIVITY), - ("ota.", CONNECTIVITY), ("tech.network.", CONNECTIVITY), - # firmware 1.0.16+ (KOTA: lines, status, error IDs) - ("reboot.crash", SOFTWARE), ("reboot.status_oom_watchdog", SOFTWARE), ("status.oom", SOFTWARE), - ("kota.low_heap", SOFTWARE), - ("kota.i2c_stuck", HARDWARE), ("kota.sensor_missing", HARDWARE), ("kota.rtc_", HARDWARE), - ("kota.sd_", HARDWARE), ("status.error", HARDWARE), ("status.rtc", HARDWARE), ("status.sd", HARDWARE), - ("kota.wifi_down", CONNECTIVITY), ("kota.weak_wifi", CONNECTIVITY), ("kota.no_upload", CONNECTIVITY), - ("kota.ota_unregistered", CONNECTIVITY), ("kota.buffer_backlog", CONNECTIVITY), ("status.conn", CONNECTIVITY), - ("kota.time_invalid", CHECK), - *((f"eid.{m}", HARDWARE) for m in ("SD", "ENV", "RTC", "BME", "SEN", "AS", "MIC", "FLK", "SPI")), - *((f"eid.{m}", CONNECTIVITY) for m in ("INF", "WIFI", "OTA", "PROV")), - ("eid.", SOFTWARE), - ("rtc.clock_not_running", CHECK), ("reboot.", CHECK), -] -_MAC = re.compile(r"^[0-9A-F]{12}$") - - -def normalize_mac(mac: str) -> str: - return re.sub(r"[^0-9A-Fa-f]", "", mac or "").upper() - - -def service_status(report: Report) -> str: - """Suggested status; the technician can change it in the form.""" - problems = [rc for rc in report.roots if rc.severity >= Severity.WARNING] - if not problems: - return OK if any(m == "data_uploaded" and t is not None for m, _, t in report.milestones) else CHECK - rid = problems[0].finding.rule_id - for prefix, status in _STATUS_BY_RULE: - if rid.startswith(prefix): - return status - return CHECK - - -BOX_MARK = {"ok": "✔", "warn": "!", "bad": "✖", "info": "i"} # same marks as the Diagnosis panel -SD_WIFI = {True: "reads Wi-Fi settings from the SD card", - False: "does not read Wi-Fi settings from the SD card", - None: "Wi-Fi-from-SD support unknown"} - - -def service_note(report: Report, limit: int = NOTE_LIMIT) -> str: - """The Diagnosis panel as text: verdict, LED, firmware, box self-report, - start-up progress with times, findings with details and when they were seen. - - Falls back to shorter finding details when the full text does not fit the - service form (consequences' explanations first, then all), then truncates. - """ - for detail in ("all", "roots", "none"): - text = _note(report, detail) - if len(text) <= limit: - return text - return text[:limit - 20].rstrip() + "\n...(shortened)" - - -def _note(report: Report, detail: str) -> str: - fw = report.firmware - source = " (reported by the box)" if report.led_exact else "" - lines = [f"[KOTA Technician Tool {__version__}] {report.verdict}", "", - f"Status LED should be: {report.led_text()}{source}"] - if report.led_reason: - lines.append(f" from: {report.led_reason}") - lines += ["", f"Firmware: {fw.describe()}", f" {SD_WIFI[fw.supports_sd_wifi]}"] - if report.box: - lines += ["", "Box self-report (KOTA lines):"] - lines += [f" {BOX_MARK.get(st, '-')} {topic}: {text}" for topic, text, st in report.box] - lines += ["", "Start-up progress (current boot):"] - lines += [f" ✔ {label} ({t / 1000:.1f} s)" if t is not None else f" ○ {label}" - for _, label, t in report.milestones] - lines += ["", "Findings - root causes first:" if report.roots else "Findings: no problems detected in the log"] - for rc in report.roots: - lines += _finding(rc.finding, "", detail in ("all", "roots")) - for c in rc.consequences: - lines += _finding(c, " ↳ ", detail == "all") - for title, items in (("Seen in earlier boots only", report.earlier), - ("Resolved during this session", report.resolved)): - if items: - lines += ["", f"{title} ({len(items)}):"] - lines += [f" - [{f.severity.label}] {f.title} - {f.seen_text()}" for f in items] - if report.technician: - lines += ["", "Technician checks:"] - lines += [f" * {t}" for t in report.technician[-12:]] - return "\n".join(lines) - - -def _finding(f, prefix: str, detailed: bool) -> list[str]: - ind = " " * len(prefix) + " " - out = [f"{prefix}[{f.severity.label}] {f.title}"] - if detailed: - out.append(f"{ind}What it means: {f.meaning}") - if f.actions: - out.append(f"{ind}What to do:") - out += [f"{ind} {i}. {a}" for i, a in enumerate(f.actions, 1)] - out += [f"{ind}Error {eid}" + (f": {a}" if a else "") for eid, a in f.error_ids.items()] - out.append(f"{ind}{f.seen_text()}") - if f.evidence: - out.append(f"{ind}Log: {f.evidence[-1]}") - return out - - -def report_url(mac: str, report: Report, base: str = DEFAULT_BASE) -> str: - mac = normalize_mac(mac) - if not _MAC.match(mac): - raise ValueError(f"not a box MAC address: {mac!r}") - payload = {"v": 1, "mac": mac, "status": service_status(report), "note": service_note(report), - "tool": f"v{__version__}"} - raw = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8") - frag = base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=") - base = base if base.endswith("/") else base + "/" - return f"{base}{mac}#kota-report={frag}" - - -def decode_fragment(url: str) -> dict: - """Inverse of report_url (used by tests and for troubleshooting).""" - frag = url.split("#kota-report=", 1)[1] - return json.loads(base64.urlsafe_b64decode(frag + "=" * (-len(frag) % 4)).decode("utf-8")) +"""Hand-off of a diagnosis to the KOTA service system (Google Apps Script). + +The tool opens the box's service page in the technician's browser: + #kota-report= +The service page (v1.1.0+) reads the fragment and pre-fills status and note; +the technician checks it, adds the location and saves - so the report is +authored by their own Google login and the tool holds no credentials. +The fragment is never sent to any server (redirect host or Apps Script). +""" + +from __future__ import annotations + +import base64 +import json +import re + +from .. import __version__ +from ..diag.engine import Report, Severity + +DEFAULT_BASE = "https://service.kota-app.com/" +NOTE_LIMIT = 4800 # the service form and Code.gs accept 5000; leave room for the technician + +# Status names exactly as in the service system (Code.gs STATUSES). +OK = "Działa poprawnie" +CHECK = "Do sprawdzenia" +IN_SERVICE = "W trakcie serwisu" +HARDWARE = "Błąd hardware" +SOFTWARE = "Błąd software" +POWER = "Problem z zasilaniem" +CONNECTIVITY = "Problem z łącznością" + +# Root-finding rule id prefix -> service status (first match wins). +_STATUS_BY_RULE = [ + ("reboot.brownout", POWER), ("reboot.power", POWER), ("tech.power.", POWER), + ("sys.crash", SOFTWARE), ("reboot.watchdog", SOFTWARE), ("sys.out_of_resources", SOFTWARE), + ("influx.bad_request", SOFTWARE), ("ota.selfcheck_failed", SOFTWARE), ("tech.quick.hung", SOFTWARE), + ("rtc.init_failed", HARDWARE), ("rtc.lost_after_sync", HARDWARE), ("i2c.", HARDWARE), ("sensor.", HARDWARE), + ("sd.", HARDWARE), ("sen66.", HARDWARE), ("bme688.", HARDWARE), ("as7341.", HARDWARE), + ("mic.read_fail", HARDWARE), ("mic.silent", HARDWARE), ("tech.rtc_i2c.", HARDWARE), ("tech.sensors.", HARDWARE), + ("tech.mic.mic_faulty", HARDWARE), ("tech.mic.mic_switch_faulty", HARDWARE), ("tech.white.sd_", HARDWARE), + ("matter.", CONNECTIVITY), ("net.", CONNECTIVITY), ("wifi.", CONNECTIVITY), ("influx.", CONNECTIVITY), + ("ota.", CONNECTIVITY), ("tech.network.", CONNECTIVITY), + # firmware 1.0.16+ (KOTA: lines, status, error IDs) + ("reboot.crash", SOFTWARE), ("reboot.status_oom_watchdog", SOFTWARE), ("status.oom", SOFTWARE), + ("kota.low_heap", SOFTWARE), + ("kota.i2c_stuck", HARDWARE), ("kota.sensor_missing", HARDWARE), ("kota.rtc_", HARDWARE), + ("kota.sd_", HARDWARE), ("status.error", HARDWARE), ("status.rtc", HARDWARE), ("status.sd", HARDWARE), + ("kota.wifi_down", CONNECTIVITY), ("kota.weak_wifi", CONNECTIVITY), ("kota.no_upload", CONNECTIVITY), + ("kota.ota_unregistered", CONNECTIVITY), ("kota.buffer_backlog", CONNECTIVITY), ("status.conn", CONNECTIVITY), + ("kota.time_invalid", CHECK), + *((f"eid.{m}", HARDWARE) for m in ("SD", "ENV", "RTC", "BME", "SEN", "AS", "MIC", "FLK", "SPI")), + *((f"eid.{m}", CONNECTIVITY) for m in ("INF", "WIFI", "OTA", "PROV")), + ("eid.", SOFTWARE), + ("rtc.clock_not_running", CHECK), ("reboot.", CHECK), +] +_MAC = re.compile(r"^[0-9A-F]{12}$") + + +def normalize_mac(mac: str) -> str: + return re.sub(r"[^0-9A-Fa-f]", "", mac or "").upper() + + +def service_status(report: Report) -> str: + """Suggested status; the technician can change it in the form.""" + problems = [rc for rc in report.roots if rc.severity >= Severity.WARNING] + if not problems: + return OK if any(m == "data_uploaded" and t is not None for m, _, t in report.milestones) else CHECK + rid = problems[0].finding.rule_id + for prefix, status in _STATUS_BY_RULE: + if rid.startswith(prefix): + return status + return CHECK + + +BOX_MARK = {"ok": "✔", "warn": "!", "bad": "✖", "info": "i"} # same marks as the Diagnosis panel +SD_WIFI = {True: "reads Wi-Fi settings from the SD card", + False: "does not read Wi-Fi settings from the SD card", + None: "Wi-Fi-from-SD support unknown"} + + +def service_note(report: Report, limit: int = NOTE_LIMIT) -> str: + """The Diagnosis panel as text: verdict, LED, firmware, box self-report, + start-up progress with times, findings with details and when they were seen. + + Falls back to shorter finding details when the full text does not fit the + service form (consequences' explanations first, then all), then truncates. + """ + for detail in ("all", "roots", "none"): + text = _note(report, detail) + if len(text) <= limit: + return text + return text[:limit - 20].rstrip() + "\n...(shortened)" + + +def _note(report: Report, detail: str) -> str: + fw = report.firmware + source = " (reported by the box)" if report.led_exact else "" + lines = [f"[KOTA Technician Tool {__version__}] {report.verdict}", "", + f"Status LED should be: {report.led_text()}{source}"] + if report.led_reason: + lines.append(f" from: {report.led_reason}") + lines += ["", f"Firmware: {fw.describe()}", f" {SD_WIFI[fw.supports_sd_wifi]}"] + if report.box: + lines += ["", "Box self-report (KOTA lines):"] + lines += [f" {BOX_MARK.get(st, '-')} {topic}: {text}" for topic, text, st in report.box] + lines += ["", "Start-up progress (current boot):"] + lines += [f" ✔ {label} ({t / 1000:.1f} s)" if t is not None else f" ○ {label}" + for _, label, t in report.milestones] + lines += ["", "Findings - root causes first:" if report.roots else "Findings: no problems detected in the log"] + for rc in report.roots: + lines += _finding(rc.finding, "", detail in ("all", "roots")) + for c in rc.consequences: + lines += _finding(c, " ↳ ", detail == "all") + for title, items in (("Seen in earlier boots only", report.earlier), + ("Resolved during this session", report.resolved)): + if items: + lines += ["", f"{title} ({len(items)}):"] + lines += [f" - [{f.severity.label}] {f.title} - {f.seen_text()}" for f in items] + if report.technician: + lines += ["", "Technician checks:"] + lines += [f" * {t}" for t in report.technician[-12:]] + return "\n".join(lines) + + +def _finding(f, prefix: str, detailed: bool) -> list[str]: + ind = " " * len(prefix) + " " + out = [f"{prefix}[{f.severity.label}] {f.title}"] + if detailed: + out.append(f"{ind}What it means: {f.meaning}") + if f.actions: + out.append(f"{ind}What to do:") + out += [f"{ind} {i}. {a}" for i, a in enumerate(f.actions, 1)] + out += [f"{ind}Error {eid}" + (f": {a}" if a else "") for eid, a in f.error_ids.items()] + out.append(f"{ind}{f.seen_text()}") + if f.evidence: + out.append(f"{ind}Log: {f.evidence[-1]}") + return out + + +def report_url(mac: str, report: Report, base: str = DEFAULT_BASE) -> str: + mac = normalize_mac(mac) + if not _MAC.match(mac): + raise ValueError(f"not a box MAC address: {mac!r}") + payload = {"v": 1, "mac": mac, "status": service_status(report), "note": service_note(report), + "tool": f"v{__version__}"} + raw = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8") + frag = base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=") + base = base if base.endswith("/") else base + "/" + return f"{base}{mac}#kota-report={frag}" + + +def decode_fragment(url: str) -> dict: + """Inverse of report_url (used by tests and for troubleshooting).""" + frag = url.split("#kota-report=", 1)[1] + return json.loads(base64.urlsafe_b64decode(frag + "=" * (-len(frag) % 4)).decode("utf-8")) diff --git a/kota_tech/ui/cheatsheet.py b/kota_tech/ui/cheatsheet.py index 83eede6..b42d389 100644 --- a/kota_tech/ui/cheatsheet.py +++ b/kota_tech/ui/cheatsheet.py @@ -1,87 +1,87 @@ -"""Quick-start cheat sheet shown on launch. - -LED table for firmware 1.0.16+ (firmware-diagnostics-what-changed.md, -status.cpp Status::pattern_for): colour AND pattern name the fault. Older -boxes (v1.0, built Jun 29 2026) show the same colours, always solid. -""" - -# (swatch colour, LED name, pattern -> meaning, first action) -LED_TABLE = [ - ("#2e9e44", "Green", "solid: everything OK.", "Nothing to do."), - ("#1fc8d8", "Cyan", - "breathing: not paired in Matter yet
fast blink: a phone is connected for pairing.", - "Pair the box in the Matter app (QR sticker or the QR code in the tool)."), - ("#f4f4f4", "White", - "fast blink: SD card or ENV.TXT error
solid: factory reset (Matter button held 5 s)
" - "breathing: Wi-Fi setup portal.", - "SD card is inside the case: re-seat or re-provision it. Setup portal: short-press the button to leave."), - ("#d64541", "Red", - "1-6 pulses: 1 BME688, 2 SEN66, 3 AS7341, 4 microphone, 5 flicker, 6 self-check failed
" - "solid: other internal error / out of memory.", - "Count the pulses, then check that sensor's cable. The box restarts itself after 150 s."), - ("#2f6fd6", "Blue", - "1 pulse: clock (RTC) not responding
2 pulses: I2C bus stuck (shorted / broken sensor cable).", - "Check the I2C cable between the boards first, then the RTC module."), - ("#8e44ad", "Purple", - "solid: no network
blinking: server rejected the box (wrong ENV.TXT credentials).", - "Solid: check Wi-Fi / Internet. Blinking: re-provision the SD card with the current ENV.TXT."), - ("#e6c229", "Yellow", "blinking, faster over time: downloading an update.", "Do not switch off. Wait."), - ("#f08c1a", "Orange", "solid: self-check after an update (~2.5 min).", - "Wait; should turn green (or red on failure)."), - ("#444444", "Off", "Firmware did not start (or crashed very early).", - "Check power switch & USB cable. If the small LED is also dark: re-flash."), -] - -HINTS = [ - "Before a self-restart the LED flashes the error colour 3 times; after the restart it " - "shows the previous error for 3 s. So a reboot loop shows its cause. Connect serial and press " - "Reset & capture: the tool reads the restart reason from the box.", - "Small LED next to the RGB LED (heartbeat): blinks once per second while online, " - "double-blinks while offline. Dark or steady = firmware hung or not running.", - "Serial monitor: lines starting with KOTA: are the box's own diagnosis (boot, I2C, SD, " - "clock, Matter, microphone, status, LED, and a health line every 60 s). The Diagnosis panel reads them " - "directly; filter the view with KOTA diagnostics + errors. Error lines carry a code like " - "[E-SD-03] - the tool shows the recommended action for it.", - "Outside the case (back): power switch, the microphone switch just above it " - "(off = no noise measurements), and the Matter button. Holding the " - "Matter button 5 s factory-resets the box (it forgets its Matter " - "network and must be commissioned again with the sticker QR code).", - "Inside the case (unscrew): RESET and BOOT buttons and the SD card. " - "Normally not needed - the tool resets the board over USB.", - "Flashing uses only the firmware files in the tool's firmware folder; choose the build " - "by its compile date.", -] - -STEPS = [ - "Switch the sensorbox power switch ON.", - "Connect the laptop to the box's native USB port with a data-capable cable.", - "Open the Serial monitor tab, choose the sensorbox port, press " - "Connect.", - "Press Reset & capture to record a full boot log from the first line.", - "Read the Diagnosis panel on the right, then open Guided troubleshooting " - "and start Quick check: the tool asks what you see and checks the log live.", -] - - -def cheatsheet_html() -> str: - rows = "".join( - f"      {name}" - f"{meaning}{action}" - for c, name, meaning, action in LED_TABLE - ) - steps = "".join(f"
  • {s}
  • " for s in STEPS) - hints = "".join(f"
  • {h}
  • " for h in HINTS) - return f""" -

    Sensorbox quick start

    -
      {steps}
    -

    RGB status LED - colour and pattern

    - - - - - {rows} -
    LEDPattern: meaningFirst action
    -

    Good to know

    -
      {hints}
    - """ +"""Quick-start cheat sheet shown on launch. + +LED table for firmware 1.0.16+ (firmware-diagnostics-what-changed.md, +status.cpp Status::pattern_for): colour AND pattern name the fault. Older +boxes (v1.0, built Jun 29 2026) show the same colours, always solid. +""" + +# (swatch colour, LED name, pattern -> meaning, first action) +LED_TABLE = [ + ("#2e9e44", "Green", "solid: everything OK.", "Nothing to do."), + ("#1fc8d8", "Cyan", + "breathing: not paired in Matter yet
    fast blink: a phone is connected for pairing.", + "Pair the box in the Matter app (QR sticker or the QR code in the tool)."), + ("#f4f4f4", "White", + "fast blink: SD card or ENV.TXT error
    solid: factory reset (Matter button held 5 s)
    " + "breathing: Wi-Fi setup portal.", + "SD card is inside the case: re-seat or re-provision it. Setup portal: short-press the button to leave."), + ("#d64541", "Red", + "1-6 pulses: 1 BME688, 2 SEN66, 3 AS7341, 4 microphone, 5 flicker, 6 self-check failed
    " + "solid: other internal error / out of memory.", + "Count the pulses, then check that sensor's cable. The box restarts itself after 150 s."), + ("#2f6fd6", "Blue", + "1 pulse: clock (RTC) not responding
    2 pulses: I2C bus stuck (shorted / broken sensor cable).", + "Check the I2C cable between the boards first, then the RTC module."), + ("#8e44ad", "Purple", + "solid: no network
    blinking: server rejected the box (wrong ENV.TXT credentials).", + "Solid: check Wi-Fi / Internet. Blinking: re-provision the SD card with the current ENV.TXT."), + ("#e6c229", "Yellow", "blinking, faster over time: downloading an update.", "Do not switch off. Wait."), + ("#f08c1a", "Orange", "solid: self-check after an update (~2.5 min).", + "Wait; should turn green (or red on failure)."), + ("#444444", "Off", "Firmware did not start (or crashed very early).", + "Check power switch & USB cable. If the small LED is also dark: re-flash."), +] + +HINTS = [ + "Before a self-restart the LED flashes the error colour 3 times; after the restart it " + "shows the previous error for 3 s. So a reboot loop shows its cause. Connect serial and press " + "Reset & capture: the tool reads the restart reason from the box.", + "Small LED next to the RGB LED (heartbeat): blinks once per second while online, " + "double-blinks while offline. Dark or steady = firmware hung or not running.", + "Serial monitor: lines starting with KOTA: are the box's own diagnosis (boot, I2C, SD, " + "clock, Matter, microphone, status, LED, and a health line every 60 s). The Diagnosis panel reads them " + "directly; filter the view with KOTA diagnostics + errors. Error lines carry a code like " + "[E-SD-03] - the tool shows the recommended action for it.", + "Outside the case (back): power switch, the microphone switch just above it " + "(off = no noise measurements), and the Matter button. Holding the " + "Matter button 5 s factory-resets the box (it forgets its Matter " + "network and must be commissioned again with the sticker QR code).", + "Inside the case (unscrew): RESET and BOOT buttons and the SD card. " + "Normally not needed - the tool resets the board over USB.", + "Flashing uses only the firmware files in the tool's firmware folder; choose the build " + "by its compile date.", +] + +STEPS = [ + "Switch the sensorbox power switch ON.", + "Connect the laptop to the box's native USB port with a data-capable cable.", + "Open the Serial monitor tab, choose the sensorbox port, press " + "Connect.", + "Press Reset & capture to record a full boot log from the first line.", + "Read the Diagnosis panel on the right, then open Guided troubleshooting " + "and start Quick check: the tool asks what you see and checks the log live.", +] + + +def cheatsheet_html() -> str: + rows = "".join( + f"      {name}" + f"{meaning}{action}" + for c, name, meaning, action in LED_TABLE + ) + steps = "".join(f"
  • {s}
  • " for s in STEPS) + hints = "".join(f"
  • {h}
  • " for h in HINTS) + return f""" +

    Sensorbox quick start

    +
      {steps}
    +

    RGB status LED - colour and pattern

    + + + + + {rows} +
    LEDPattern: meaningFirst action
    +

    Good to know

    +
      {hints}
    + """ diff --git a/kota_tech/ui/diag_panel.py b/kota_tech/ui/diag_panel.py index 184eac5..39116e7 100644 --- a/kota_tech/ui/diag_panel.py +++ b/kota_tech/ui/diag_panel.py @@ -1,252 +1,252 @@ -"""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. -""" - -from __future__ import annotations - -from PySide6.QtCore import Qt -from PySide6.QtGui import QBrush, QColor -from PySide6.QtWidgets import ( - QFrame, - QHBoxLayout, - QLabel, - QListWidget, - QListWidgetItem, - QPushButton, - QSizePolicy, - QSplitter, - QTextBrowser, - QTreeWidget, - QTreeWidgetItem, - QVBoxLayout, - QWidget, -) - -from ..diag.engine import LED, Finding, Report, Severity - -SEV_COLOR = { - Severity.CRITICAL: "#b03a2e", - Severity.ERROR: "#d9534f", - Severity.WARNING: "#c77c02", - Severity.INFO: "#2e6da4", -} -# verdict banner (background, text) by verdict severity; None = healthy -BANNER = { - Severity.CRITICAL: ("#f6d5d1", "#7b1e14"), - Severity.ERROR: ("#f9dedc", "#8a2019"), - Severity.WARNING: ("#fdebc8", "#6b4400"), - None: ("#d8f0dd", "#1b5e2a"), -} -_KEY = Qt.ItemDataRole.UserRole -BOX_STATE = {"ok": ("#1b7a33", "✔"), "warn": ("#c77c02", "!"), "bad": ("#b03a2e", "✖"), "info": ("#2e6da4", "i")} - - -def _heading(text: str) -> QLabel: - lbl = QLabel(text) - f = lbl.font() - f.setBold(True) - lbl.setFont(f) - return lbl - - -def _esc(s: str) -> str: - return s.replace("&", "&").replace("<", "<").replace(">", ">") - - -class DiagnosticsPanel(QWidget): - def __init__(self, parent: QWidget | None = None): - super().__init__(parent) - self.setMinimumWidth(380) - - self.verdict = QLabel(wordWrap=True) - self.verdict.setMargin(10) - vf = self.verdict.font() - vf.setPointSize(vf.pointSize() + 2) - vf.setBold(True) - self.verdict.setFont(vf) - - self.led_swatch = QLabel() - self.led_swatch.setFixedSize(22, 22) - self.led_text = QLabel(wordWrap=True) - led_row = QHBoxLayout() - led_row.addWidget(self.led_swatch, 0, Qt.AlignmentFlag.AlignTop) - led_row.addWidget(self.led_text, 1) - - self.firmware = QLabel(wordWrap=True) - self.firmware.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) - - self.box_title = _heading("Box self-report (KOTA lines)") - self.box = QLabel(wordWrap=True) - self.box.setTextFormat(Qt.TextFormat.RichText) - self.box.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) - - self.milestones = QListWidget() - self.milestones.setFrameShape(QFrame.Shape.NoFrame) - self.milestones.setSelectionMode(QListWidget.SelectionMode.NoSelection) - self.milestones.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum) - - self.tree = QTreeWidget() - self.tree.setHeaderHidden(True) - self.tree.setIndentation(16) - self.tree.setTextElideMode(Qt.TextElideMode.ElideRight) - self.detail = QTextBrowser() - self.detail.setOpenExternalLinks(False) - findings = QSplitter(Qt.Orientation.Vertical) - findings.addWidget(self.tree) - findings.addWidget(self.detail) - findings.setSizes([220, 260]) - - lay = QVBoxLayout(self) - lay.setContentsMargins(4, 0, 0, 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(self.milestones) - lay.addWidget(_heading("Findings - root causes first; click one for details")) - lay.addWidget(findings, 1) - - self._findings: dict[str, Finding] = {} - self._selected: str | None = None - # key -> expanded, chosen by the user; survives the rebuild on refresh. - self._expanded: dict[str, bool] = {} - self.tree.currentItemChanged.connect(self._on_select) - self.tree.itemCollapsed.connect(lambda it: self._remember(it, False)) - self.tree.itemExpanded.connect(lambda it: self._remember(it, True)) - - # ------------------------------------------------------------------ - def show_report(self, r: Report) -> None: - bg, fg = BANNER.get(r.verdict_severity, BANNER[Severity.WARNING]) - if r.lines == 0: - bg, fg = "#e6e6e6", "#333333" - self.verdict.setText(r.verdict) - self.verdict.setStyleSheet(f"QLabel {{ background:{bg}; color:{fg}; border-radius:6px; }}") - - name, colour = LED[r.led] - self.led_swatch.setStyleSheet( - f"QLabel {{ background:{colour}; border:1px solid #777; border-radius:11px; }}") - ask = "" if r.led == "UNKNOWN" else " - check it matches the box" - pattern = f", {_esc(r.led_pattern)}" if r.led_pattern else "" - meaning = f" = {_esc(r.led_meaning)}" if r.led_meaning else "" - source = " (reported by the box)" if r.led_exact else "" - reason = f"
    from: {_esc(r.led_reason)}" if r.led_reason else "" - self.led_text.setText(f"{name}{pattern}{meaning}{source}{ask}{reason}") - - fw = r.firmware - sd = {True: "reads Wi-Fi settings from the SD card", - False: "does not read Wi-Fi settings from the SD card", - None: "Wi-Fi-from-SD support unknown"} - self.firmware.setText(f"{_esc(fw.describe())}
    {sd[fw.supports_sd_wifi]}") - - self.box_title.setVisible(bool(r.box)) - self.box.setVisible(bool(r.box)) - self.box.setText("" + "".join( - f"" - f"" - for topic, text, st in r.box) + "
    {BOX_STATE[st][1]} {_esc(topic)} {_esc(text)}
    ") - - self.milestones.clear() - for _, label, t in r.milestones: - 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"))) - self.milestones.addItem(it) - rows = min(max(self.milestones.count(), 1), 15) - self.milestones.setFixedHeight(rows * max(self.milestones.sizeHintForRow(0), 16) + 6) - - self._fill_tree(r) - - def _fill_tree(self, r: Report) -> None: - self.tree.blockSignals(True) - self.tree.clear() - self._findings = {} - if not r.roots: - it = QTreeWidgetItem(["No problems detected in the log so far."]) - it.setForeground(0, QBrush(QColor("#1b7a33"))) - self.tree.addTopLevelItem(it) - for rc in r.roots: - top = self._item(rc.finding, bold=True) - self.tree.addTopLevelItem(top) - for c in rc.consequences: - top.addChild(self._item(c, prefix="↳ ")) - top.setExpanded(self._expanded.get(rc.finding.key, True)) - for title, key, items in (("Seen in earlier boots only", "grp:earlier", r.earlier), - ("Resolved during this session", "grp:resolved", r.resolved)): - if not items: - continue - grp = QTreeWidgetItem([f"{title} ({len(items)})"]) - grp.setData(0, _KEY, key) - grp.setForeground(0, QBrush(QColor("#666666"))) - self.tree.addTopLevelItem(grp) - for f in items: - grp.addChild(self._item(f, prefix="✔ " if key == "grp:resolved" else "")) - grp.setExpanded(self._expanded.get(key, False)) - # Keep the selection across refreshes; default to the top root cause. - target = self._selected if self._selected in self._findings else ( - r.roots[0].finding.key if r.roots else None) - self.tree.blockSignals(False) - self._select(target) - - def _remember(self, item: QTreeWidgetItem, expanded: bool) -> None: - if key := item.data(0, _KEY): - self._expanded[key] = expanded - - def _item(self, f: Finding, prefix: str = "", bold: bool = False) -> QTreeWidgetItem: - it = QTreeWidgetItem([f"{prefix}[{f.severity.label}] {f.title}"]) - it.setData(0, _KEY, f.key) - it.setForeground(0, QBrush(QColor(SEV_COLOR.get(f.severity, "#333333")))) - it.setToolTip(0, f.title) - if bold: - font = it.font(0) - font.setBold(True) - it.setFont(0, font) - self._findings[f.key] = f - return it - - def _select(self, key: str | None) -> None: - if key is None: - self.detail.setHtml("

    Nothing to show yet.

    ") - return - match = self.tree.findItems("*", Qt.MatchFlag.MatchWildcard | Qt.MatchFlag.MatchRecursive) - for it in match: - if it.data(0, _KEY) == key: - self.tree.setCurrentItem(it) - return - - def _on_select(self, cur: QTreeWidgetItem | None, _prev) -> None: - key = cur.data(0, _KEY) if cur else None - f = self._findings.get(key or "") - if f is None: - return - self._selected = key - self.detail.setHtml(_detail_html(f)) - - -def _detail_html(f: Finding) -> str: - colour = SEV_COLOR.get(f.severity, "#333") - actions = "".join(f"
  • {_esc(a)}
  • " for a in f.actions) - seen = _esc(f.seen_text()) - evidence = "
    ".join(_esc(e) for e in f.evidence) - codes = "".join(f"
  • {_esc(eid)}" + (f" - {_esc(a)}" if a else "") + "
  • " - for eid, a in f.error_ids.items()) - return ( - f"

    {_esc(f.title)}

    " - f"

    What it means: {_esc(f.meaning)}

    " - + (f"

    What to do:

      {actions}
    " if actions else "") - + (f"

    Firmware error codes (error_ids.csv):

      {codes}
    " if codes else "") - + f"

    {seen}

    " - + (f"

    {evidence}

    " - if evidence else "") - ) +"""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. +""" + +from __future__ import annotations + +from PySide6.QtCore import Qt +from PySide6.QtGui import QBrush, QColor +from PySide6.QtWidgets import ( + QFrame, + QHBoxLayout, + QLabel, + QListWidget, + QListWidgetItem, + QPushButton, + QSizePolicy, + QSplitter, + QTextBrowser, + QTreeWidget, + QTreeWidgetItem, + QVBoxLayout, + QWidget, +) + +from ..diag.engine import LED, Finding, Report, Severity + +SEV_COLOR = { + Severity.CRITICAL: "#b03a2e", + Severity.ERROR: "#d9534f", + Severity.WARNING: "#c77c02", + Severity.INFO: "#2e6da4", +} +# verdict banner (background, text) by verdict severity; None = healthy +BANNER = { + Severity.CRITICAL: ("#f6d5d1", "#7b1e14"), + Severity.ERROR: ("#f9dedc", "#8a2019"), + Severity.WARNING: ("#fdebc8", "#6b4400"), + None: ("#d8f0dd", "#1b5e2a"), +} +_KEY = Qt.ItemDataRole.UserRole +BOX_STATE = {"ok": ("#1b7a33", "✔"), "warn": ("#c77c02", "!"), "bad": ("#b03a2e", "✖"), "info": ("#2e6da4", "i")} + + +def _heading(text: str) -> QLabel: + lbl = QLabel(text) + f = lbl.font() + f.setBold(True) + lbl.setFont(f) + return lbl + + +def _esc(s: str) -> str: + return s.replace("&", "&").replace("<", "<").replace(">", ">") + + +class DiagnosticsPanel(QWidget): + def __init__(self, parent: QWidget | None = None): + super().__init__(parent) + self.setMinimumWidth(380) + + self.verdict = QLabel(wordWrap=True) + self.verdict.setMargin(10) + vf = self.verdict.font() + vf.setPointSize(vf.pointSize() + 2) + vf.setBold(True) + self.verdict.setFont(vf) + + self.led_swatch = QLabel() + self.led_swatch.setFixedSize(22, 22) + self.led_text = QLabel(wordWrap=True) + led_row = QHBoxLayout() + led_row.addWidget(self.led_swatch, 0, Qt.AlignmentFlag.AlignTop) + led_row.addWidget(self.led_text, 1) + + self.firmware = QLabel(wordWrap=True) + self.firmware.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + + self.box_title = _heading("Box self-report (KOTA lines)") + self.box = QLabel(wordWrap=True) + self.box.setTextFormat(Qt.TextFormat.RichText) + self.box.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + + self.milestones = QListWidget() + self.milestones.setFrameShape(QFrame.Shape.NoFrame) + self.milestones.setSelectionMode(QListWidget.SelectionMode.NoSelection) + self.milestones.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum) + + self.tree = QTreeWidget() + self.tree.setHeaderHidden(True) + self.tree.setIndentation(16) + self.tree.setTextElideMode(Qt.TextElideMode.ElideRight) + self.detail = QTextBrowser() + self.detail.setOpenExternalLinks(False) + findings = QSplitter(Qt.Orientation.Vertical) + findings.addWidget(self.tree) + findings.addWidget(self.detail) + findings.setSizes([220, 260]) + + lay = QVBoxLayout(self) + lay.setContentsMargins(4, 0, 0, 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(self.milestones) + lay.addWidget(_heading("Findings - root causes first; click one for details")) + lay.addWidget(findings, 1) + + self._findings: dict[str, Finding] = {} + self._selected: str | None = None + # key -> expanded, chosen by the user; survives the rebuild on refresh. + self._expanded: dict[str, bool] = {} + self.tree.currentItemChanged.connect(self._on_select) + self.tree.itemCollapsed.connect(lambda it: self._remember(it, False)) + self.tree.itemExpanded.connect(lambda it: self._remember(it, True)) + + # ------------------------------------------------------------------ + def show_report(self, r: Report) -> None: + bg, fg = BANNER.get(r.verdict_severity, BANNER[Severity.WARNING]) + if r.lines == 0: + bg, fg = "#e6e6e6", "#333333" + self.verdict.setText(r.verdict) + self.verdict.setStyleSheet(f"QLabel {{ background:{bg}; color:{fg}; border-radius:6px; }}") + + name, colour = LED[r.led] + self.led_swatch.setStyleSheet( + f"QLabel {{ background:{colour}; border:1px solid #777; border-radius:11px; }}") + ask = "" if r.led == "UNKNOWN" else " - check it matches the box" + pattern = f", {_esc(r.led_pattern)}" if r.led_pattern else "" + meaning = f" = {_esc(r.led_meaning)}" if r.led_meaning else "" + source = " (reported by the box)" if r.led_exact else "" + reason = f"
    from: {_esc(r.led_reason)}" if r.led_reason else "" + self.led_text.setText(f"{name}{pattern}{meaning}{source}{ask}{reason}") + + fw = r.firmware + sd = {True: "reads Wi-Fi settings from the SD card", + False: "does not read Wi-Fi settings from the SD card", + None: "Wi-Fi-from-SD support unknown"} + self.firmware.setText(f"{_esc(fw.describe())}
    {sd[fw.supports_sd_wifi]}") + + self.box_title.setVisible(bool(r.box)) + self.box.setVisible(bool(r.box)) + self.box.setText("" + "".join( + f"" + f"" + for topic, text, st in r.box) + "
    {BOX_STATE[st][1]} {_esc(topic)} {_esc(text)}
    ") + + self.milestones.clear() + for _, label, t in r.milestones: + 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"))) + self.milestones.addItem(it) + rows = min(max(self.milestones.count(), 1), 15) + self.milestones.setFixedHeight(rows * max(self.milestones.sizeHintForRow(0), 16) + 6) + + self._fill_tree(r) + + def _fill_tree(self, r: Report) -> None: + self.tree.blockSignals(True) + self.tree.clear() + self._findings = {} + if not r.roots: + it = QTreeWidgetItem(["No problems detected in the log so far."]) + it.setForeground(0, QBrush(QColor("#1b7a33"))) + self.tree.addTopLevelItem(it) + for rc in r.roots: + top = self._item(rc.finding, bold=True) + self.tree.addTopLevelItem(top) + for c in rc.consequences: + top.addChild(self._item(c, prefix="↳ ")) + top.setExpanded(self._expanded.get(rc.finding.key, True)) + for title, key, items in (("Seen in earlier boots only", "grp:earlier", r.earlier), + ("Resolved during this session", "grp:resolved", r.resolved)): + if not items: + continue + grp = QTreeWidgetItem([f"{title} ({len(items)})"]) + grp.setData(0, _KEY, key) + grp.setForeground(0, QBrush(QColor("#666666"))) + self.tree.addTopLevelItem(grp) + for f in items: + grp.addChild(self._item(f, prefix="✔ " if key == "grp:resolved" else "")) + grp.setExpanded(self._expanded.get(key, False)) + # Keep the selection across refreshes; default to the top root cause. + target = self._selected if self._selected in self._findings else ( + r.roots[0].finding.key if r.roots else None) + self.tree.blockSignals(False) + self._select(target) + + def _remember(self, item: QTreeWidgetItem, expanded: bool) -> None: + if key := item.data(0, _KEY): + self._expanded[key] = expanded + + def _item(self, f: Finding, prefix: str = "", bold: bool = False) -> QTreeWidgetItem: + it = QTreeWidgetItem([f"{prefix}[{f.severity.label}] {f.title}"]) + it.setData(0, _KEY, f.key) + it.setForeground(0, QBrush(QColor(SEV_COLOR.get(f.severity, "#333333")))) + it.setToolTip(0, f.title) + if bold: + font = it.font(0) + font.setBold(True) + it.setFont(0, font) + self._findings[f.key] = f + return it + + def _select(self, key: str | None) -> None: + if key is None: + self.detail.setHtml("

    Nothing to show yet.

    ") + return + match = self.tree.findItems("*", Qt.MatchFlag.MatchWildcard | Qt.MatchFlag.MatchRecursive) + for it in match: + if it.data(0, _KEY) == key: + self.tree.setCurrentItem(it) + return + + def _on_select(self, cur: QTreeWidgetItem | None, _prev) -> None: + key = cur.data(0, _KEY) if cur else None + f = self._findings.get(key or "") + if f is None: + return + self._selected = key + self.detail.setHtml(_detail_html(f)) + + +def _detail_html(f: Finding) -> str: + colour = SEV_COLOR.get(f.severity, "#333") + actions = "".join(f"
  • {_esc(a)}
  • " for a in f.actions) + seen = _esc(f.seen_text()) + evidence = "
    ".join(_esc(e) for e in f.evidence) + codes = "".join(f"
  • {_esc(eid)}" + (f" - {_esc(a)}" if a else "") + "
  • " + for eid, a in f.error_ids.items()) + return ( + f"

    {_esc(f.title)}

    " + f"

    What it means: {_esc(f.meaning)}

    " + + (f"

    What to do:

      {actions}
    " if actions else "") + + (f"

    Firmware error codes (error_ids.csv):

      {codes}
    " if codes else "") + + f"

    {seen}

    " + + (f"

    {evidence}

    " + if evidence else "") + ) diff --git a/kota_tech/ui/flash_tab.py b/kota_tech/ui/flash_tab.py index b963f33..1accf88 100644 --- a/kota_tech/ui/flash_tab.py +++ b/kota_tech/ui/flash_tab.py @@ -1,364 +1,364 @@ -"""Flash firmware tab: only the .bin files in the tool's firmware/ folder, -chosen by their compile date; App-only by default.""" - -from __future__ import annotations - -import datetime as dt -import tempfile -from pathlib import Path - -from PySide6.QtCore import QObject, Qt, QTimer, QUrl, Signal -from PySide6.QtGui import QDesktopServices -from PySide6.QtWidgets import ( - QButtonGroup, - QCheckBox, - QComboBox, - QGroupBox, - QHBoxLayout, - QLabel, - QMessageBox, - QPlainTextEdit, - QProgressBar, - QPushButton, - QRadioButton, - QVBoxLayout, - QWidget, -) - -from ..core.session import SESSIONS_DIR -from ..diag.firmware import local_build, register_local_build, same_build -from ..flash.image import FIRMWARE_DIR, FirmwareImage, folder_images -from ..flash.plan import Mode, build_plan -from ..flash.runner import FlashRunner, Result -from ..serialio.ports import PortInfo, list_serial_ports - -BACKUP_DIR = SESSIONS_DIR.parent / "backups" -VERIFY_TIMEOUT_MS = 45_000 - -HELP = ( - "If flashing cannot connect: use a data-capable USB cable directly on the laptop, close " - "other programs using the port, then retry. Last resort (case must be opened): hold BOOT, " - "press and release RESET, release BOOT, and press Start again." -) - - -class _Bridge(QObject): - step = Signal(int, str) - progress = Signal(int) - output = Signal(str) - done = Signal(object) - - -class FlashTab(QWidget): - def __init__(self, serial_tab, parent: QWidget | None = None): - super().__init__(parent) - self.serial_tab = serial_tab - self._images: list[FirmwareImage] = [] - self._ports: list[PortInfo] = [] # before any signal can call _update() - self._runner: FlashRunner | None = None - self._port: PortInfo | None = None - self._image: FirmwareImage | None = None - - # 1. box - box = QGroupBox("1. Sensorbox") - self.port_combo = QComboBox(minimumWidth=360) - refresh = QPushButton("Refresh") - refresh.clicked.connect(self.refresh_ports) - self.box_fw = QLabel(wordWrap=True) - bl = QVBoxLayout(box) - row = QHBoxLayout() - row.addWidget(self.port_combo) - row.addWidget(refresh) - row.addStretch(1) - bl.addLayout(row) - bl.addWidget(self.box_fw) - - # 2. firmware - only files in the tool's firmware/ folder - fwbox = QGroupBox("2. Firmware (by compile date)") - self.image_combo = QComboBox(minimumWidth=360) - self.image_combo.setToolTip(f"Firmware files in {FIRMWARE_DIR}") - self.reload_btn = QPushButton("Refresh list") - self.reload_btn.setToolTip("Read the firmware folder again (after the firmware team added a file)") - self.reload_btn.clicked.connect(lambda: self._load_images(self._selected_sha())) - self.folder_btn = QPushButton("Open firmware folder") - self.folder_btn.clicked.connect( - lambda: QDesktopServices.openUrl(QUrl.fromLocalFile(str(FIRMWARE_DIR)))) - self.image_info = QLabel(wordWrap=True) - self.image_info.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) - fl = QVBoxLayout(fwbox) - fl.addWidget(self.image_combo) - brow = QHBoxLayout() - brow.addWidget(self.reload_btn) - brow.addWidget(self.folder_btn) - brow.addStretch(1) - fl.addLayout(brow) - fl.addWidget(self.image_info) - self._problems: list[str] = [] - - # 3. mode - modebox = QGroupBox("3. What to write") - self.rb_app = QRadioButton("App only (recommended for repairs)") - self.rb_full = QRadioButton("Full image (new or completely wiped boards)") - self.mode_group = QButtonGroup(self) - self.mode_group.addButton(self.rb_app) - self.mode_group.addButton(self.rb_full) - self.rb_app.setChecked(True) - app_note = QLabel("Replaces the firmware only. Keeps the Matter pairing, the OTA registration and the " - "settings stored on the box.", wordWrap=True) - full_note = QLabel("Writes everything like the factory. Erases the Matter pairing and the OTA " - "registration - the box must be paired again afterwards.", wordWrap=True) - for n in (app_note, full_note): - n.setContentsMargins(24, 0, 0, 6) - n.setStyleSheet("color:#555") - self.confirm_full = QCheckBox("I understand the pairing and OTA registration will be erased") - self.backup = QCheckBox("Back up the box's complete flash first (8 MB, about 3 minutes)") - ml = QVBoxLayout(modebox) - for w in (self.rb_app, app_note, self.rb_full, full_note, self.confirm_full, self.backup): - ml.addWidget(w) - - # 4. run - runbox = QGroupBox("4. Flash") - self.plan_label = QLabel(wordWrap=True) - self.start_btn = QPushButton("Start flashing") - self.start_btn.setMinimumHeight(36) - self.start_btn.clicked.connect(self._start) - self.step_label = QLabel() - self.bar = QProgressBar() - self.result = QLabel(wordWrap=True) - self.result.setMargin(8) - self.result.setTextFormat(Qt.TextFormat.RichText) - self.details_btn = QPushButton("Show esptool output") - self.details_btn.setCheckable(True) - self.output = QPlainTextEdit(readOnly=True) - self.output.setVisible(False) - self.details_btn.toggled.connect(self.output.setVisible) - rl = QVBoxLayout(runbox) - for w in (self.plan_label, self.start_btn, self.step_label, self.bar, self.result, self.details_btn, self.output): - rl.addWidget(w) - help_lbl = QLabel(HELP, wordWrap=True) - help_lbl.setStyleSheet("color:#555") - rl.addWidget(help_lbl) - rl.addStretch(1) # keep the run column packed at the top - - left = QVBoxLayout() - for w in (box, fwbox, modebox): - left.addWidget(w) - left.addStretch(1) - lay = QHBoxLayout(self) - lay.addLayout(left, 1) - lay.addWidget(runbox, 1) - - self._bridge = _Bridge() - self._bridge.step.connect(self._on_step) - self._bridge.progress.connect(self.bar.setValue) - self._bridge.output.connect(self.output.appendPlainText) - self._bridge.done.connect(self._on_done) - self._verify_timer = QTimer(self, interval=500) - self._verify_timer.timeout.connect(self._check_verify) - - for sig in (self.rb_app.toggled, self.confirm_full.toggled, self.backup.toggled): - sig.connect(self._update) - self.rb_full.toggled.connect(lambda on: self.backup.setChecked(on)) # backup default on for Full - self.image_combo.currentIndexChanged.connect(self._update) - self.port_combo.currentIndexChanged.connect(self._update) - - self._load_images() - self.refresh_ports() - self._show_result("", None) - - # ------------------------------------------------------------ setup - def _load_images(self, select_sha: str | None = None) -> None: - self._images, self._problems = folder_images() - for img in self._images: # lets the Serial monitor show a box build's compile date - register_local_build(img.app.elf_sha, img.app.version, img.app.built, img.name) - self.image_combo.blockSignals(True) - self.image_combo.clear() - for img in self._images: - self.image_combo.addItem(img.label()) - if not self._images: - self.image_combo.addItem("No firmware file in the firmware folder") - idx = next((i for i, im in enumerate(self._images) if im.sha256 == select_sha), 0) - self.image_combo.setCurrentIndex(idx) # default: newest compile date - self.image_combo.blockSignals(False) - self._update() - - def _selected_sha(self) -> str | None: - img = self._selected_image() - return img.sha256 if img else None - - def refresh_ports(self) -> None: - current = self.port_combo.currentData() - self._ports = list_serial_ports() - self.port_combo.clear() - for p in self._ports: - self.port_combo.addItem(p.label, p.device) - if not self._ports: - self.port_combo.addItem("No sensorbox found - check USB cable and power switch", None) - self.port_combo.setCurrentIndex(max(self.port_combo.findData(current), 0)) - self._update() - - def showEvent(self, event): # noqa: N802 - refresh when the tab is opened - self.refresh_ports() - super().showEvent(event) - - # ------------------------------------------------------------ state - def _selected_image(self) -> FirmwareImage | None: - i = self.image_combo.currentIndex() - return self._images[i] if 0 <= i < len(self._images) else None - - def _selected_port(self) -> PortInfo | None: - dev = self.port_combo.currentData() - return next((p for p in self._ports if p.device == dev), None) - - def _update(self) -> None: - img = self._selected_image() - info = "" - if img: - a = img.app - kind = "complete image (bootloader + layout + app)" if img.kind == "merged" else "app only" - info = (f"Compiled {a.built} · {a.project} v{a.version}, ESP-IDF {a.idf}, {kind}
    " - f"file firmware/{img.name} · build {a.elf_sha[:8]} · " - f"SHA-256 {img.sha256[:16]}... · checksum OK") - else: - info = (f"Put the firmware .bin file from the firmware team into
    {FIRMWARE_DIR}
    " - "and press Refresh list. Only files in this folder can be flashed.") - if self._problems: - info += ("
    Not offered: " - + "; ".join(self._problems) + "") - self.image_info.setText(info) - # An app-only file has no bootloader/layout: Full mode is impossible. - full_ok = bool(img and img.kind == "merged") - self.rb_full.setEnabled(full_ok and self._runner is None) - if not full_ok and self.rb_full.isChecked(): - self.rb_app.setChecked(True) - self.box_fw.setText(self._box_text(img)) - - self.confirm_full.setVisible(self.rb_full.isChecked()) - ok = bool(img and self._selected_port() and self._runner is None - and (self.rb_app.isChecked() or self.confirm_full.isChecked())) - self.start_btn.setEnabled(ok) - if img and self._selected_port(): - mode = Mode.APP_ONLY if self.rb_app.isChecked() else Mode.FULL - steps = ["Back up the complete flash (~3 min)"] if self.backup.isChecked() else [] - if mode == Mode.APP_ONLY: - steps += ["Check the box has the same partition layout", "Write the firmware app (~20 s)"] - else: - steps += ["Write the complete image (~25 s)"] - steps += ["Restart the box and verify the new firmware boots"] - self.plan_label.setText("Plan:
      " + "".join(f"
    1. {s}
    2. " for s in steps) + "
    ") - else: - self.plan_label.setText("Select a sensorbox and a firmware image.") - - def _box_text(self, img: FirmwareImage | None) -> str: - fw = self.serial_tab.engine.firmware - if not fw.elf_sha: - return "Firmware on the box: unknown - connect in the Serial monitor and press Reset & capture to see it." - msg = f"Box currently runs: {fw.describe()}" - if not img: - return msg - if same_build(fw.elf_sha, img.app.elf_sha): - return msg + "
    It already has this firmware. Re-flash only if you suspect the firmware is damaged." - local = local_build(fw.elf_sha) - box_built, img_built = _parse_built(fw.built or (local[1] if local else None)), img.app.built_at - if box_built and img_built and img_built > box_built: - return msg + f"
    The selected firmware is newer (built {img.app.built})." - if box_built and img_built and img_built < box_built: - return msg + ("
    The selected firmware is OLDER than the box's (the box was " - "probably updated over OTA). The box updates itself again once online.") - return msg + "
    The box runs a different build." - - # ------------------------------------------------------------ flash - def _start(self) -> None: - img, port = self._selected_image(), self._selected_port() - if not img or not port: - return - mode = Mode.APP_ONLY if self.rb_app.isChecked() else Mode.FULL - what = ("the firmware app (pairing and settings are kept)" if mode == Mode.APP_ONLY - else "the COMPLETE image (pairing and OTA registration are erased)") - if QMessageBox.question(self, "Flash firmware", - f"Write {what}\n\nFirmware: v{img.app.version}, compiled {img.app.built}\n" - f"File: firmware/{img.name}\nBox: {port.label}\n\n" - "Keep the box powered and the USB cable connected until it finishes.", - QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel - ) != QMessageBox.StandardButton.Ok: - return - backup_to = None - if self.backup.isChecked(): - BACKUP_DIR.mkdir(parents=True, exist_ok=True) - stamp = dt.datetime.now().strftime("%Y%m%d_%H%M%S") - backup_to = BACKUP_DIR / f"{(port.mac or port.device).replace(':', '')}_{stamp}.bin" - plan = build_plan(img, mode, port.device, Path(tempfile.mkdtemp(prefix="kota_flash_")), backup_to) - - self._port, self._image = port, img - self.serial_tab.release_port() # esptool needs the port exclusively - self.output.clear() - self.output.appendPlainText("\n".join(plan.summary())) - self._show_result("", None) - self._runner = FlashRunner(plan, self._bridge.step.emit, self._bridge.progress.emit, - self._bridge.output.emit, self._bridge.done.emit) - self._set_busy(True) - self._runner.start() - - def _set_busy(self, busy: bool) -> None: - for w in (self.port_combo, self.image_combo, self.rb_app, self.backup, self.confirm_full, - self.reload_btn): - w.setEnabled(not busy) - self._update() - - def _on_step(self, i: int, title: str) -> None: - n = len(self._runner.plan.steps) if self._runner else 0 - self.step_label.setText(f"Step {i + 1} of {n + 1}: {title}") - - def _on_done(self, res: Result) -> None: - self._runner = None - if not res.ok: - self._set_busy(False) - self.step_label.setText("Stopped.") - hint = f"

    {res.hint}

    " if res.hint else "" - self._show_result(f"Flashing failed. {res.message}{hint}", "error") - self.details_btn.setChecked(True) - return - self.step_label.setText("Final step: restarting the box and checking which firmware it boots...") - self.bar.setValue(0) - self._show_result("Firmware written. Waiting for the box to start...", "info") - self.serial_tab.connect_port(self._port, capture=True) - self._verify_ms = 0 - self._verify_timer.start() - - def _check_verify(self) -> None: - self._verify_ms += self._verify_timer.interval() - self.bar.setValue(min(100, int(100 * self._verify_ms / VERIFY_TIMEOUT_MS))) - fw = self.serial_tab.engine.firmware - want = self._image.app.elf_sha[:8] - done = same_build(fw.elf_sha, self._image.app.elf_sha) - if done or self._verify_ms >= VERIFY_TIMEOUT_MS: - self._verify_timer.stop() - self._set_busy(False) - self.bar.setValue(100) - if done: - self.step_label.setText("Done.") - self._show_result(f"Success. The box boots the new firmware (build {want}). " - "Check the Diagnosis panel in the Serial monitor.", "ok") - else: - seen = fw.elf_sha[:8] if fw.elf_sha else "no boot seen" - self._show_result(f"Written, but not verified. Expected build {want}, saw: {seen}. " - "Open the Serial monitor and press Reset & capture.", "warning") - - def _show_result(self, html: str, kind: str | None) -> None: - colours = {"ok": ("#2e9e44", "#e3f4e7"), "info": ("#2e6da4", "#e3edf7"), - "warning": ("#c77c02", "#fdf0d6"), "error": ("#d9534f", "#f9e0df")} - self.result.setVisible(bool(html)) - if html: - border, bg = colours[kind or "info"] - self.result.setStyleSheet(f"QLabel {{ border:2px solid {border}; background:{bg}; border-radius:6px; }}") - self.result.setText(html) - - -def _parse_built(s: str | None) -> dt.datetime | None: - """'Jun 29 2026 11:03:32' (app_init "Compile time") -> datetime.""" - if not s: - return None - try: - return dt.datetime.strptime(" ".join(s.split()), "%b %d %Y %H:%M:%S") - except ValueError: - return None +"""Flash firmware tab: only the .bin files in the tool's firmware/ folder, +chosen by their compile date; App-only by default.""" + +from __future__ import annotations + +import datetime as dt +import tempfile +from pathlib import Path + +from PySide6.QtCore import QObject, Qt, QTimer, QUrl, Signal +from PySide6.QtGui import QDesktopServices +from PySide6.QtWidgets import ( + QButtonGroup, + QCheckBox, + QComboBox, + QGroupBox, + QHBoxLayout, + QLabel, + QMessageBox, + QPlainTextEdit, + QProgressBar, + QPushButton, + QRadioButton, + QVBoxLayout, + QWidget, +) + +from ..core.session import SESSIONS_DIR +from ..diag.firmware import local_build, register_local_build, same_build +from ..flash.image import FIRMWARE_DIR, FirmwareImage, folder_images +from ..flash.plan import Mode, build_plan +from ..flash.runner import FlashRunner, Result +from ..serialio.ports import PortInfo, list_serial_ports + +BACKUP_DIR = SESSIONS_DIR.parent / "backups" +VERIFY_TIMEOUT_MS = 45_000 + +HELP = ( + "If flashing cannot connect: use a data-capable USB cable directly on the laptop, close " + "other programs using the port, then retry. Last resort (case must be opened): hold BOOT, " + "press and release RESET, release BOOT, and press Start again." +) + + +class _Bridge(QObject): + step = Signal(int, str) + progress = Signal(int) + output = Signal(str) + done = Signal(object) + + +class FlashTab(QWidget): + def __init__(self, serial_tab, parent: QWidget | None = None): + super().__init__(parent) + self.serial_tab = serial_tab + self._images: list[FirmwareImage] = [] + self._ports: list[PortInfo] = [] # before any signal can call _update() + self._runner: FlashRunner | None = None + self._port: PortInfo | None = None + self._image: FirmwareImage | None = None + + # 1. box + box = QGroupBox("1. Sensorbox") + self.port_combo = QComboBox(minimumWidth=360) + refresh = QPushButton("Refresh") + refresh.clicked.connect(self.refresh_ports) + self.box_fw = QLabel(wordWrap=True) + bl = QVBoxLayout(box) + row = QHBoxLayout() + row.addWidget(self.port_combo) + row.addWidget(refresh) + row.addStretch(1) + bl.addLayout(row) + bl.addWidget(self.box_fw) + + # 2. firmware - only files in the tool's firmware/ folder + fwbox = QGroupBox("2. Firmware (by compile date)") + self.image_combo = QComboBox(minimumWidth=360) + self.image_combo.setToolTip(f"Firmware files in {FIRMWARE_DIR}") + self.reload_btn = QPushButton("Refresh list") + self.reload_btn.setToolTip("Read the firmware folder again (after the firmware team added a file)") + self.reload_btn.clicked.connect(lambda: self._load_images(self._selected_sha())) + self.folder_btn = QPushButton("Open firmware folder") + self.folder_btn.clicked.connect( + lambda: QDesktopServices.openUrl(QUrl.fromLocalFile(str(FIRMWARE_DIR)))) + self.image_info = QLabel(wordWrap=True) + self.image_info.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + fl = QVBoxLayout(fwbox) + fl.addWidget(self.image_combo) + brow = QHBoxLayout() + brow.addWidget(self.reload_btn) + brow.addWidget(self.folder_btn) + brow.addStretch(1) + fl.addLayout(brow) + fl.addWidget(self.image_info) + self._problems: list[str] = [] + + # 3. mode + modebox = QGroupBox("3. What to write") + self.rb_app = QRadioButton("App only (recommended for repairs)") + self.rb_full = QRadioButton("Full image (new or completely wiped boards)") + self.mode_group = QButtonGroup(self) + self.mode_group.addButton(self.rb_app) + self.mode_group.addButton(self.rb_full) + self.rb_app.setChecked(True) + app_note = QLabel("Replaces the firmware only. Keeps the Matter pairing, the OTA registration and the " + "settings stored on the box.", wordWrap=True) + full_note = QLabel("Writes everything like the factory. Erases the Matter pairing and the OTA " + "registration - the box must be paired again afterwards.", wordWrap=True) + for n in (app_note, full_note): + n.setContentsMargins(24, 0, 0, 6) + n.setStyleSheet("color:#555") + self.confirm_full = QCheckBox("I understand the pairing and OTA registration will be erased") + self.backup = QCheckBox("Back up the box's complete flash first (8 MB, about 3 minutes)") + ml = QVBoxLayout(modebox) + for w in (self.rb_app, app_note, self.rb_full, full_note, self.confirm_full, self.backup): + ml.addWidget(w) + + # 4. run + runbox = QGroupBox("4. Flash") + self.plan_label = QLabel(wordWrap=True) + self.start_btn = QPushButton("Start flashing") + self.start_btn.setMinimumHeight(36) + self.start_btn.clicked.connect(self._start) + self.step_label = QLabel() + self.bar = QProgressBar() + self.result = QLabel(wordWrap=True) + self.result.setMargin(8) + self.result.setTextFormat(Qt.TextFormat.RichText) + self.details_btn = QPushButton("Show esptool output") + self.details_btn.setCheckable(True) + self.output = QPlainTextEdit(readOnly=True) + self.output.setVisible(False) + self.details_btn.toggled.connect(self.output.setVisible) + rl = QVBoxLayout(runbox) + for w in (self.plan_label, self.start_btn, self.step_label, self.bar, self.result, self.details_btn, self.output): + rl.addWidget(w) + help_lbl = QLabel(HELP, wordWrap=True) + help_lbl.setStyleSheet("color:#555") + rl.addWidget(help_lbl) + rl.addStretch(1) # keep the run column packed at the top + + left = QVBoxLayout() + for w in (box, fwbox, modebox): + left.addWidget(w) + left.addStretch(1) + lay = QHBoxLayout(self) + lay.addLayout(left, 1) + lay.addWidget(runbox, 1) + + self._bridge = _Bridge() + self._bridge.step.connect(self._on_step) + self._bridge.progress.connect(self.bar.setValue) + self._bridge.output.connect(self.output.appendPlainText) + self._bridge.done.connect(self._on_done) + self._verify_timer = QTimer(self, interval=500) + self._verify_timer.timeout.connect(self._check_verify) + + for sig in (self.rb_app.toggled, self.confirm_full.toggled, self.backup.toggled): + sig.connect(self._update) + self.rb_full.toggled.connect(lambda on: self.backup.setChecked(on)) # backup default on for Full + self.image_combo.currentIndexChanged.connect(self._update) + self.port_combo.currentIndexChanged.connect(self._update) + + self._load_images() + self.refresh_ports() + self._show_result("", None) + + # ------------------------------------------------------------ setup + def _load_images(self, select_sha: str | None = None) -> None: + self._images, self._problems = folder_images() + for img in self._images: # lets the Serial monitor show a box build's compile date + register_local_build(img.app.elf_sha, img.app.version, img.app.built, img.name) + self.image_combo.blockSignals(True) + self.image_combo.clear() + for img in self._images: + self.image_combo.addItem(img.label()) + if not self._images: + self.image_combo.addItem("No firmware file in the firmware folder") + idx = next((i for i, im in enumerate(self._images) if im.sha256 == select_sha), 0) + self.image_combo.setCurrentIndex(idx) # default: newest compile date + self.image_combo.blockSignals(False) + self._update() + + def _selected_sha(self) -> str | None: + img = self._selected_image() + return img.sha256 if img else None + + def refresh_ports(self) -> None: + current = self.port_combo.currentData() + self._ports = list_serial_ports() + self.port_combo.clear() + for p in self._ports: + self.port_combo.addItem(p.label, p.device) + if not self._ports: + self.port_combo.addItem("No sensorbox found - check USB cable and power switch", None) + self.port_combo.setCurrentIndex(max(self.port_combo.findData(current), 0)) + self._update() + + def showEvent(self, event): # noqa: N802 - refresh when the tab is opened + self.refresh_ports() + super().showEvent(event) + + # ------------------------------------------------------------ state + def _selected_image(self) -> FirmwareImage | None: + i = self.image_combo.currentIndex() + return self._images[i] if 0 <= i < len(self._images) else None + + def _selected_port(self) -> PortInfo | None: + dev = self.port_combo.currentData() + return next((p for p in self._ports if p.device == dev), None) + + def _update(self) -> None: + img = self._selected_image() + info = "" + if img: + a = img.app + kind = "complete image (bootloader + layout + app)" if img.kind == "merged" else "app only" + info = (f"Compiled {a.built} · {a.project} v{a.version}, ESP-IDF {a.idf}, {kind}
    " + f"file firmware/{img.name} · build {a.elf_sha[:8]} · " + f"SHA-256 {img.sha256[:16]}... · checksum OK") + else: + info = (f"Put the firmware .bin file from the firmware team into
    {FIRMWARE_DIR}
    " + "and press Refresh list. Only files in this folder can be flashed.") + if self._problems: + info += ("
    Not offered: " + + "; ".join(self._problems) + "") + self.image_info.setText(info) + # An app-only file has no bootloader/layout: Full mode is impossible. + full_ok = bool(img and img.kind == "merged") + self.rb_full.setEnabled(full_ok and self._runner is None) + if not full_ok and self.rb_full.isChecked(): + self.rb_app.setChecked(True) + self.box_fw.setText(self._box_text(img)) + + self.confirm_full.setVisible(self.rb_full.isChecked()) + ok = bool(img and self._selected_port() and self._runner is None + and (self.rb_app.isChecked() or self.confirm_full.isChecked())) + self.start_btn.setEnabled(ok) + if img and self._selected_port(): + mode = Mode.APP_ONLY if self.rb_app.isChecked() else Mode.FULL + steps = ["Back up the complete flash (~3 min)"] if self.backup.isChecked() else [] + if mode == Mode.APP_ONLY: + steps += ["Check the box has the same partition layout", "Write the firmware app (~20 s)"] + else: + steps += ["Write the complete image (~25 s)"] + steps += ["Restart the box and verify the new firmware boots"] + self.plan_label.setText("Plan:
      " + "".join(f"
    1. {s}
    2. " for s in steps) + "
    ") + else: + self.plan_label.setText("Select a sensorbox and a firmware image.") + + def _box_text(self, img: FirmwareImage | None) -> str: + fw = self.serial_tab.engine.firmware + if not fw.elf_sha: + return "Firmware on the box: unknown - connect in the Serial monitor and press Reset & capture to see it." + msg = f"Box currently runs: {fw.describe()}" + if not img: + return msg + if same_build(fw.elf_sha, img.app.elf_sha): + return msg + "
    It already has this firmware. Re-flash only if you suspect the firmware is damaged." + local = local_build(fw.elf_sha) + box_built, img_built = _parse_built(fw.built or (local[1] if local else None)), img.app.built_at + if box_built and img_built and img_built > box_built: + return msg + f"
    The selected firmware is newer (built {img.app.built})." + if box_built and img_built and img_built < box_built: + return msg + ("
    The selected firmware is OLDER than the box's (the box was " + "probably updated over OTA). The box updates itself again once online.") + return msg + "
    The box runs a different build." + + # ------------------------------------------------------------ flash + def _start(self) -> None: + img, port = self._selected_image(), self._selected_port() + if not img or not port: + return + mode = Mode.APP_ONLY if self.rb_app.isChecked() else Mode.FULL + what = ("the firmware app (pairing and settings are kept)" if mode == Mode.APP_ONLY + else "the COMPLETE image (pairing and OTA registration are erased)") + if QMessageBox.question(self, "Flash firmware", + f"Write {what}\n\nFirmware: v{img.app.version}, compiled {img.app.built}\n" + f"File: firmware/{img.name}\nBox: {port.label}\n\n" + "Keep the box powered and the USB cable connected until it finishes.", + QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel + ) != QMessageBox.StandardButton.Ok: + return + backup_to = None + if self.backup.isChecked(): + BACKUP_DIR.mkdir(parents=True, exist_ok=True) + stamp = dt.datetime.now().strftime("%Y%m%d_%H%M%S") + backup_to = BACKUP_DIR / f"{(port.mac or port.device).replace(':', '')}_{stamp}.bin" + plan = build_plan(img, mode, port.device, Path(tempfile.mkdtemp(prefix="kota_flash_")), backup_to) + + self._port, self._image = port, img + self.serial_tab.release_port() # esptool needs the port exclusively + self.output.clear() + self.output.appendPlainText("\n".join(plan.summary())) + self._show_result("", None) + self._runner = FlashRunner(plan, self._bridge.step.emit, self._bridge.progress.emit, + self._bridge.output.emit, self._bridge.done.emit) + self._set_busy(True) + self._runner.start() + + def _set_busy(self, busy: bool) -> None: + for w in (self.port_combo, self.image_combo, self.rb_app, self.backup, self.confirm_full, + self.reload_btn): + w.setEnabled(not busy) + self._update() + + def _on_step(self, i: int, title: str) -> None: + n = len(self._runner.plan.steps) if self._runner else 0 + self.step_label.setText(f"Step {i + 1} of {n + 1}: {title}") + + def _on_done(self, res: Result) -> None: + self._runner = None + if not res.ok: + self._set_busy(False) + self.step_label.setText("Stopped.") + hint = f"

    {res.hint}

    " if res.hint else "" + self._show_result(f"Flashing failed. {res.message}{hint}", "error") + self.details_btn.setChecked(True) + return + self.step_label.setText("Final step: restarting the box and checking which firmware it boots...") + self.bar.setValue(0) + self._show_result("Firmware written. Waiting for the box to start...", "info") + self.serial_tab.connect_port(self._port, capture=True) + self._verify_ms = 0 + self._verify_timer.start() + + def _check_verify(self) -> None: + self._verify_ms += self._verify_timer.interval() + self.bar.setValue(min(100, int(100 * self._verify_ms / VERIFY_TIMEOUT_MS))) + fw = self.serial_tab.engine.firmware + want = self._image.app.elf_sha[:8] + done = same_build(fw.elf_sha, self._image.app.elf_sha) + if done or self._verify_ms >= VERIFY_TIMEOUT_MS: + self._verify_timer.stop() + self._set_busy(False) + self.bar.setValue(100) + if done: + self.step_label.setText("Done.") + self._show_result(f"Success. The box boots the new firmware (build {want}). " + "Check the Diagnosis panel in the Serial monitor.", "ok") + else: + seen = fw.elf_sha[:8] if fw.elf_sha else "no boot seen" + self._show_result(f"Written, but not verified. Expected build {want}, saw: {seen}. " + "Open the Serial monitor and press Reset & capture.", "warning") + + def _show_result(self, html: str, kind: str | None) -> None: + colours = {"ok": ("#2e9e44", "#e3f4e7"), "info": ("#2e6da4", "#e3edf7"), + "warning": ("#c77c02", "#fdf0d6"), "error": ("#d9534f", "#f9e0df")} + self.result.setVisible(bool(html)) + if html: + border, bg = colours[kind or "info"] + self.result.setStyleSheet(f"QLabel {{ border:2px solid {border}; background:{bg}; border-radius:6px; }}") + self.result.setText(html) + + +def _parse_built(s: str | None) -> dt.datetime | None: + """'Jun 29 2026 11:03:32' (app_init "Compile time") -> datetime.""" + if not s: + return None + try: + return dt.datetime.strptime(" ".join(s.split()), "%b %d %Y %H:%M:%S") + except ValueError: + return None diff --git a/kota_tech/ui/main_window.py b/kota_tech/ui/main_window.py index 6ca784b..2dd9359 100644 --- a/kota_tech/ui/main_window.py +++ b/kota_tech/ui/main_window.py @@ -1,424 +1,424 @@ -from __future__ import annotations - -from pathlib import Path - -from PySide6.QtCore import QCoreApplication, QObject, QSettings, Qt, QTimer, QUrl, Signal -from PySide6.QtGui import QColor, QDesktopServices, QFont, QGuiApplication, QTextCharFormat, QTextCursor -from PySide6.QtWidgets import ( - QCheckBox, - QComboBox, - QFileDialog, - QHBoxLayout, - QInputDialog, - QLabel, - QMainWindow, - QMessageBox, - QPlainTextEdit, - QPushButton, - QSplitter, - QTabWidget, - QTextBrowser, - QVBoxLayout, - QWidget, -) - -from .. import __version__ -from ..core.logfile import read_log_file -from ..core.logline import LineKind, LogLine, host_event -from ..core.session import SessionLog -from ..diag.engine import DiagnosticsEngine -from ..diag.guides import GuideRunner -from ..serialio.manager import SerialManager, State -from ..serialio.ports import PortInfo, list_serial_ports -from ..matter.payload import SetupPayload, payload_from_log -from ..service.report import DEFAULT_BASE, normalize_mac, report_url -from .cheatsheet import cheatsheet_html -from .diag_panel import DiagnosticsPanel -from .flash_tab import FlashTab -from .matter_qr import MatterQrDialog -from .guide_panel import GuidePanel -from .sd_tab import SdTab - -MAX_LINES = 20000 -# "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"}} -COLORS = { - "E": "#ff6b6b", - "W": "#f5c451", - "I": "#d4d4d4", - LineKind.BOOT: "#6fc3ff", - LineKind.RESET: "#6fc3ff", - LineKind.PANIC: "#ff4dd2", - LineKind.HOST: "#7ee07e", - LineKind.KOTA: "#5fd7e8", - LineKind.RAW: "#a8a8a8", -} - - -class _Bridge(QObject): - """Marshals SerialManager callbacks (reader thread) onto the UI thread.""" - - line = Signal(object) - state = Signal(object, str) - - -class SerialTab(QWidget): - def __init__(self, parent: QWidget | None = None): - super().__init__(parent) - self._bridge = _Bridge() - self._bridge.line.connect(self._on_line) - self._bridge.state.connect(self._on_state) - self._manager = SerialManager(self._bridge.line.emit, self._bridge.state.emit) - self._session = SessionLog() - self._ports: list[PortInfo] = [] - self._counts = {"E": 0, "W": 0, "boots": 0} - self._engine = DiagnosticsEngine() - self._diag_dirty = True - self._runner = GuideRunner(self._engine, on_action=self._guide_action, on_log=self._guide_log) - - # --- controls - self.port_combo = QComboBox(minimumWidth=320) - self.all_ports = QCheckBox("Show all ports") - self.refresh_btn = QPushButton("Refresh") - 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.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) - self.view.setFont(mono) - self.view.setStyleSheet("QPlainTextEdit { background:#1e1e1e; color:#d4d4d4; }") - - 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(self.guide, "Guided troubleshooting") - split = QSplitter(Qt.Orientation.Horizontal) - split.addWidget(self.view) - split.addWidget(self.side_tabs) - split.setStretchFactor(0, 3) - split.setStretchFactor(1, 2) - split.setSizes([700, 480]) - - self.status = QLabel() - layout = QVBoxLayout(self) - layout.addLayout(row1) - layout.addLayout(row2) - 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.connect_btn.clicked.connect(self._toggle_connect) - self.reset_btn.clicked.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.diag.qr_btn.clicked.connect(lambda: MatterQrDialog(self.matter_payload(), self).exec()) - - # 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) - self._diag_timer.start() - - # Auto-detect a box being plugged in while idle. - self._poll = QTimer(self, interval=2000) - self._poll.timeout.connect(self._auto_refresh) - self._poll.start() - - self.refresh_ports() - self._on_state(State.DISCONNECTED, "") - - # ---- ports ------------------------------------------------------- - def refresh_ports(self) -> None: - current = self.port_combo.currentData() - self._ports = list_serial_ports(include_all=self.all_ports.isChecked()) - self.port_combo.clear() - for p in self._ports: - self.port_combo.addItem(p.label, p.device) - if not self._ports: - self.port_combo.addItem("No sensorbox found - check USB cable and power switch", None) - idx = self.port_combo.findData(current) - self.port_combo.setCurrentIndex(max(idx, 0)) - - def _auto_refresh(self) -> None: - if self._manager.is_running: - return - devices = [p.device for p in list_serial_ports(self.all_ports.isChecked())] - if devices != [p.device for p in self._ports]: - self.refresh_ports() - - def _selected_port(self) -> PortInfo | None: - dev = self.port_combo.currentData() - return next((p for p in self._ports if p.device == dev), None) - - # ---- connection -------------------------------------------------- - def _toggle_connect(self) -> None: - if self._manager.is_running: - # Session file is closed in _on_state(DISCONNECTED), after the - # reader thread's final queued lines have been written. - self._manager.disconnect() - return - port = self._selected_port() - if port is None: - return - self._connect(port) - - def _connect(self, port: PortInfo) -> None: - self._counts = {"E": 0, "W": 0, "boots": 0} - self._engine.reset() - self._runner.cancel() - self._diag_dirty = True - self._session.start(port.mac or port.device) - self._active_port = port - self._manager.connect(port) - - 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 [], - self._engine.matter_qr, self._engine.matter_manual) - - # ---- service system ------------------------------------------------- - def _service_base(self) -> str: - return str(self._settings.value("service/base", DEFAULT_BASE)) - - def _open_service_report(self, auto: bool) -> None: - mac = normalize_mac(self._engine.mac or (self._active_port.mac if self._active_port else "") or "") - if len(mac) != 12: - if not auto: - QMessageBox.information(self, "Service report", "The box's MAC address is not known yet. " - "Connect to the box and press Reset & capture first.") - return - url = report_url(mac, self._engine.report(), self._service_base()) - QDesktopServices.openUrl(QUrl(url)) - # Log that it happened, not the report itself (it is already in the session log). - self._guide_log(f"service report for {mac} opened in the browser - check it and press Save there") - - # ---- hand-off with the flash tab ------------------------------------ - @property - def engine(self) -> DiagnosticsEngine: - return self._engine - - def release_port(self) -> None: - """Stop monitoring so esptool can open the port exclusively.""" - if self._manager.is_running: - self._no_auto_service = True # a flash hand-off is not the end of a session - self._manager.disconnect() # joins the reader thread - QCoreApplication.processEvents() # deliver its queued final lines/state now - self._no_auto_service = False - - def connect_port(self, port: PortInfo, capture: bool = True) -> None: - """Monitor `port`; with capture, restart the box once connected to record its boot.""" - self.release_port() - idx = self.port_combo.findData(port.device) - if idx < 0: - self.port_combo.addItem(port.label, port.device) - self._ports.append(port) - idx = self.port_combo.count() - 1 - self.port_combo.setCurrentIndex(idx) - self._reset_on_connect = capture - self._connect(port) - - def _on_state(self, state: State, detail: str) -> None: - running = state != State.DISCONNECTED - if not running and self._manager.is_running: - return # stale event from a session that was already replaced - if state == State.CONNECTED and getattr(self, "_reset_on_connect", False): - self._reset_on_connect = False - QTimer.singleShot(300, self._manager.reset_board) - if not running and self._session.path is not None: - self._session.write_report(self._engine.report().to_text()) - if (self.auto_service.isChecked() and self._engine.lines - and not getattr(self, "_no_auto_service", False)): - self._open_service_report(auto=True) - self._session.stop() - self.connect_btn.setText("Disconnect" if running else "Connect") - self.reset_btn.setEnabled(state == State.CONNECTED) - self.port_combo.setEnabled(not running) - self.all_ports.setEnabled(not running) - self.refresh_btn.setEnabled(not running) - # Offline analysis would mix with the live session; disconnect first. - self.analyze_btn.setEnabled(not running) - self._state_text = f"{state.value}" + (f" ({detail})" if detail else "") - self._update_status() - - def _update_status(self) -> None: - sess = f" | Session log: {self._session.path}" if self._session.path else "" - c = self._counts - self.status.setText( - f"{getattr(self, '_state_text', '')} | boots seen: {c['boots']} " - f"errors: {c['E']} warnings: {c['W']}{sess}" - ) - - # ---- lines ------------------------------------------------------- - def _on_line(self, line: LogLine) -> None: - self._session.write(line) - self._engine.feed(line) - self._runner.feed(line) - self._diag_dirty = True - if line.kind == LineKind.BOOT: - self._counts["boots"] += 1 - if line.level in ("E", "W"): - self._counts[line.level] += 1 - self._update_status() - - if self._visible(line): - self._append(line) - - def _visible(self, line: LogLine) -> bool: - allowed = LEVEL_FILTERS[self.filter_combo.currentText()] - if allowed is None or line.kind in (LineKind.HOST, LineKind.PANIC, LineKind.BOOT, LineKind.RESET): - return True - if allowed == "KOTA": - return line.kind == LineKind.KOTA or line.level == "E" - return line.level in allowed - - def _append(self, line: LogLine) -> None: - sb = self.view.verticalScrollBar() - at_bottom = sb.value() >= sb.maximum() - 4 - fmt = QTextCharFormat() - colour = COLORS.get(line.kind) if line.kind == LineKind.KOTA and line.level == "I" else None - fmt.setForeground(QColor(colour or COLORS.get(line.level) or COLORS.get(line.kind, "#d4d4d4"))) - if line.kind in (LineKind.PANIC, LineKind.BOOT, LineKind.HOST) or line.record in ("boot", "status"): - fmt.setFontWeight(QFont.Weight.Bold) - cur = QTextCursor(self.view.document()) - cur.movePosition(QTextCursor.MoveOperation.End) - if not self.view.document().isEmpty(): - cur.insertBlock() - cur.insertText(line.text, fmt) - if at_bottom: - sb.setValue(sb.maximum()) - - # ---- diagnostics ------------------------------------------------- - def _refresh_diag(self) -> None: - if self._diag_dirty: - self._diag_dirty = False - self.diag.show_report(self._engine.report()) - self.guide.refresh() # countdowns and timeouts need a steady tick - - # ---- guided troubleshooting hooks ------------------------------------ - def _guide_action(self, action: str) -> bool: - if action == "reset" and self._manager.state == State.CONNECTED: - self._manager.reset_board() - return True - return False - - def _guide_log(self, text: str) -> None: - # Technician answers go into the session log and the log view. - line = host_event(text) - self._session.write(line) - self._append(line) - self._diag_dirty = True - - def _copy_report(self) -> None: - QGuiApplication.clipboard().setText(self._engine.report().to_text()) - - def _analyze_file(self) -> None: - path, _ = QFileDialog.getOpenFileName( - self, "Analyze log file", str(self._session.root), "Logs (*.log *.txt);;All files (*)") - if not path: - return - lines = read_log_file(Path(path)) - self.view.clear() - self._engine.reset() - self._runner.cancel() - self._counts = {"E": 0, "W": 0, "boots": 0} - for line in lines: - self._engine.feed(line) - if line.kind == LineKind.BOOT: - self._counts["boots"] += 1 - if line.level in ("E", "W"): - self._counts[line.level] += 1 - self._append(line) - self._state_text = f"Analyzed file: {Path(path).name} ({len(lines)} lines)" - self._update_status() - self._diag_dirty = True - self._refresh_diag() - - # ---- misc -------------------------------------------------------- - def _save_view(self) -> None: - path, _ = QFileDialog.getSaveFileName(self, "Save log view", "sensorbox_log.txt", "Text (*.txt)") - if path: - with open(path, "w", encoding="utf-8") as fh: - fh.write(self.view.toPlainText()) - - def _open_folder(self) -> None: - self._session.root.mkdir(parents=True, exist_ok=True) - QDesktopServices.openUrl(QUrl.fromLocalFile(str(self._session.root))) - - def shutdown(self) -> None: - self._manager.disconnect() - self._session.stop() - - -class MainWindow(QMainWindow): - def __init__(self): - super().__init__() - self.setWindowTitle(f"KOTA Sensorbox Technician Tool v{__version__}") - self.resize(1200, 800) - tabs = QTabWidget() - guide = QTextBrowser(openExternalLinks=True) - guide.setHtml(cheatsheet_html()) - self.serial_tab = SerialTab() - self.flash_tab = FlashTab(self.serial_tab) - tabs.addTab(guide, "Quick start") - tabs.addTab(self.serial_tab, "Serial monitor") - tabs.addTab(self.flash_tab, "Flash firmware") - self.sd_tab = SdTab(self.serial_tab) - tabs.addTab(self.sd_tab, "SD card") - self.setCentralWidget(tabs) - settings_menu = self.menuBar().addMenu("Settings") - settings_menu.addAction("Service system address...", self._edit_service_base) - - def _edit_service_base(self) -> None: - st = self.serial_tab - text, ok = QInputDialog.getText( - self, "Service system address", - "Address of the KOTA service system (the box MAC is added at the end):", - text=st._service_base()) - if ok and text.strip().startswith(("https://", "http://")): - st._settings.setValue("service/base", text.strip()) - elif ok: - QMessageBox.warning(self, "Service system address", "The address must start with https://") - - def closeEvent(self, event): # noqa: N802 (Qt naming) - self.serial_tab.shutdown() - super().closeEvent(event) +from __future__ import annotations + +from pathlib import Path + +from PySide6.QtCore import QCoreApplication, QObject, QSettings, Qt, QTimer, QUrl, Signal +from PySide6.QtGui import QColor, QDesktopServices, QFont, QGuiApplication, QTextCharFormat, QTextCursor +from PySide6.QtWidgets import ( + QCheckBox, + QComboBox, + QFileDialog, + QHBoxLayout, + QInputDialog, + QLabel, + QMainWindow, + QMessageBox, + QPlainTextEdit, + QPushButton, + QSplitter, + QTabWidget, + QTextBrowser, + QVBoxLayout, + QWidget, +) + +from .. import __version__ +from ..core.logfile import read_log_file +from ..core.logline import LineKind, LogLine, host_event +from ..core.session import SessionLog +from ..diag.engine import DiagnosticsEngine +from ..diag.guides import GuideRunner +from ..serialio.manager import SerialManager, State +from ..serialio.ports import PortInfo, list_serial_ports +from ..matter.payload import SetupPayload, payload_from_log +from ..service.report import DEFAULT_BASE, normalize_mac, report_url +from .cheatsheet import cheatsheet_html +from .diag_panel import DiagnosticsPanel +from .flash_tab import FlashTab +from .matter_qr import MatterQrDialog +from .guide_panel import GuidePanel +from .sd_tab import SdTab + +MAX_LINES = 20000 +# "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"}} +COLORS = { + "E": "#ff6b6b", + "W": "#f5c451", + "I": "#d4d4d4", + LineKind.BOOT: "#6fc3ff", + LineKind.RESET: "#6fc3ff", + LineKind.PANIC: "#ff4dd2", + LineKind.HOST: "#7ee07e", + LineKind.KOTA: "#5fd7e8", + LineKind.RAW: "#a8a8a8", +} + + +class _Bridge(QObject): + """Marshals SerialManager callbacks (reader thread) onto the UI thread.""" + + line = Signal(object) + state = Signal(object, str) + + +class SerialTab(QWidget): + def __init__(self, parent: QWidget | None = None): + super().__init__(parent) + self._bridge = _Bridge() + self._bridge.line.connect(self._on_line) + self._bridge.state.connect(self._on_state) + self._manager = SerialManager(self._bridge.line.emit, self._bridge.state.emit) + self._session = SessionLog() + self._ports: list[PortInfo] = [] + self._counts = {"E": 0, "W": 0, "boots": 0} + self._engine = DiagnosticsEngine() + self._diag_dirty = True + self._runner = GuideRunner(self._engine, on_action=self._guide_action, on_log=self._guide_log) + + # --- controls + self.port_combo = QComboBox(minimumWidth=320) + self.all_ports = QCheckBox("Show all ports") + self.refresh_btn = QPushButton("Refresh") + 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.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) + self.view.setFont(mono) + self.view.setStyleSheet("QPlainTextEdit { background:#1e1e1e; color:#d4d4d4; }") + + 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(self.guide, "Guided troubleshooting") + split = QSplitter(Qt.Orientation.Horizontal) + split.addWidget(self.view) + split.addWidget(self.side_tabs) + split.setStretchFactor(0, 3) + split.setStretchFactor(1, 2) + split.setSizes([700, 480]) + + self.status = QLabel() + layout = QVBoxLayout(self) + layout.addLayout(row1) + layout.addLayout(row2) + 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.connect_btn.clicked.connect(self._toggle_connect) + self.reset_btn.clicked.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.diag.qr_btn.clicked.connect(lambda: MatterQrDialog(self.matter_payload(), self).exec()) + + # 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) + self._diag_timer.start() + + # Auto-detect a box being plugged in while idle. + self._poll = QTimer(self, interval=2000) + self._poll.timeout.connect(self._auto_refresh) + self._poll.start() + + self.refresh_ports() + self._on_state(State.DISCONNECTED, "") + + # ---- ports ------------------------------------------------------- + def refresh_ports(self) -> None: + current = self.port_combo.currentData() + self._ports = list_serial_ports(include_all=self.all_ports.isChecked()) + self.port_combo.clear() + for p in self._ports: + self.port_combo.addItem(p.label, p.device) + if not self._ports: + self.port_combo.addItem("No sensorbox found - check USB cable and power switch", None) + idx = self.port_combo.findData(current) + self.port_combo.setCurrentIndex(max(idx, 0)) + + def _auto_refresh(self) -> None: + if self._manager.is_running: + return + devices = [p.device for p in list_serial_ports(self.all_ports.isChecked())] + if devices != [p.device for p in self._ports]: + self.refresh_ports() + + def _selected_port(self) -> PortInfo | None: + dev = self.port_combo.currentData() + return next((p for p in self._ports if p.device == dev), None) + + # ---- connection -------------------------------------------------- + def _toggle_connect(self) -> None: + if self._manager.is_running: + # Session file is closed in _on_state(DISCONNECTED), after the + # reader thread's final queued lines have been written. + self._manager.disconnect() + return + port = self._selected_port() + if port is None: + return + self._connect(port) + + def _connect(self, port: PortInfo) -> None: + self._counts = {"E": 0, "W": 0, "boots": 0} + self._engine.reset() + self._runner.cancel() + self._diag_dirty = True + self._session.start(port.mac or port.device) + self._active_port = port + self._manager.connect(port) + + 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 [], + self._engine.matter_qr, self._engine.matter_manual) + + # ---- service system ------------------------------------------------- + def _service_base(self) -> str: + return str(self._settings.value("service/base", DEFAULT_BASE)) + + def _open_service_report(self, auto: bool) -> None: + mac = normalize_mac(self._engine.mac or (self._active_port.mac if self._active_port else "") or "") + if len(mac) != 12: + if not auto: + QMessageBox.information(self, "Service report", "The box's MAC address is not known yet. " + "Connect to the box and press Reset & capture first.") + return + url = report_url(mac, self._engine.report(), self._service_base()) + QDesktopServices.openUrl(QUrl(url)) + # Log that it happened, not the report itself (it is already in the session log). + self._guide_log(f"service report for {mac} opened in the browser - check it and press Save there") + + # ---- hand-off with the flash tab ------------------------------------ + @property + def engine(self) -> DiagnosticsEngine: + return self._engine + + def release_port(self) -> None: + """Stop monitoring so esptool can open the port exclusively.""" + if self._manager.is_running: + self._no_auto_service = True # a flash hand-off is not the end of a session + self._manager.disconnect() # joins the reader thread + QCoreApplication.processEvents() # deliver its queued final lines/state now + self._no_auto_service = False + + def connect_port(self, port: PortInfo, capture: bool = True) -> None: + """Monitor `port`; with capture, restart the box once connected to record its boot.""" + self.release_port() + idx = self.port_combo.findData(port.device) + if idx < 0: + self.port_combo.addItem(port.label, port.device) + self._ports.append(port) + idx = self.port_combo.count() - 1 + self.port_combo.setCurrentIndex(idx) + self._reset_on_connect = capture + self._connect(port) + + def _on_state(self, state: State, detail: str) -> None: + running = state != State.DISCONNECTED + if not running and self._manager.is_running: + return # stale event from a session that was already replaced + if state == State.CONNECTED and getattr(self, "_reset_on_connect", False): + self._reset_on_connect = False + QTimer.singleShot(300, self._manager.reset_board) + if not running and self._session.path is not None: + self._session.write_report(self._engine.report().to_text()) + if (self.auto_service.isChecked() and self._engine.lines + and not getattr(self, "_no_auto_service", False)): + self._open_service_report(auto=True) + self._session.stop() + self.connect_btn.setText("Disconnect" if running else "Connect") + self.reset_btn.setEnabled(state == State.CONNECTED) + self.port_combo.setEnabled(not running) + self.all_ports.setEnabled(not running) + self.refresh_btn.setEnabled(not running) + # Offline analysis would mix with the live session; disconnect first. + self.analyze_btn.setEnabled(not running) + self._state_text = f"{state.value}" + (f" ({detail})" if detail else "") + self._update_status() + + def _update_status(self) -> None: + sess = f" | Session log: {self._session.path}" if self._session.path else "" + c = self._counts + self.status.setText( + f"{getattr(self, '_state_text', '')} | boots seen: {c['boots']} " + f"errors: {c['E']} warnings: {c['W']}{sess}" + ) + + # ---- lines ------------------------------------------------------- + def _on_line(self, line: LogLine) -> None: + self._session.write(line) + self._engine.feed(line) + self._runner.feed(line) + self._diag_dirty = True + if line.kind == LineKind.BOOT: + self._counts["boots"] += 1 + if line.level in ("E", "W"): + self._counts[line.level] += 1 + self._update_status() + + if self._visible(line): + self._append(line) + + def _visible(self, line: LogLine) -> bool: + allowed = LEVEL_FILTERS[self.filter_combo.currentText()] + if allowed is None or line.kind in (LineKind.HOST, LineKind.PANIC, LineKind.BOOT, LineKind.RESET): + return True + if allowed == "KOTA": + return line.kind == LineKind.KOTA or line.level == "E" + return line.level in allowed + + def _append(self, line: LogLine) -> None: + sb = self.view.verticalScrollBar() + at_bottom = sb.value() >= sb.maximum() - 4 + fmt = QTextCharFormat() + colour = COLORS.get(line.kind) if line.kind == LineKind.KOTA and line.level == "I" else None + fmt.setForeground(QColor(colour or COLORS.get(line.level) or COLORS.get(line.kind, "#d4d4d4"))) + if line.kind in (LineKind.PANIC, LineKind.BOOT, LineKind.HOST) or line.record in ("boot", "status"): + fmt.setFontWeight(QFont.Weight.Bold) + cur = QTextCursor(self.view.document()) + cur.movePosition(QTextCursor.MoveOperation.End) + if not self.view.document().isEmpty(): + cur.insertBlock() + cur.insertText(line.text, fmt) + if at_bottom: + sb.setValue(sb.maximum()) + + # ---- diagnostics ------------------------------------------------- + def _refresh_diag(self) -> None: + if self._diag_dirty: + self._diag_dirty = False + self.diag.show_report(self._engine.report()) + self.guide.refresh() # countdowns and timeouts need a steady tick + + # ---- guided troubleshooting hooks ------------------------------------ + def _guide_action(self, action: str) -> bool: + if action == "reset" and self._manager.state == State.CONNECTED: + self._manager.reset_board() + return True + return False + + def _guide_log(self, text: str) -> None: + # Technician answers go into the session log and the log view. + line = host_event(text) + self._session.write(line) + self._append(line) + self._diag_dirty = True + + def _copy_report(self) -> None: + QGuiApplication.clipboard().setText(self._engine.report().to_text()) + + def _analyze_file(self) -> None: + path, _ = QFileDialog.getOpenFileName( + self, "Analyze log file", str(self._session.root), "Logs (*.log *.txt);;All files (*)") + if not path: + return + lines = read_log_file(Path(path)) + self.view.clear() + self._engine.reset() + self._runner.cancel() + self._counts = {"E": 0, "W": 0, "boots": 0} + for line in lines: + self._engine.feed(line) + if line.kind == LineKind.BOOT: + self._counts["boots"] += 1 + if line.level in ("E", "W"): + self._counts[line.level] += 1 + self._append(line) + self._state_text = f"Analyzed file: {Path(path).name} ({len(lines)} lines)" + self._update_status() + self._diag_dirty = True + self._refresh_diag() + + # ---- misc -------------------------------------------------------- + def _save_view(self) -> None: + path, _ = QFileDialog.getSaveFileName(self, "Save log view", "sensorbox_log.txt", "Text (*.txt)") + if path: + with open(path, "w", encoding="utf-8") as fh: + fh.write(self.view.toPlainText()) + + def _open_folder(self) -> None: + self._session.root.mkdir(parents=True, exist_ok=True) + QDesktopServices.openUrl(QUrl.fromLocalFile(str(self._session.root))) + + def shutdown(self) -> None: + self._manager.disconnect() + self._session.stop() + + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle(f"KOTA Sensorbox Technician Tool v{__version__}") + self.resize(1200, 800) + tabs = QTabWidget() + guide = QTextBrowser(openExternalLinks=True) + guide.setHtml(cheatsheet_html()) + self.serial_tab = SerialTab() + self.flash_tab = FlashTab(self.serial_tab) + tabs.addTab(guide, "Quick start") + tabs.addTab(self.serial_tab, "Serial monitor") + tabs.addTab(self.flash_tab, "Flash firmware") + self.sd_tab = SdTab(self.serial_tab) + tabs.addTab(self.sd_tab, "SD card") + self.setCentralWidget(tabs) + settings_menu = self.menuBar().addMenu("Settings") + settings_menu.addAction("Service system address...", self._edit_service_base) + + def _edit_service_base(self) -> None: + st = self.serial_tab + text, ok = QInputDialog.getText( + self, "Service system address", + "Address of the KOTA service system (the box MAC is added at the end):", + text=st._service_base()) + if ok and text.strip().startswith(("https://", "http://")): + st._settings.setValue("service/base", text.strip()) + elif ok: + QMessageBox.warning(self, "Service system address", "The address must start with https://") + + def closeEvent(self, event): # noqa: N802 (Qt naming) + self.serial_tab.shutdown() + super().closeEvent(event) diff --git a/kota_tech/ui/matter_qr.py b/kota_tech/ui/matter_qr.py index 18e2f2e..3b5d214 100644 --- a/kota_tech/ui/matter_qr.py +++ b/kota_tech/ui/matter_qr.py @@ -1,76 +1,76 @@ -"""Matter pairing QR code on screen, so the technician can scan it from the laptop.""" - -from __future__ import annotations - -import segno -from PySide6.QtCore import Qt -from PySide6.QtGui import QColor, QImage, QPainter, QPixmap -from PySide6.QtWidgets import QDialog, QDialogButtonBox, QLabel, QVBoxLayout, QWidget - -from ..matter.payload import SetupPayload - - -def qr_pixmap(text: str, size: int = 260) -> QPixmap: - """Black-on-white QR with the quiet zone phones need (4 modules).""" - qr = segno.make(text, error="m") - matrix = [list(row) for row in qr.matrix_iter(scale=1, border=4)] - n = len(matrix) - module = max(1, size // n) - img = QImage(n * module, n * module, QImage.Format.Format_RGB32) - img.fill(QColor("white")) - p = QPainter(img) - p.setPen(Qt.PenStyle.NoPen) - p.setBrush(QColor("black")) - for y, row in enumerate(matrix): - for x, dark in enumerate(row): - if dark: - p.drawRect(x * module, y * module, module, module) - p.end() - return QPixmap.fromImage(img) - - -class MatterQrWidget(QWidget): - def __init__(self, payload: SetupPayload, size: int = 260, parent: QWidget | None = None): - super().__init__(parent) - self.image = QLabel() - self.image.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.code = QLabel() - self.code.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.code.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) - f = self.code.font() - f.setPointSize(f.pointSize() + 4) - f.setBold(True) - self.code.setFont(f) - self.note = QLabel(wordWrap=True) - self.note.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.note.setStyleSheet("color:#555") - lay = QVBoxLayout(self) - lay.addWidget(self.image) - lay.addWidget(self.code) - lay.addWidget(self.note) - self._size = size - self.set_payload(payload) - - def set_payload(self, payload: SetupPayload) -> None: - self.payload = payload - self.image.setPixmap(qr_pixmap(payload.qr_text(), self._size)) - self.code.setText(f"Manual code: {payload.manual_code_pretty()}") - if payload.from_box: - src = "QR and manual code exactly as reported by the box (KOTA: matter line)" - else: - src = f"Discriminator {payload.discriminator} " + ( - "(from this box's log)" if payload.from_log else - "(standard code of the v1.0 firmware - connect the box to confirm)") - self.note.setText(f"Scan with the Matter app (Apple Home, Google Home, ...), or type the manual code. " - f"{src}. Same as the sticker on the box.") - - -class MatterQrDialog(QDialog): - def __init__(self, payload: SetupPayload, parent: QWidget | None = None): - super().__init__(parent) - self.setWindowTitle("Matter pairing code") - lay = QVBoxLayout(self) - lay.addWidget(MatterQrWidget(payload, 320)) - buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) - buttons.rejected.connect(self.reject) - lay.addWidget(buttons) +"""Matter pairing QR code on screen, so the technician can scan it from the laptop.""" + +from __future__ import annotations + +import segno +from PySide6.QtCore import Qt +from PySide6.QtGui import QColor, QImage, QPainter, QPixmap +from PySide6.QtWidgets import QDialog, QDialogButtonBox, QLabel, QVBoxLayout, QWidget + +from ..matter.payload import SetupPayload + + +def qr_pixmap(text: str, size: int = 260) -> QPixmap: + """Black-on-white QR with the quiet zone phones need (4 modules).""" + qr = segno.make(text, error="m") + matrix = [list(row) for row in qr.matrix_iter(scale=1, border=4)] + n = len(matrix) + module = max(1, size // n) + img = QImage(n * module, n * module, QImage.Format.Format_RGB32) + img.fill(QColor("white")) + p = QPainter(img) + p.setPen(Qt.PenStyle.NoPen) + p.setBrush(QColor("black")) + for y, row in enumerate(matrix): + for x, dark in enumerate(row): + if dark: + p.drawRect(x * module, y * module, module, module) + p.end() + return QPixmap.fromImage(img) + + +class MatterQrWidget(QWidget): + def __init__(self, payload: SetupPayload, size: int = 260, parent: QWidget | None = None): + super().__init__(parent) + self.image = QLabel() + self.image.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.code = QLabel() + self.code.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.code.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) + f = self.code.font() + f.setPointSize(f.pointSize() + 4) + f.setBold(True) + self.code.setFont(f) + self.note = QLabel(wordWrap=True) + self.note.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.note.setStyleSheet("color:#555") + lay = QVBoxLayout(self) + lay.addWidget(self.image) + lay.addWidget(self.code) + lay.addWidget(self.note) + self._size = size + self.set_payload(payload) + + def set_payload(self, payload: SetupPayload) -> None: + self.payload = payload + self.image.setPixmap(qr_pixmap(payload.qr_text(), self._size)) + self.code.setText(f"Manual code: {payload.manual_code_pretty()}") + if payload.from_box: + src = "QR and manual code exactly as reported by the box (KOTA: matter line)" + else: + src = f"Discriminator {payload.discriminator} " + ( + "(from this box's log)" if payload.from_log else + "(standard code of the v1.0 firmware - connect the box to confirm)") + self.note.setText(f"Scan with the Matter app (Apple Home, Google Home, ...), or type the manual code. " + f"{src}. Same as the sticker on the box.") + + +class MatterQrDialog(QDialog): + def __init__(self, payload: SetupPayload, parent: QWidget | None = None): + super().__init__(parent) + self.setWindowTitle("Matter pairing code") + lay = QVBoxLayout(self) + lay.addWidget(MatterQrWidget(payload, 320)) + buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) + buttons.rejected.connect(self.reject) + lay.addWidget(buttons) diff --git a/tests/fixtures/live_boot_A4CB8FC946F8_fw1.0.16_uncommissioned.txt b/tests/fixtures/live_boot_A4CB8FC946F8_fw1.0.16_uncommissioned.txt index d1dd4d3..f8454e5 100644 --- a/tests/fixtures/live_boot_A4CB8FC946F8_fw1.0.16_uncommissioned.txt +++ b/tests/fixtures/live_boot_A4CB8FC946F8_fw1.0.16_uncommissioned.txt @@ -1,1064 +1,1064 @@ ---- connected to COM5 --- -W (7411) wifi:Haven't to connect to a suitable AP now! ---- reset issued by tool --- -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x15 (USB_UART_CHIP_RESET),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x4037f35e -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (25) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (28) boot: efuse block revision: v1.4 -I (32) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (43) boot: Enabling RNG early entropy source... -I (48) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (63) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (76) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (89) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (102) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (109) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c190020 size=66d30h (421168) map -I (194) esp_image: segment 1: paddr=000a6d58 vaddr=3fc9c700 size=05d00h ( 23808) load -I (199) esp_image: segment 2: paddr=000aca60 vaddr=40374000 size=035b8h ( 13752) load -I (203) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=1862d0h (1598160) map -I (487) esp_image: segment 4: paddr=002362f8 vaddr=403775b8 size=15054h ( 86100) load -I (506) esp_image: segment 5: paddr=0024b354 vaddr=5000002c size=00020h ( 32) load -I (516) boot: Loaded app from partition at offset 0x40000 -I (517) boot: Disabling RNG early entropy source... -I (527) octal_psram: vendor id : 0x0d (AP) -I (527) octal_psram: dev id : 0x02 (generation 3) -I (528) octal_psram: density : 0x03 (64 Mbit) -I (529) octal_psram: good-die : 0x01 (Pass) -I (534) octal_psram: Latency : 0x01 (Fixed) -I (538) octal_psram: VCC : 0x01 (3V) -I (542) octal_psram: SRF : 0x01 (Fast Refresh) -I (547) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (552) octal_psram: BurstLen : 0x01 (32 Byte) -I (556) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (562) octal_psram: DriveStrength: 0x00 (1/1) -I (566) esp_psram: Found 8MB PSRAM device -I (569) esp_psram: Speed: 40MHz -I (572) cpu_start: Multicore app -I (1306) esp_psram: SPI SRAM memory test OK -I (1314) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1315) cpu_start: Pro cpu start user code -I (1315) cpu_start: cpu freq: 160000000 Hz -I (1317) app_init: Application information: -I (1321) app_init: Project name: kota-sensorboard -I (1326) app_init: App version: 1.0.16 -I (1330) app_init: Compile time: Sep 28 2026 15:07:57 -I (1335) app_init: ELF file SHA256: 4f10989e2... -I (1339) app_init: ESP-IDF: v5.5.3-dirty -I (1344) efuse_init: Min chip rev: v0.0 -I (1347) efuse_init: Max chip rev: v0.99 -I (1352) efuse_init: Chip rev: v0.2 -I (1356) heap_init: Initializing. RAM available for dynamic allocation: -I (1362) heap_init: At 3FCB4AD0 len 00034C40 (211 KiB): RAM -I (1367) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1372) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1378) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1383) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1391) spi_flash: detected chip: generic -I (1393) spi_flash: flash io: dio -I (1399) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1403) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1418) main_task: Started on CPU0 -I (1421) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1429) main_task: Calling app_main() -E (1432) ID: [E-MAIN-04] Device MAC address: A4:CB:8F:C9:46:F8 -I (1458) KOTA: led color=green pattern=solid -I (1659) I2C: Creating I2C mutex -I (1676) KOTA: i2c SDA=1 SCL=1 0x39=AS7341 0x68=DS1307 0x6B=SEN66 0x76=BME688 -I (1677) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1679) SPI: Creating SPI mutex -I (1722) SD_CARD: SD card mounted successfully -I (1723) SD_CARD: /sdcard exists, SD card initialized successfully -I (1788) KOTA: sd type=SDHC size=29.3GB fs=FAT32 free=29.3GB env_keys=INFLUX_TOKEN,INFLUX_URL buffer_lines=764 -I (1792) WIFI_CREDENTIALS: SSID/PASSWORD not set in .env; Matter will be used for WiFi provisioning -I (1802) MAIN: Matter node created successfully -W (1803) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1810) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1818) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1827) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1836) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1845) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1854) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1862) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1871) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1880) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1888) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1897) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1906) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1915) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1924) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1932) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1941) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1950) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1960) pp: pp rom version: e7ae62f -I (1962) net80211: net80211 rom version: e7ae62f -I (1967) wifi:wifi driver task: 3fcc7d40, prio:23, stack:6656, core=0 -I (1985) wifi:wifi firmware version: 4df78f2 -I (1985) wifi:wifi certification version: v7.0 -I (1986) wifi:config NVS flash: enabled -I (1986) wifi:config nano formatting: disabled -I (1990) wifi:Init data frame dynamic rx buffer num: 32 -I (1995) wifi:Init static rx mgmt buffer num: 5 -I (1999) wifi:Init management short buffer num: 32 -I (2004) wifi:Init dynamic tx buffer num: 32 -I (2008) wifi:Init static tx FG buffer num: 2 -I (2012) wifi:Init static rx buffer size: 1600 -I (2016) wifi:Init static rx buffer num: 10 -I (2020) wifi:Init dynamic rx buffer num: 32 -I (2025) wifi_init: rx ba win: 6 -I (2027) wifi_init: accept mbox: 6 -I (2030) wifi_init: tcpip mbox: 32 -I (2033) wifi_init: udp mbox: 6 -I (2036) wifi_init: tcp mbox: 6 -I (2039) wifi_init: tcp tx win: 5760 -I (2042) wifi_init: tcp rx win: 5760 -I (2045) wifi_init: tcp mss: 1440 -I (2057) chip[DL]: NVS set: chip-counters/reboot-count = 15857 (0x3DF1) -I (2060) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (2063) BLE_INIT: BT controller compile version [1bb2f50] -I (2067) BLE_INIT: Using main XTAL as clock source -I (2072) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (2080) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (2085) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -I (2133) NimBLE: GAP procedure initiated: stop advertising. - -I (2135) CHIP[DL]: BLE host-controller synced -I (2635) chip[DL]: Starting ESP WiFi layer -I (2637) wifi:mode : sta (a4:cb:8f:c9:46:f8) -I (2638) wifi:enable tsf -W (2639) wifi:Haven't to connect to a suitable AP now! -I (2639) chip[DL]: -I (2639) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43Done driving station state, nothing else to do... - -W (2652) wifi:Haven't to connect to a suitable AP now! -I (2657) chip[DL]: Done driving station state, nothing else to do... -I (2657) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 -I (2663) chip[SVR]: -Initializing subscription resumption storage... -I (2676) chip[SVR]: Server initializing... -I (2680) chip[TS]: Last Known Good Time: 2026-09-28T13:29:12 -I (2685) chip[DMG]: AccessControl: initializing -I (2688) chip[DMG]: Examples::AccessControlDelegate::Init -I (2693) chip[DMG]: AccessControl: setting -I (2697) chip[DMG]: DefaultAclStorage: initializing -I (2702) chip[DMG]: DefaultAclStorage: 0 entries loaded -I (2707) chip[SVR]: WARNING: mTestEventTriggerDelegate is null -I (2726) chip[ZCL]: Using ZAP configuration... -I (2726) esp_matter_cluster: Cluster plugin init common callback -I (2727) chip[DMG]: AccessControlCluster: initializing -I (2730) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported -I (2740) chip[DIS]: Updating services using commissioning mode 1 -I (2744) chip[DIS]: CHIP minimal mDNS started advertising. -I (2749) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 -I (2759) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: A180A7E5D58D9F80. -I (2769) chip[DIS]: mDNS service published: _matterc._udp -I (2774) chip[IN]: CASE Server enabling CASE session setups -I (2780) chip[SVR]: Joining Multicast groups -I (2783) chip[SVR]: Server Listening... -I (2788) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** -I (2798) data_model: Dynamic endpoint 0 added -E (2801) data_model: Cluster cannot be NULL. -I (2806) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** -I (2816) data_model: Dynamic endpoint 1 added -E (2820) data_model: Cluster cannot be NULL. -I (2825) MAIN: ESP-Matter started successfully -I (2826) chip[DL]: WIFI_EVENT_STA_START -W (2832) wifi:Haven't to connect to a suitable AP now! -I (2837) chip[DL]: Done driving station state, nothing else to do... -I (2843) KOTA: boot fw=1.0.16 build=4f10989e hw=2 mac=A4CB8FC946F8 reset=USB_UART_CHIP_RESET mode=matter commissioned=0 last_restart=none -I (2845) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) -I (2862) NimBLE: GAP procedure initiated: advertise; -I (2866) NimBLE: disc_mode=2 -I (2869) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 -I (2878) NimBLE: - -I (2881) chip[DL]: CHIPoBLE advertising started -I (2886) KOTA: matter fabrics=0 window=open qr=MT:Y.K9042C00KA0648G00 manual=34970112332 -I (2892) MAIN: Commissioning window opened -W (2893) wifi:Haven't to connect to a suitable AP now! -I (2897) KOTA: matter fabrics=0 window=open qr=MT:Y.K9042C00KA0648G00 manual=34970112332 -I (2905) KOTA: led color=cyan pattern=breathe period=3000ms -I (2901) KOTA: health up=1s status=OK wifi=disconnected rssi=0 time_valid=0 sd=ok buffer=0 heap=123883 psram=7.9M last_upload=never ota=unregistered -W (3394) wifi:Haven't to connect to a suitable AP now! -I (3894) KOTA: rtc running=0 time=2000-01-01T00:00:00 -W (4395) wifi:Haven't to connect to a suitable AP now! -W (5396) wifi:Haven't to connect to a suitable AP now! -W (6396) wifi:Haven't to connect to a suitable AP now! -W (7397) wifi:Haven't to connect to a suitable AP now! -W (8398) wifi:Haven't to connect to a suitable AP now! -W (9399) wifi:Haven't to connect to a suitable AP now! -W (10400) wifi:Haven't to connect to a suitable AP now! -W (11401) wifi:Haven't to connect to a suitable AP now! -W (12402) wifi:Haven't to connect to a suitable AP now! -W (13403) wifi:Haven't to connect to a suitable AP now! -I (13895) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -W (13962) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -I (13975) BME688: Delaying for 10000 microseconds -W (14404) wifi:Haven't to connect to a suitable AP now! -I (14591) KOTA: mic enabled=1 (GPIO3=0, expected 0) -I (14593) Flicker: Flicker sensor initialized on pin 14 -I (14594) temperature_sensor: Range [20°C ~ 100°C], error < 2°C -I (14597) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data -W (14601) wifi: -I (14597) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data -Haven't to connect to a suitable AP now! -I (14598) SEN66: Creating sensor mutex - -I (14605) INFLUX_BUFFER: Flushing chunk starting at line 10: -W (14611) AS7341: Reading spectral data from AS7341 -I (14616) SEN66: Waking up sensor by setting power GPIO to enabled level... -I (14617) DeviceStatus: Active tasks: 26 -I (14621) INFLUX: Writing to InfluxDB -I (14641) DeviceStatus: Task list: -main R 1 1132 4 -gather_sensor_t X 1 1480 24 -IDLE1 R 0 700 6 -IDLE0 R 0 692 5 -gather_sensor_t B 1 2552 22 -reset_task B 0 3344 9 -ds1307_update B 1 768 17 -tiT B 18 2420 11 -gather_task B 1 1984 19 -gather_task B 1 14292 20 -gather_sensor_t B 1 1364 25 -gather_sensor_t B 1 1948 23 -ota_task B 1 7504 18 -CHIP B 1 3636 13 -health B 1 928 16 -Tmr Svc B 1 1364 7 -led_task B 5 964 8 -sys_evt B 20 2676 10 -ipc0 S 24 372 1 -ipc1 S 24 524 2 -esp_timer S 22 3332 3 -gather_sensor_t B 1 2544 21 -gather_sensor_t B 1 2552 26 -wifi B 23 4324 12 -btController B 23 1960 14 -nimble_host B 21 1476 15 - -W (14710) DeviceStatus: RTC is uninitialized, using current time as timestamp -W (14973) AS7341: Reading basic counts data from AS7341 -W (14989) AS7341: Reading flicker detection state from AS7341 -W (15206) as7341: FD Status Register: 0x20 (0b00100000) -I (15211) AS7341: Updating Matter attributes with AS7341 data: 14946 -I (15211) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms -I (15212) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** -I (15217) Flicker: Starting to collect samples in gather task -I (15228) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 14946 ********** -E (15245) MAIN: [E-MAIN-03] Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -E (15254) MAIN: [E-MAIN-03] Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -W (15264) AS7341: Formatting AS7341 data in line protocol -AS7341 line protocol: -as7341_adc,device=A4CB8FC946F8 f1=1,f2=2,f3=2,f4=3,f5=3,f6=3,f7=6,f8=4,clear=14,nir=2 946684801000000000 -as7341_basic_counts,device=A4CB8FC946F8 f1=2.23,f2=4.46,f3=4.46,f4=6.69,f5=6.69,f6=6.69,f7=13.39,f8=8.92,clear=31.23,nir=4.46 946684801000000000 -as7341_flicker,device=A4CB8FC946F8 state="unknown" 946684801000000000 -W (15404) wifi:Haven't to connect to a suitable AP now! -W (15717) DeviceStatus: RTC is uninitialized, using current time as timestamp -I (16256) Flicker: Finished collecting samples, notifying main task -I (16257) Flicker: Collected 1024 samples for dominant frequency analysis -I (16264) SENSOR_GATHERER: Sensor should now be asleep -I (16276) BME688: Delaying for 199517 microseconds -I (16276) SENSOR_GATHERER: Sensor should now be asleep -W (16405) wifi:Haven't to connect to a suitable AP now! -I (16714) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** -I (16715) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** -E (16726) MAIN: [E-MAIN-03] Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -I (16717) SENSOR_GATHERER: Sensor should now be asleep -E (16735) MAIN: [E-MAIN-03] Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -W (16750) BME688: RTC is uninitialized, using current time as timestamp -W (17406) wifi:Haven't to connect to a suitable AP now! -W (17756) BME688: RTC is uninitialized, using current time as timestamp -W (18407) wifi:Haven't to connect to a suitable AP now! -W (18756) BME688: RTC is uninitialized, using current time as timestamp -W (19408) wifi:Haven't to connect to a suitable AP now! -I (19633) SEN66: Sent start measurement command, waiting for sensor to be ready... -W (19756) BME688: RTC is uninitialized, using current time as timestamp -W (20409) wifi:Haven't to connect to a suitable AP now! -W (20756) BME688: RTC is uninitialized, using current time as timestamp -W (21410) wifi:Haven't to connect to a suitable AP now! -E (21642) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (21642) esp-tls: Failed to open new connection -E (21645) transport_base: Failed to open a new connection -E (21650) HTTP_CLIENT: Connection failed, sock < 0 -I (21655) INFLUX: HTTP request performed, status code: 0 -E (21659) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT -E (21667) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL -W (21756) BME688: RTC is uninitialized, using current time as timestamp -W (22411) wifi:Haven't to connect to a suitable AP now! -I (22602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (22756) BME688: RTC is uninitialized, using current time as timestamp -I (23102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (23412) wifi:Haven't to connect to a suitable AP now! -I (23602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (23674) INFLUX: Writing to InfluxDB -W (23756) BME688: RTC is uninitialized, using current time as timestamp -I (24102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (24413) wifi:Haven't to connect to a suitable AP now! -I (24596) OTA_CLIENT: Device is not registered, attempting to register... -I (24598) OTA_CLIENT: HTTP client handle: 0x3fcf0fe4 -I (24602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (24738) MICROPHONE: Read 600000 bytes in 9940 ms -I (24738) MICROPHONE: Read 600000 bytes of audio data -W (24756) BME688: RTC is uninitialized, using current time as timestamp -I (25102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (25414) wifi:Haven't to connect to a suitable AP now! -I (25602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (25756) BME688: RTC is uninitialized, using current time as timestamp -I (26102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (26415) wifi:Haven't to connect to a suitable AP now! -I (26602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (26756) BME688: RTC is uninitialized, using current time as timestamp -I (27102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (27416) wifi:Haven't to connect to a suitable AP now! -I (27602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (27756) BME688: RTC is uninitialized, using current time as timestamp -I (28102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (28187) MICROPHONE: Raw block stats: mean=-81.8 min=-41376 max=37946 -I (28188) MICROPHONE: Sum SQ: 0.032256, RMS pressure: 0.000402, Normalized: 0.000402, dB peak: -67.92 -W (28192) SEN66: RTC is uninitialized, using current time as timestamp -W (28417) wifi:Haven't to connect to a suitable AP now! -I (28602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (28756) BME688: RTC is uninitialized, using current time as timestamp -I (29102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (29198) SEN66: RTC is uninitialized, using current time as timestamp -W (29418) wifi:Haven't to connect to a suitable AP now! -I (29602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (29756) BME688: RTC is uninitialized, using current time as timestamp -I (30102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (30198) SEN66: RTC is uninitialized, using current time as timestamp -W (30419) wifi:Haven't to connect to a suitable AP now! -I (30602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -E (30675) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (30675) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (30676) esp-tls: Failed to open new connection -E (30683) esp-tls: Failed to open new connection -E (30687) transport_base: Failed to open a new connection -E (30691) transport_base: Failed to open a new connection -E (30696) HTTP_CLIENT: Connection failed, sock < 0 -E (30701) HTTP_CLIENT: Connection failed, sock < 0 -E (30706) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 -I (30710) INFLUX: HTTP request performed, status code: 0 -E (30719) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT -E (30723) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT -E (30738) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL -W (30756) BME688: RTC is uninitialized, using current time as timestamp -I (31102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (31198) SEN66: RTC is uninitialized, using current time as timestamp -W (31420) wifi:Haven't to connect to a suitable AP now! -I (31602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (31756) BME688: RTC is uninitialized, using current time as timestamp -I (32102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (32198) SEN66: RTC is uninitialized, using current time as timestamp -W (32421) wifi:Haven't to connect to a suitable AP now! -I (32602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (32741) chip[DL]: bleAdv Timeout : Start slow advertisement -I (32743) chip[DL]: Configuring CHIPoBLE advertising (interval 500 ms, connectable) -I (32744) chip[DL]: Device already advertising, stop active advertisement and restart -I (32745) INFLUX: Writing to InfluxDB -I (32751) NimBLE: GAP procedure initiated: stop advertising. - -W (32756) BME688: RTC is uninitialized, using current time as timestamp -I (32760) NimBLE: GAP procedure initiated: advertise; -I (32770) NimBLE: disc_mode=2 -I (32773) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=800 adv_itvl_max=800 -I (32783) NimBLE: - -I (33102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (33198) SEN66: RTC is uninitialized, using current time as timestamp -W (33425) wifi:Haven't to connect to a suitable AP now! -I (33602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (33785) BME688: RTC is uninitialized, using current time as timestamp -I (34102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (34198) SEN66: RTC is uninitialized, using current time as timestamp -W (34426) wifi:Haven't to connect to a suitable AP now! -I (34602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (34785) BME688: RTC is uninitialized, using current time as timestamp -I (35102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (35198) SEN66: RTC is uninitialized, using current time as timestamp -W (35427) wifi:Haven't to connect to a suitable AP now! -I (35602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (35785) BME688: RTC is uninitialized, using current time as timestamp -I (36102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (36198) SEN66: RTC is uninitialized, using current time as timestamp -W (36428) wifi:Haven't to connect to a suitable AP now! -I (36602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (36785) BME688: RTC is uninitialized, using current time as timestamp -I (37102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (37198) SEN66: RTC is uninitialized, using current time as timestamp -W (37429) wifi:Haven't to connect to a suitable AP now! -I (37602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (37785) BME688: RTC is uninitialized, using current time as timestamp -I (38102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (38198) SEN66: RTC is uninitialized, using current time as timestamp -W (38430) wifi:Haven't to connect to a suitable AP now! -I (38602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (38785) BME688: RTC is uninitialized, using current time as timestamp -I (39102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (39198) SEN66: RTC is uninitialized, using current time as timestamp -W (39431) wifi:Haven't to connect to a suitable AP now! -I (39602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -E (39754) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (39754) esp-tls: Failed to open new connection -E (39757) transport_base: Failed to open a new connection -E (39762) HTTP_CLIENT: Connection failed, sock < 0 -I (39766) INFLUX: HTTP request performed, status code: 0 -E (39771) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT -E (39779) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL -W (39785) BME688: RTC is uninitialized, using current time as timestamp -I (40102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (40198) SEN66: RTC is uninitialized, using current time as timestamp -W (40432) wifi:Haven't to connect to a suitable AP now! -I (40602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (40731) OTA_CLIENT: Device is not registered, attempting to register... -I (40733) OTA_CLIENT: HTTP client handle: 0x3fcf02e4 -W (40792) BME688: RTC is uninitialized, using current time as timestamp -I (41102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (41198) SEN66: RTC is uninitialized, using current time as timestamp -W (41433) wifi:Haven't to connect to a suitable AP now! -I (41602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (41786) INFLUX: Writing to InfluxDB -W (41792) BME688: RTC is uninitialized, using current time as timestamp -I (42102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (42198) SEN66: RTC is uninitialized, using current time as timestamp -W (42434) wifi:Haven't to connect to a suitable AP now! -I (42602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (42792) BME688: RTC is uninitialized, using current time as timestamp -I (43102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (43198) SEN66: RTC is uninitialized, using current time as timestamp -W (43435) wifi:Haven't to connect to a suitable AP now! -I (43602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (43792) BME688: RTC is uninitialized, using current time as timestamp -I (44102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (44198) SEN66: RTC is uninitialized, using current time as timestamp -W (44436) wifi:Haven't to connect to a suitable AP now! -I (44602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (44792) BME688: RTC is uninitialized, using current time as timestamp -I (45102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (45198) SEN66: RTC is uninitialized, using current time as timestamp -W (45437) wifi:Haven't to connect to a suitable AP now! -I (45602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (45792) BME688: RTC is uninitialized, using current time as timestamp -I (46102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (46198) SEN66: RTC is uninitialized, using current time as timestamp -W (46438) wifi:Haven't to connect to a suitable AP now! -I (46602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (46792) BME688: RTC is uninitialized, using current time as timestamp -I (47102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (47198) SEN66: RTC is uninitialized, using current time as timestamp -W (47439) wifi:Haven't to connect to a suitable AP now! -I (47602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -E (47734) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (47735) esp-tls: Failed to open new connection -E (47736) transport_base: Failed to open a new connection -E (47742) HTTP_CLIENT: Connection failed, sock < 0 -E (47746) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 -E (47754) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT -W (47792) BME688: RTC is uninitialized, using current time as timestamp -I (48102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (48198) SEN66: RTC is uninitialized, using current time as timestamp -W (48440) wifi:Haven't to connect to a suitable AP now! -I (48602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -E (48734) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (48735) esp-tls: Failed to open new connection -E (48737) transport_base: Failed to open a new connection -E (48742) HTTP_CLIENT: Connection failed, sock < 0 -I (48746) INFLUX: HTTP request performed, status code: 0 -E (48751) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT -E (48759) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL -W (48792) BME688: RTC is uninitialized, using current time as timestamp -I (49102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (49198) SEN66: RTC is uninitialized, using current time as timestamp -W (49441) wifi:Haven't to connect to a suitable AP now! -I (49602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (49792) BME688: RTC is uninitialized, using current time as timestamp -I (50102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (50198) SEN66: RTC is uninitialized, using current time as timestamp -W (50442) wifi:Haven't to connect to a suitable AP now! -I (50602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (50766) INFLUX: Writing to InfluxDB -W (50792) BME688: RTC is uninitialized, using current time as timestamp -I (51102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (51198) SEN66: RTC is uninitialized, using current time as timestamp -W (51443) wifi:Haven't to connect to a suitable AP now! -I (51602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (51792) BME688: RTC is uninitialized, using current time as timestamp -I (52102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (52198) SEN66: RTC is uninitialized, using current time as timestamp -W (52444) wifi:Haven't to connect to a suitable AP now! -I (52602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (52792) BME688: RTC is uninitialized, using current time as timestamp -I (53102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (53198) SEN66: RTC is uninitialized, using current time as timestamp -W (53445) wifi:Haven't to connect to a suitable AP now! -I (53602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (53792) BME688: RTC is uninitialized, using current time as timestamp -I (54102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (54198) SEN66: RTC is uninitialized, using current time as timestamp -W (54446) wifi:Haven't to connect to a suitable AP now! -I (54602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (54792) BME688: RTC is uninitialized, using current time as timestamp -I (55102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (55198) SEN66: RTC is uninitialized, using current time as timestamp -W (55447) wifi:Haven't to connect to a suitable AP now! -I (55602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (55792) BME688: RTC is uninitialized, using current time as timestamp -I (56102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (56198) SEN66: RTC is uninitialized, using current time as timestamp -W (56448) wifi:Haven't to connect to a suitable AP now! -I (56602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (56792) BME688: RTC is uninitialized, using current time as timestamp -I (57102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (57198) SEN66: RTC is uninitialized, using current time as timestamp -W (57449) wifi:Haven't to connect to a suitable AP now! -I (57602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (57761) OTA_CLIENT: Device is not registered, attempting to register... -I (57763) OTA_CLIENT: HTTP client handle: 0x3fcf0aac -E (57767) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (57771) esp-tls: Failed to open new connection -E (57774) transport_base: Failed to open a new connection -E (57780) HTTP_CLIENT: Connection failed, sock < 0 -I (57784) INFLUX: HTTP request performed, status code: 0 -E (57789) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT -E (57797) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL -W (57792) BME688: RTC is uninitialized, using current time as timestamp -I (58102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (58198) SEN66: RTC is uninitialized, using current time as timestamp -W (58450) wifi:Haven't to connect to a suitable AP now! -I (58602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (58810) BME688: RTC is uninitialized, using current time as timestamp -I (59102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (59198) SEN66: RTC is uninitialized, using current time as timestamp -W (59451) wifi:Haven't to connect to a suitable AP now! -I (59602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -E (59804) INFLUX_BUFFER: [E-INF-18] Failed to write chunk with 5 retries to Influx: ESP_FAIL -I (59804) KOTA: status OK -> CONNECTION_ERROR reason=influx_flush_failed -E (59808) MAIN: [E-MAIN-27] Failed to flush Influx buffer: ESP_FAIL -W (59810) BME688: RTC is uninitialized, using current time as timestamp -I (60102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (60198) SEN66: RTC is uninitialized, using current time as timestamp -W (60452) wifi:Haven't to connect to a suitable AP now! -I (60602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (60820) BME688: RTC is uninitialized, using current time as timestamp -I (61102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (61198) SEN66: RTC is uninitialized, using current time as timestamp -W (61453) wifi:Haven't to connect to a suitable AP now! -I (61602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (61820) BME688: RTC is uninitialized, using current time as timestamp -I (62102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (62198) SEN66: RTC is uninitialized, using current time as timestamp -W (62454) wifi:Haven't to connect to a suitable AP now! -I (62602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (62820) BME688: RTC is uninitialized, using current time as timestamp -W (62927) wifi:Haven't to connect to a suitable AP now! -I (62927) KOTA: health up=61s status=CONNECTION_ERROR wifi=disconnected rssi=0 time_valid=0 sd=ok buffer=764 heap=46611 psram=7.3M last_upload=never ota=unregistered -I (63102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (63198) SEN66: RTC is uninitialized, using current time as timestamp -W (63455) wifi:Haven't to connect to a suitable AP now! -I (63602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -E (63767) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (63767) esp-tls: Failed to open new connection -E (63769) transport_base: Failed to open a new connection -E (63775) HTTP_CLIENT: Connection failed, sock < 0 -E (63779) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 -E (63787) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT -W (63820) BME688: RTC is uninitialized, using current time as timestamp -I (64102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (64198) SEN66: RTC is uninitialized, using current time as timestamp -W (64456) wifi:Haven't to connect to a suitable AP now! -I (64602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (64634) SEN66: First measurement, waiting additional 120 seconds for sensor to stabilize... -W (64820) BME688: RTC is uninitialized, using current time as timestamp -I (65102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (65198) SEN66: RTC is uninitialized, using current time as timestamp -W (65457) wifi:Haven't to connect to a suitable AP now! -I (65602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (65820) BME688: RTC is uninitialized, using current time as timestamp -I (66102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (66198) SEN66: RTC is uninitialized, using current time as timestamp -W (66458) wifi:Haven't to connect to a suitable AP now! -I (66602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (66820) BME688: RTC is uninitialized, using current time as timestamp -I (67102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (67198) SEN66: RTC is uninitialized, using current time as timestamp -W (67459) wifi:Haven't to connect to a suitable AP now! -I (67602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (67820) BME688: RTC is uninitialized, using current time as timestamp -I (68102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (68198) SEN66: RTC is uninitialized, using current time as timestamp -W (68460) wifi:Haven't to connect to a suitable AP now! -I (68602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (68820) BME688: RTC is uninitialized, using current time as timestamp -I (69102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (69198) SEN66: RTC is uninitialized, using current time as timestamp -W (69461) wifi:Haven't to connect to a suitable AP now! -I (69602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (69820) BME688: RTC is uninitialized, using current time as timestamp -I (70102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (70198) SEN66: RTC is uninitialized, using current time as timestamp -W (70462) wifi:Haven't to connect to a suitable AP now! -I (70602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (70820) BME688: RTC is uninitialized, using current time as timestamp -I (71102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (71198) SEN66: RTC is uninitialized, using current time as timestamp -W (71463) wifi:Haven't to connect to a suitable AP now! -I (71602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (71820) BME688: RTC is uninitialized, using current time as timestamp -I (72102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (72198) SEN66: RTC is uninitialized, using current time as timestamp -W (72464) wifi:Haven't to connect to a suitable AP now! -I (72602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (72820) BME688: RTC is uninitialized, using current time as timestamp -I (73102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (73198) SEN66: RTC is uninitialized, using current time as timestamp -W (73465) wifi:Haven't to connect to a suitable AP now! -I (73602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (73794) OTA_CLIENT: Device is not registered, attempting to register... -I (73796) OTA_CLIENT: HTTP client handle: 0x3fcf03a8 -W (73820) BME688: RTC is uninitialized, using current time as timestamp -I (74102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (74198) SEN66: RTC is uninitialized, using current time as timestamp -W (74466) wifi:Haven't to connect to a suitable AP now! -I (74602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (74609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (74820) BME688: RTC is uninitialized, using current time as timestamp -I (75102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (75109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (75198) SEN66: RTC is uninitialized, using current time as timestamp -W (75467) wifi:Haven't to connect to a suitable AP now! -I (75602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (75609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (75820) BME688: RTC is uninitialized, using current time as timestamp -I (76102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (76109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (76198) SEN66: RTC is uninitialized, using current time as timestamp -W (76468) wifi:Haven't to connect to a suitable AP now! -I (76602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (76609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (76820) BME688: RTC is uninitialized, using current time as timestamp -I (77102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (77109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (77198) SEN66: RTC is uninitialized, using current time as timestamp -W (77469) wifi:Haven't to connect to a suitable AP now! -I (77602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (77609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (77820) BME688: RTC is uninitialized, using current time as timestamp -I (78102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (78109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (78198) SEN66: RTC is uninitialized, using current time as timestamp -W (78470) wifi:Haven't to connect to a suitable AP now! -I (78602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (78609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (78820) BME688: RTC is uninitialized, using current time as timestamp -I (79102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (79109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (79198) SEN66: RTC is uninitialized, using current time as timestamp -W (79471) wifi:Haven't to connect to a suitable AP now! -I (79602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (79609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (79820) BME688: RTC is uninitialized, using current time as timestamp -I (80102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (80109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (80198) SEN66: RTC is uninitialized, using current time as timestamp -W (80472) wifi:Haven't to connect to a suitable AP now! -I (80602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (80609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -E (80797) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (80798) esp-tls: Failed to open new connection -E (80799) transport_base: Failed to open a new connection -E (80805) HTTP_CLIENT: Connection failed, sock < 0 -E (80809) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 -E (80817) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT -W (80820) BME688: RTC is uninitialized, using current time as timestamp -I (81102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (81109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (81198) SEN66: RTC is uninitialized, using current time as timestamp -W (81473) wifi:Haven't to connect to a suitable AP now! -I (81602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (81609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (81831) BME688: RTC is uninitialized, using current time as timestamp -I (82102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (82109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (82198) SEN66: RTC is uninitialized, using current time as timestamp -W (82474) wifi:Haven't to connect to a suitable AP now! -I (82602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (82609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (82831) BME688: RTC is uninitialized, using current time as timestamp -I (83102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (83109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (83198) SEN66: RTC is uninitialized, using current time as timestamp -W (83475) wifi:Haven't to connect to a suitable AP now! -I (83602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (83609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (83831) BME688: RTC is uninitialized, using current time as timestamp -I (84102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (84109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (84198) SEN66: RTC is uninitialized, using current time as timestamp -W (84476) wifi:Haven't to connect to a suitable AP now! -I (84602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (84609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (84831) BME688: RTC is uninitialized, using current time as timestamp -I (85102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (85109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (85198) SEN66: RTC is uninitialized, using current time as timestamp -W (85477) wifi:Haven't to connect to a suitable AP now! -I (85602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (85609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (85831) BME688: RTC is uninitialized, using current time as timestamp -I (86102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (86109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (86198) SEN66: RTC is uninitialized, using current time as timestamp -W (86478) wifi:Haven't to connect to a suitable AP now! -I (86602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (86609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (86831) BME688: RTC is uninitialized, using current time as timestamp -I (87102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (87109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (87198) SEN66: RTC is uninitialized, using current time as timestamp -W (87479) wifi:Haven't to connect to a suitable AP now! -I (87602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (87609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (87831) BME688: RTC is uninitialized, using current time as timestamp -I (88102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (88109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (88198) SEN66: RTC is uninitialized, using current time as timestamp -W (88480) wifi:Haven't to connect to a suitable AP now! -I (88602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (88609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (88831) BME688: RTC is uninitialized, using current time as timestamp -I (89102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (89109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (89198) SEN66: RTC is uninitialized, using current time as timestamp -W (89481) wifi:Haven't to connect to a suitable AP now! -I (89602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (89609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (89831) BME688: RTC is uninitialized, using current time as timestamp -I (90102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (90109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (90198) SEN66: RTC is uninitialized, using current time as timestamp -W (90482) wifi:Haven't to connect to a suitable AP now! -I (90602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (90609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -I (90824) OTA_CLIENT: Device is not registered, attempting to register... -I (90826) OTA_CLIENT: HTTP client handle: 0x3fcf03a8 -W (90831) BME688: RTC is uninitialized, using current time as timestamp -I (91102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (91109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (91198) SEN66: RTC is uninitialized, using current time as timestamp -W (91483) wifi:Haven't to connect to a suitable AP now! -I (91602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (91609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (91831) BME688: RTC is uninitialized, using current time as timestamp -I (92102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (92109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (92198) SEN66: RTC is uninitialized, using current time as timestamp -W (92484) wifi:Haven't to connect to a suitable AP now! -I (92602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (92609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (92831) BME688: RTC is uninitialized, using current time as timestamp -I (93102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (93109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (93198) SEN66: RTC is uninitialized, using current time as timestamp -W (93485) wifi:Haven't to connect to a suitable AP now! -I (93602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (93609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (93831) BME688: RTC is uninitialized, using current time as timestamp -I (94102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (94109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (94198) SEN66: RTC is uninitialized, using current time as timestamp -W (94486) wifi:Haven't to connect to a suitable AP now! -I (94602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (94609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (94831) BME688: RTC is uninitialized, using current time as timestamp -I (95102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (95109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (95198) SEN66: RTC is uninitialized, using current time as timestamp -W (95487) wifi:Haven't to connect to a suitable AP now! -I (95602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (95609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (95831) BME688: RTC is uninitialized, using current time as timestamp -I (96102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (96109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (96198) SEN66: RTC is uninitialized, using current time as timestamp -W (96488) wifi:Haven't to connect to a suitable AP now! -I (96602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (96609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (96831) BME688: RTC is uninitialized, using current time as timestamp -I (97102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (97109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (97198) SEN66: RTC is uninitialized, using current time as timestamp -W (97489) wifi:Haven't to connect to a suitable AP now! -I (97602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (97609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -E (97827) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (97827) esp-tls: Failed to open new connection -E (97829) transport_base: Failed to open a new connection -W (97831) BME688: RTC is uninitialized, using current time as timestamp -E (97835) HTTP_CLIENT: Connection failed, sock < 0 -E (97845) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 -E (97854) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT -I (98102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (98109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (98198) SEN66: RTC is uninitialized, using current time as timestamp -W (98490) wifi:Haven't to connect to a suitable AP now! -I (98602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (98609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (98841) BME688: RTC is uninitialized, using current time as timestamp -I (99102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (99109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (99198) SEN66: RTC is uninitialized, using current time as timestamp -W (99491) wifi:Haven't to connect to a suitable AP now! -I (99602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (99609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (99841) BME688: RTC is uninitialized, using current time as timestamp -I (100102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (100109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (100198) SEN66: RTC is uninitialized, using current time as timestamp -W (100492) wifi:Haven't to connect to a suitable AP now! -I (100602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (100609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (100841) BME688: RTC is uninitialized, using current time as timestamp -I (101102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (101109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (101198) SEN66: RTC is uninitialized, using current time as timestamp -W (101493) wifi:Haven't to connect to a suitable AP now! -I (101602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (101609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (101841) BME688: RTC is uninitialized, using current time as timestamp -I (102102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (102109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (102198) SEN66: RTC is uninitialized, using current time as timestamp -W (102494) wifi:Haven't to connect to a suitable AP now! -I (102602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (102609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (102841) BME688: RTC is uninitialized, using current time as timestamp -I (103102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (103109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (103198) SEN66: RTC is uninitialized, using current time as timestamp -W (103495) wifi:Haven't to connect to a suitable AP now! -I (103602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (103609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (103841) BME688: RTC is uninitialized, using current time as timestamp -I (104102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (104109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (104198) SEN66: RTC is uninitialized, using current time as timestamp -W (104496) wifi:Haven't to connect to a suitable AP now! -I (104602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (104609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (104841) BME688: RTC is uninitialized, using current time as timestamp -I (105102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (105109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (105198) SEN66: RTC is uninitialized, using current time as timestamp -W (105497) wifi:Haven't to connect to a suitable AP now! -I (105602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (105609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (105841) BME688: RTC is uninitialized, using current time as timestamp -I (106102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (106109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (106198) SEN66: RTC is uninitialized, using current time as timestamp -W (106498) wifi:Haven't to connect to a suitable AP now! -I (106602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (106609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (106841) BME688: RTC is uninitialized, using current time as timestamp -I (107102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (107109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (107198) SEN66: RTC is uninitialized, using current time as timestamp -W (107499) wifi:Haven't to connect to a suitable AP now! -I (107602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (107609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (107841) BME688: RTC is uninitialized, using current time as timestamp -I (107861) OTA_CLIENT: Device is not registered, attempting to register... -I (107863) OTA_CLIENT: HTTP client handle: 0x3fcf03a8 -I (108102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (108109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (108198) SEN66: RTC is uninitialized, using current time as timestamp -W (108500) wifi:Haven't to connect to a suitable AP now! -I (108602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (108609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (108841) BME688: RTC is uninitialized, using current time as timestamp -I (109102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (109109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (109198) SEN66: RTC is uninitialized, using current time as timestamp -W (109501) wifi:Haven't to connect to a suitable AP now! -I (109602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (109609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (109841) BME688: RTC is uninitialized, using current time as timestamp -I (110102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (110109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (110198) SEN66: RTC is uninitialized, using current time as timestamp -W (110502) wifi:Haven't to connect to a suitable AP now! -I (110602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (110609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (110841) BME688: RTC is uninitialized, using current time as timestamp -I (111102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (111109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (111198) SEN66: RTC is uninitialized, using current time as timestamp -W (111503) wifi:Haven't to connect to a suitable AP now! -I (111602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (111609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (111841) BME688: RTC is uninitialized, using current time as timestamp -I (112102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (112109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (112198) SEN66: RTC is uninitialized, using current time as timestamp -W (112504) wifi:Haven't to connect to a suitable AP now! -I (112602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (112609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (112841) BME688: RTC is uninitialized, using current time as timestamp -I (113102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (113109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (113198) SEN66: RTC is uninitialized, using current time as timestamp -W (113505) wifi:Haven't to connect to a suitable AP now! -I (113602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (113609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (113841) BME688: RTC is uninitialized, using current time as timestamp -I (114102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (114109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (114198) SEN66: RTC is uninitialized, using current time as timestamp -W (114506) wifi:Haven't to connect to a suitable AP now! -I (114602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (114609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (114841) BME688: RTC is uninitialized, using current time as timestamp -E (114864) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (114864) esp-tls: Failed to open new connection -E (114867) transport_base: Failed to open a new connection -E (114872) HTTP_CLIENT: Connection failed, sock < 0 -E (114876) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 -E (114885) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT -I (115102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (115109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (115198) SEN66: RTC is uninitialized, using current time as timestamp -W (115507) wifi:Haven't to connect to a suitable AP now! -I (115602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (115609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (115841) BME688: RTC is uninitialized, using current time as timestamp -I (116102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (116109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (116198) SEN66: RTC is uninitialized, using current time as timestamp -W (116508) wifi:Haven't to connect to a suitable AP now! -I (116602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (116609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (116841) BME688: RTC is uninitialized, using current time as timestamp -I (117102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (117109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (117198) SEN66: RTC is uninitialized, using current time as timestamp -W (117509) wifi:Haven't to connect to a suitable AP now! -I (117602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (117609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (117841) BME688: RTC is uninitialized, using current time as timestamp -I (118102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (118109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (118198) SEN66: RTC is uninitialized, using current time as timestamp -W (118510) wifi:Haven't to connect to a suitable AP now! -I (118602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (118609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (118841) BME688: RTC is uninitialized, using current time as timestamp -I (119102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (119109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (119198) SEN66: RTC is uninitialized, using current time as timestamp -W (119511) wifi:Haven't to connect to a suitable AP now! -I (119602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (119609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -I (119817) INFLUX_BUFFER: Flushing chunk starting at line 10: -I (119818) INFLUX: Writing to InfluxDB -W (119841) BME688: RTC is uninitialized, using current time as timestamp -I (120102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (120109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (120198) SEN66: RTC is uninitialized, using current time as timestamp -W (120512) wifi:Haven't to connect to a suitable AP now! -I (120602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (120609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (120841) BME688: RTC is uninitialized, using current time as timestamp -I (121102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (121109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (121198) SEN66: RTC is uninitialized, using current time as timestamp -W (121513) wifi:Haven't to connect to a suitable AP now! -I (121602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (121609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (121841) BME688: RTC is uninitialized, using current time as timestamp -I (122102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (122109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (122198) SEN66: RTC is uninitialized, using current time as timestamp -W (122514) wifi:Haven't to connect to a suitable AP now! -I (122602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (122609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (122841) BME688: RTC is uninitialized, using current time as timestamp -W (122935) wifi:Haven't to connect to a suitable AP now! -I (122935) KOTA: health up=121s status=CONNECTION_ERROR wifi=disconnected rssi=0 time_valid=0 sd=ok buffer=764 heap=43167 psram=7.3M last_upload=never ota=unregistered -I (123102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (123109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (123198) SEN66: RTC is uninitialized, using current time as timestamp -W (123515) wifi:Haven't to connect to a suitable AP now! -I (123602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (123609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (123841) BME688: RTC is uninitialized, using current time as timestamp -I (124102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (124109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (124198) SEN66: RTC is uninitialized, using current time as timestamp -W (124516) wifi:Haven't to connect to a suitable AP now! -I (124602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (124609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (124841) BME688: RTC is uninitialized, using current time as timestamp -I (124892) OTA_CLIENT: Device is not registered, attempting to register... -I (124894) OTA_CLIENT: HTTP client handle: 0x3fcf1210 -I (125102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (125109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (125198) SEN66: RTC is uninitialized, using current time as timestamp -W (125517) wifi:Haven't to connect to a suitable AP now! -I (125602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (125609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (125841) BME688: RTC is uninitialized, using current time as timestamp -I (126102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (126109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (126198) SEN66: RTC is uninitialized, using current time as timestamp -W (126518) wifi:Haven't to connect to a suitable AP now! -I (126602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (126609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -E (126819) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (126820) esp-tls: Failed to open new connection -E (126822) transport_base: Failed to open a new connection -E (126828) HTTP_CLIENT: Connection failed, sock < 0 -I (126832) INFLUX: HTTP request performed, status code: 0 -E (126837) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT -E (126845) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL -W (126841) BME688: RTC is uninitialized, using current time as timestamp -I (127102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (127109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (127198) SEN66: RTC is uninitialized, using current time as timestamp -W (127519) wifi:Haven't to connect to a suitable AP now! -I (127602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (127609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (127858) BME688: RTC is uninitialized, using current time as timestamp -I (128102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (128109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (128198) SEN66: RTC is uninitialized, using current time as timestamp -W (128520) wifi:Haven't to connect to a suitable AP now! -I (128602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (128609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -I (128852) INFLUX: Writing to InfluxDB -W (128858) BME688: RTC is uninitialized, using current time as timestamp -I (129102) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (129109) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (129198) SEN66: RTC is uninitialized, using current time as timestamp -W (129521) wifi:Haven't to connect to a suitable AP now! -I (129602) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (129609) SENSOR_GATHERER: 2/4 tasks still running, waiting... -W (129858) BME688: RTC is uninitialized, using current time as timestamp +--- connected to COM5 --- +W (7411) wifi:Haven't to connect to a suitable AP now! +--- reset issued by tool --- +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x15 (USB_UART_CHIP_RESET),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x4037f35e +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (25) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (28) boot: efuse block revision: v1.4 +I (32) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (43) boot: Enabling RNG early entropy source... +I (48) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (63) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (76) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (89) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (102) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (109) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c190020 size=66d30h (421168) map +I (194) esp_image: segment 1: paddr=000a6d58 vaddr=3fc9c700 size=05d00h ( 23808) load +I (199) esp_image: segment 2: paddr=000aca60 vaddr=40374000 size=035b8h ( 13752) load +I (203) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=1862d0h (1598160) map +I (487) esp_image: segment 4: paddr=002362f8 vaddr=403775b8 size=15054h ( 86100) load +I (506) esp_image: segment 5: paddr=0024b354 vaddr=5000002c size=00020h ( 32) load +I (516) boot: Loaded app from partition at offset 0x40000 +I (517) boot: Disabling RNG early entropy source... +I (527) octal_psram: vendor id : 0x0d (AP) +I (527) octal_psram: dev id : 0x02 (generation 3) +I (528) octal_psram: density : 0x03 (64 Mbit) +I (529) octal_psram: good-die : 0x01 (Pass) +I (534) octal_psram: Latency : 0x01 (Fixed) +I (538) octal_psram: VCC : 0x01 (3V) +I (542) octal_psram: SRF : 0x01 (Fast Refresh) +I (547) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (552) octal_psram: BurstLen : 0x01 (32 Byte) +I (556) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (562) octal_psram: DriveStrength: 0x00 (1/1) +I (566) esp_psram: Found 8MB PSRAM device +I (569) esp_psram: Speed: 40MHz +I (572) cpu_start: Multicore app +I (1306) esp_psram: SPI SRAM memory test OK +I (1314) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1315) cpu_start: Pro cpu start user code +I (1315) cpu_start: cpu freq: 160000000 Hz +I (1317) app_init: Application information: +I (1321) app_init: Project name: kota-sensorboard +I (1326) app_init: App version: 1.0.16 +I (1330) app_init: Compile time: Sep 28 2026 15:07:57 +I (1335) app_init: ELF file SHA256: 4f10989e2... +I (1339) app_init: ESP-IDF: v5.5.3-dirty +I (1344) efuse_init: Min chip rev: v0.0 +I (1347) efuse_init: Max chip rev: v0.99 +I (1352) efuse_init: Chip rev: v0.2 +I (1356) heap_init: Initializing. RAM available for dynamic allocation: +I (1362) heap_init: At 3FCB4AD0 len 00034C40 (211 KiB): RAM +I (1367) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1372) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1378) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1383) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1391) spi_flash: detected chip: generic +I (1393) spi_flash: flash io: dio +I (1399) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1403) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1418) main_task: Started on CPU0 +I (1421) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1429) main_task: Calling app_main() +E (1432) ID: [E-MAIN-04] Device MAC address: A4:CB:8F:C9:46:F8 +I (1458) KOTA: led color=green pattern=solid +I (1659) I2C: Creating I2C mutex +I (1676) KOTA: i2c SDA=1 SCL=1 0x39=AS7341 0x68=DS1307 0x6B=SEN66 0x76=BME688 +I (1677) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1679) SPI: Creating SPI mutex +I (1722) SD_CARD: SD card mounted successfully +I (1723) SD_CARD: /sdcard exists, SD card initialized successfully +I (1788) KOTA: sd type=SDHC size=29.3GB fs=FAT32 free=29.3GB env_keys=INFLUX_TOKEN,INFLUX_URL buffer_lines=764 +I (1792) WIFI_CREDENTIALS: SSID/PASSWORD not set in .env; Matter will be used for WiFi provisioning +I (1802) MAIN: Matter node created successfully +W (1803) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1810) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1818) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1827) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1836) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1845) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1854) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1862) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1871) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1880) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1888) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1897) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1906) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1915) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1924) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1932) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1941) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1950) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1960) pp: pp rom version: e7ae62f +I (1962) net80211: net80211 rom version: e7ae62f +I (1967) wifi:wifi driver task: 3fcc7d40, prio:23, stack:6656, core=0 +I (1985) wifi:wifi firmware version: 4df78f2 +I (1985) wifi:wifi certification version: v7.0 +I (1986) wifi:config NVS flash: enabled +I (1986) wifi:config nano formatting: disabled +I (1990) wifi:Init data frame dynamic rx buffer num: 32 +I (1995) wifi:Init static rx mgmt buffer num: 5 +I (1999) wifi:Init management short buffer num: 32 +I (2004) wifi:Init dynamic tx buffer num: 32 +I (2008) wifi:Init static tx FG buffer num: 2 +I (2012) wifi:Init static rx buffer size: 1600 +I (2016) wifi:Init static rx buffer num: 10 +I (2020) wifi:Init dynamic rx buffer num: 32 +I (2025) wifi_init: rx ba win: 6 +I (2027) wifi_init: accept mbox: 6 +I (2030) wifi_init: tcpip mbox: 32 +I (2033) wifi_init: udp mbox: 6 +I (2036) wifi_init: tcp mbox: 6 +I (2039) wifi_init: tcp tx win: 5760 +I (2042) wifi_init: tcp rx win: 5760 +I (2045) wifi_init: tcp mss: 1440 +I (2057) chip[DL]: NVS set: chip-counters/reboot-count = 15857 (0x3DF1) +I (2060) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (2063) BLE_INIT: BT controller compile version [1bb2f50] +I (2067) BLE_INIT: Using main XTAL as clock source +I (2072) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (2080) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (2085) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +I (2133) NimBLE: GAP procedure initiated: stop advertising. + +I (2135) CHIP[DL]: BLE host-controller synced +I (2635) chip[DL]: Starting ESP WiFi layer +I (2637) wifi:mode : sta (a4:cb:8f:c9:46:f8) +I (2638) wifi:enable tsf +W (2639) wifi:Haven't to connect to a suitable AP now! +I (2639) chip[DL]: +I (2639) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43Done driving station state, nothing else to do... + +W (2652) wifi:Haven't to connect to a suitable AP now! +I (2657) chip[DL]: Done driving station state, nothing else to do... +I (2657) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 +I (2663) chip[SVR]: +Initializing subscription resumption storage... +I (2676) chip[SVR]: Server initializing... +I (2680) chip[TS]: Last Known Good Time: 2026-09-28T13:29:12 +I (2685) chip[DMG]: AccessControl: initializing +I (2688) chip[DMG]: Examples::AccessControlDelegate::Init +I (2693) chip[DMG]: AccessControl: setting +I (2697) chip[DMG]: DefaultAclStorage: initializing +I (2702) chip[DMG]: DefaultAclStorage: 0 entries loaded +I (2707) chip[SVR]: WARNING: mTestEventTriggerDelegate is null +I (2726) chip[ZCL]: Using ZAP configuration... +I (2726) esp_matter_cluster: Cluster plugin init common callback +I (2727) chip[DMG]: AccessControlCluster: initializing +I (2730) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported +I (2740) chip[DIS]: Updating services using commissioning mode 1 +I (2744) chip[DIS]: CHIP minimal mDNS started advertising. +I (2749) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 +I (2759) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: A180A7E5D58D9F80. +I (2769) chip[DIS]: mDNS service published: _matterc._udp +I (2774) chip[IN]: CASE Server enabling CASE session setups +I (2780) chip[SVR]: Joining Multicast groups +I (2783) chip[SVR]: Server Listening... +I (2788) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** +I (2798) data_model: Dynamic endpoint 0 added +E (2801) data_model: Cluster cannot be NULL. +I (2806) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** +I (2816) data_model: Dynamic endpoint 1 added +E (2820) data_model: Cluster cannot be NULL. +I (2825) MAIN: ESP-Matter started successfully +I (2826) chip[DL]: WIFI_EVENT_STA_START +W (2832) wifi:Haven't to connect to a suitable AP now! +I (2837) chip[DL]: Done driving station state, nothing else to do... +I (2843) KOTA: boot fw=1.0.16 build=4f10989e hw=2 mac=A4CB8FC946F8 reset=USB_UART_CHIP_RESET mode=matter commissioned=0 last_restart=none +I (2845) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) +I (2862) NimBLE: GAP procedure initiated: advertise; +I (2866) NimBLE: disc_mode=2 +I (2869) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 +I (2878) NimBLE: + +I (2881) chip[DL]: CHIPoBLE advertising started +I (2886) KOTA: matter fabrics=0 window=open qr=MT:Y.K9042C00KA0648G00 manual=34970112332 +I (2892) MAIN: Commissioning window opened +W (2893) wifi:Haven't to connect to a suitable AP now! +I (2897) KOTA: matter fabrics=0 window=open qr=MT:Y.K9042C00KA0648G00 manual=34970112332 +I (2905) KOTA: led color=cyan pattern=breathe period=3000ms +I (2901) KOTA: health up=1s status=OK wifi=disconnected rssi=0 time_valid=0 sd=ok buffer=0 heap=123883 psram=7.9M last_upload=never ota=unregistered +W (3394) wifi:Haven't to connect to a suitable AP now! +I (3894) KOTA: rtc running=0 time=2000-01-01T00:00:00 +W (4395) wifi:Haven't to connect to a suitable AP now! +W (5396) wifi:Haven't to connect to a suitable AP now! +W (6396) wifi:Haven't to connect to a suitable AP now! +W (7397) wifi:Haven't to connect to a suitable AP now! +W (8398) wifi:Haven't to connect to a suitable AP now! +W (9399) wifi:Haven't to connect to a suitable AP now! +W (10400) wifi:Haven't to connect to a suitable AP now! +W (11401) wifi:Haven't to connect to a suitable AP now! +W (12402) wifi:Haven't to connect to a suitable AP now! +W (13403) wifi:Haven't to connect to a suitable AP now! +I (13895) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +W (13962) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +I (13975) BME688: Delaying for 10000 microseconds +W (14404) wifi:Haven't to connect to a suitable AP now! +I (14591) KOTA: mic enabled=1 (GPIO3=0, expected 0) +I (14593) Flicker: Flicker sensor initialized on pin 14 +I (14594) temperature_sensor: Range [20°C ~ 100°C], error < 2°C +I (14597) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data +W (14601) wifi: +I (14597) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data +Haven't to connect to a suitable AP now! +I (14598) SEN66: Creating sensor mutex + +I (14605) INFLUX_BUFFER: Flushing chunk starting at line 10: +W (14611) AS7341: Reading spectral data from AS7341 +I (14616) SEN66: Waking up sensor by setting power GPIO to enabled level... +I (14617) DeviceStatus: Active tasks: 26 +I (14621) INFLUX: Writing to InfluxDB +I (14641) DeviceStatus: Task list: +main R 1 1132 4 +gather_sensor_t X 1 1480 24 +IDLE1 R 0 700 6 +IDLE0 R 0 692 5 +gather_sensor_t B 1 2552 22 +reset_task B 0 3344 9 +ds1307_update B 1 768 17 +tiT B 18 2420 11 +gather_task B 1 1984 19 +gather_task B 1 14292 20 +gather_sensor_t B 1 1364 25 +gather_sensor_t B 1 1948 23 +ota_task B 1 7504 18 +CHIP B 1 3636 13 +health B 1 928 16 +Tmr Svc B 1 1364 7 +led_task B 5 964 8 +sys_evt B 20 2676 10 +ipc0 S 24 372 1 +ipc1 S 24 524 2 +esp_timer S 22 3332 3 +gather_sensor_t B 1 2544 21 +gather_sensor_t B 1 2552 26 +wifi B 23 4324 12 +btController B 23 1960 14 +nimble_host B 21 1476 15 + +W (14710) DeviceStatus: RTC is uninitialized, using current time as timestamp +W (14973) AS7341: Reading basic counts data from AS7341 +W (14989) AS7341: Reading flicker detection state from AS7341 +W (15206) as7341: FD Status Register: 0x20 (0b00100000) +I (15211) AS7341: Updating Matter attributes with AS7341 data: 14946 +I (15211) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms +I (15212) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** +I (15217) Flicker: Starting to collect samples in gather task +I (15228) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 14946 ********** +E (15245) MAIN: [E-MAIN-03] Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +E (15254) MAIN: [E-MAIN-03] Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +W (15264) AS7341: Formatting AS7341 data in line protocol +AS7341 line protocol: +as7341_adc,device=A4CB8FC946F8 f1=1,f2=2,f3=2,f4=3,f5=3,f6=3,f7=6,f8=4,clear=14,nir=2 946684801000000000 +as7341_basic_counts,device=A4CB8FC946F8 f1=2.23,f2=4.46,f3=4.46,f4=6.69,f5=6.69,f6=6.69,f7=13.39,f8=8.92,clear=31.23,nir=4.46 946684801000000000 +as7341_flicker,device=A4CB8FC946F8 state="unknown" 946684801000000000 +W (15404) wifi:Haven't to connect to a suitable AP now! +W (15717) DeviceStatus: RTC is uninitialized, using current time as timestamp +I (16256) Flicker: Finished collecting samples, notifying main task +I (16257) Flicker: Collected 1024 samples for dominant frequency analysis +I (16264) SENSOR_GATHERER: Sensor should now be asleep +I (16276) BME688: Delaying for 199517 microseconds +I (16276) SENSOR_GATHERER: Sensor should now be asleep +W (16405) wifi:Haven't to connect to a suitable AP now! +I (16714) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** +I (16715) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** +E (16726) MAIN: [E-MAIN-03] Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +I (16717) SENSOR_GATHERER: Sensor should now be asleep +E (16735) MAIN: [E-MAIN-03] Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +W (16750) BME688: RTC is uninitialized, using current time as timestamp +W (17406) wifi:Haven't to connect to a suitable AP now! +W (17756) BME688: RTC is uninitialized, using current time as timestamp +W (18407) wifi:Haven't to connect to a suitable AP now! +W (18756) BME688: RTC is uninitialized, using current time as timestamp +W (19408) wifi:Haven't to connect to a suitable AP now! +I (19633) SEN66: Sent start measurement command, waiting for sensor to be ready... +W (19756) BME688: RTC is uninitialized, using current time as timestamp +W (20409) wifi:Haven't to connect to a suitable AP now! +W (20756) BME688: RTC is uninitialized, using current time as timestamp +W (21410) wifi:Haven't to connect to a suitable AP now! +E (21642) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (21642) esp-tls: Failed to open new connection +E (21645) transport_base: Failed to open a new connection +E (21650) HTTP_CLIENT: Connection failed, sock < 0 +I (21655) INFLUX: HTTP request performed, status code: 0 +E (21659) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT +E (21667) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL +W (21756) BME688: RTC is uninitialized, using current time as timestamp +W (22411) wifi:Haven't to connect to a suitable AP now! +I (22602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (22756) BME688: RTC is uninitialized, using current time as timestamp +I (23102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (23412) wifi:Haven't to connect to a suitable AP now! +I (23602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (23674) INFLUX: Writing to InfluxDB +W (23756) BME688: RTC is uninitialized, using current time as timestamp +I (24102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (24413) wifi:Haven't to connect to a suitable AP now! +I (24596) OTA_CLIENT: Device is not registered, attempting to register... +I (24598) OTA_CLIENT: HTTP client handle: 0x3fcf0fe4 +I (24602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (24738) MICROPHONE: Read 600000 bytes in 9940 ms +I (24738) MICROPHONE: Read 600000 bytes of audio data +W (24756) BME688: RTC is uninitialized, using current time as timestamp +I (25102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (25414) wifi:Haven't to connect to a suitable AP now! +I (25602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (25756) BME688: RTC is uninitialized, using current time as timestamp +I (26102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (26415) wifi:Haven't to connect to a suitable AP now! +I (26602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (26756) BME688: RTC is uninitialized, using current time as timestamp +I (27102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (27416) wifi:Haven't to connect to a suitable AP now! +I (27602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (27756) BME688: RTC is uninitialized, using current time as timestamp +I (28102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (28187) MICROPHONE: Raw block stats: mean=-81.8 min=-41376 max=37946 +I (28188) MICROPHONE: Sum SQ: 0.032256, RMS pressure: 0.000402, Normalized: 0.000402, dB peak: -67.92 +W (28192) SEN66: RTC is uninitialized, using current time as timestamp +W (28417) wifi:Haven't to connect to a suitable AP now! +I (28602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (28756) BME688: RTC is uninitialized, using current time as timestamp +I (29102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (29198) SEN66: RTC is uninitialized, using current time as timestamp +W (29418) wifi:Haven't to connect to a suitable AP now! +I (29602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (29756) BME688: RTC is uninitialized, using current time as timestamp +I (30102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (30198) SEN66: RTC is uninitialized, using current time as timestamp +W (30419) wifi:Haven't to connect to a suitable AP now! +I (30602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +E (30675) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (30675) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (30676) esp-tls: Failed to open new connection +E (30683) esp-tls: Failed to open new connection +E (30687) transport_base: Failed to open a new connection +E (30691) transport_base: Failed to open a new connection +E (30696) HTTP_CLIENT: Connection failed, sock < 0 +E (30701) HTTP_CLIENT: Connection failed, sock < 0 +E (30706) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 +I (30710) INFLUX: HTTP request performed, status code: 0 +E (30719) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT +E (30723) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT +E (30738) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL +W (30756) BME688: RTC is uninitialized, using current time as timestamp +I (31102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (31198) SEN66: RTC is uninitialized, using current time as timestamp +W (31420) wifi:Haven't to connect to a suitable AP now! +I (31602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (31756) BME688: RTC is uninitialized, using current time as timestamp +I (32102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (32198) SEN66: RTC is uninitialized, using current time as timestamp +W (32421) wifi:Haven't to connect to a suitable AP now! +I (32602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (32741) chip[DL]: bleAdv Timeout : Start slow advertisement +I (32743) chip[DL]: Configuring CHIPoBLE advertising (interval 500 ms, connectable) +I (32744) chip[DL]: Device already advertising, stop active advertisement and restart +I (32745) INFLUX: Writing to InfluxDB +I (32751) NimBLE: GAP procedure initiated: stop advertising. + +W (32756) BME688: RTC is uninitialized, using current time as timestamp +I (32760) NimBLE: GAP procedure initiated: advertise; +I (32770) NimBLE: disc_mode=2 +I (32773) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=800 adv_itvl_max=800 +I (32783) NimBLE: + +I (33102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (33198) SEN66: RTC is uninitialized, using current time as timestamp +W (33425) wifi:Haven't to connect to a suitable AP now! +I (33602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (33785) BME688: RTC is uninitialized, using current time as timestamp +I (34102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (34198) SEN66: RTC is uninitialized, using current time as timestamp +W (34426) wifi:Haven't to connect to a suitable AP now! +I (34602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (34785) BME688: RTC is uninitialized, using current time as timestamp +I (35102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (35198) SEN66: RTC is uninitialized, using current time as timestamp +W (35427) wifi:Haven't to connect to a suitable AP now! +I (35602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (35785) BME688: RTC is uninitialized, using current time as timestamp +I (36102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (36198) SEN66: RTC is uninitialized, using current time as timestamp +W (36428) wifi:Haven't to connect to a suitable AP now! +I (36602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (36785) BME688: RTC is uninitialized, using current time as timestamp +I (37102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (37198) SEN66: RTC is uninitialized, using current time as timestamp +W (37429) wifi:Haven't to connect to a suitable AP now! +I (37602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (37785) BME688: RTC is uninitialized, using current time as timestamp +I (38102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (38198) SEN66: RTC is uninitialized, using current time as timestamp +W (38430) wifi:Haven't to connect to a suitable AP now! +I (38602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (38785) BME688: RTC is uninitialized, using current time as timestamp +I (39102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (39198) SEN66: RTC is uninitialized, using current time as timestamp +W (39431) wifi:Haven't to connect to a suitable AP now! +I (39602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +E (39754) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (39754) esp-tls: Failed to open new connection +E (39757) transport_base: Failed to open a new connection +E (39762) HTTP_CLIENT: Connection failed, sock < 0 +I (39766) INFLUX: HTTP request performed, status code: 0 +E (39771) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT +E (39779) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL +W (39785) BME688: RTC is uninitialized, using current time as timestamp +I (40102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (40198) SEN66: RTC is uninitialized, using current time as timestamp +W (40432) wifi:Haven't to connect to a suitable AP now! +I (40602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (40731) OTA_CLIENT: Device is not registered, attempting to register... +I (40733) OTA_CLIENT: HTTP client handle: 0x3fcf02e4 +W (40792) BME688: RTC is uninitialized, using current time as timestamp +I (41102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (41198) SEN66: RTC is uninitialized, using current time as timestamp +W (41433) wifi:Haven't to connect to a suitable AP now! +I (41602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (41786) INFLUX: Writing to InfluxDB +W (41792) BME688: RTC is uninitialized, using current time as timestamp +I (42102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (42198) SEN66: RTC is uninitialized, using current time as timestamp +W (42434) wifi:Haven't to connect to a suitable AP now! +I (42602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (42792) BME688: RTC is uninitialized, using current time as timestamp +I (43102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (43198) SEN66: RTC is uninitialized, using current time as timestamp +W (43435) wifi:Haven't to connect to a suitable AP now! +I (43602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (43792) BME688: RTC is uninitialized, using current time as timestamp +I (44102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (44198) SEN66: RTC is uninitialized, using current time as timestamp +W (44436) wifi:Haven't to connect to a suitable AP now! +I (44602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (44792) BME688: RTC is uninitialized, using current time as timestamp +I (45102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (45198) SEN66: RTC is uninitialized, using current time as timestamp +W (45437) wifi:Haven't to connect to a suitable AP now! +I (45602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (45792) BME688: RTC is uninitialized, using current time as timestamp +I (46102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (46198) SEN66: RTC is uninitialized, using current time as timestamp +W (46438) wifi:Haven't to connect to a suitable AP now! +I (46602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (46792) BME688: RTC is uninitialized, using current time as timestamp +I (47102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (47198) SEN66: RTC is uninitialized, using current time as timestamp +W (47439) wifi:Haven't to connect to a suitable AP now! +I (47602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +E (47734) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (47735) esp-tls: Failed to open new connection +E (47736) transport_base: Failed to open a new connection +E (47742) HTTP_CLIENT: Connection failed, sock < 0 +E (47746) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 +E (47754) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT +W (47792) BME688: RTC is uninitialized, using current time as timestamp +I (48102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (48198) SEN66: RTC is uninitialized, using current time as timestamp +W (48440) wifi:Haven't to connect to a suitable AP now! +I (48602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +E (48734) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (48735) esp-tls: Failed to open new connection +E (48737) transport_base: Failed to open a new connection +E (48742) HTTP_CLIENT: Connection failed, sock < 0 +I (48746) INFLUX: HTTP request performed, status code: 0 +E (48751) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT +E (48759) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL +W (48792) BME688: RTC is uninitialized, using current time as timestamp +I (49102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (49198) SEN66: RTC is uninitialized, using current time as timestamp +W (49441) wifi:Haven't to connect to a suitable AP now! +I (49602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (49792) BME688: RTC is uninitialized, using current time as timestamp +I (50102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (50198) SEN66: RTC is uninitialized, using current time as timestamp +W (50442) wifi:Haven't to connect to a suitable AP now! +I (50602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (50766) INFLUX: Writing to InfluxDB +W (50792) BME688: RTC is uninitialized, using current time as timestamp +I (51102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (51198) SEN66: RTC is uninitialized, using current time as timestamp +W (51443) wifi:Haven't to connect to a suitable AP now! +I (51602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (51792) BME688: RTC is uninitialized, using current time as timestamp +I (52102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (52198) SEN66: RTC is uninitialized, using current time as timestamp +W (52444) wifi:Haven't to connect to a suitable AP now! +I (52602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (52792) BME688: RTC is uninitialized, using current time as timestamp +I (53102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (53198) SEN66: RTC is uninitialized, using current time as timestamp +W (53445) wifi:Haven't to connect to a suitable AP now! +I (53602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (53792) BME688: RTC is uninitialized, using current time as timestamp +I (54102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (54198) SEN66: RTC is uninitialized, using current time as timestamp +W (54446) wifi:Haven't to connect to a suitable AP now! +I (54602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (54792) BME688: RTC is uninitialized, using current time as timestamp +I (55102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (55198) SEN66: RTC is uninitialized, using current time as timestamp +W (55447) wifi:Haven't to connect to a suitable AP now! +I (55602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (55792) BME688: RTC is uninitialized, using current time as timestamp +I (56102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (56198) SEN66: RTC is uninitialized, using current time as timestamp +W (56448) wifi:Haven't to connect to a suitable AP now! +I (56602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (56792) BME688: RTC is uninitialized, using current time as timestamp +I (57102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (57198) SEN66: RTC is uninitialized, using current time as timestamp +W (57449) wifi:Haven't to connect to a suitable AP now! +I (57602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (57761) OTA_CLIENT: Device is not registered, attempting to register... +I (57763) OTA_CLIENT: HTTP client handle: 0x3fcf0aac +E (57767) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (57771) esp-tls: Failed to open new connection +E (57774) transport_base: Failed to open a new connection +E (57780) HTTP_CLIENT: Connection failed, sock < 0 +I (57784) INFLUX: HTTP request performed, status code: 0 +E (57789) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT +E (57797) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL +W (57792) BME688: RTC is uninitialized, using current time as timestamp +I (58102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (58198) SEN66: RTC is uninitialized, using current time as timestamp +W (58450) wifi:Haven't to connect to a suitable AP now! +I (58602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (58810) BME688: RTC is uninitialized, using current time as timestamp +I (59102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (59198) SEN66: RTC is uninitialized, using current time as timestamp +W (59451) wifi:Haven't to connect to a suitable AP now! +I (59602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +E (59804) INFLUX_BUFFER: [E-INF-18] Failed to write chunk with 5 retries to Influx: ESP_FAIL +I (59804) KOTA: status OK -> CONNECTION_ERROR reason=influx_flush_failed +E (59808) MAIN: [E-MAIN-27] Failed to flush Influx buffer: ESP_FAIL +W (59810) BME688: RTC is uninitialized, using current time as timestamp +I (60102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (60198) SEN66: RTC is uninitialized, using current time as timestamp +W (60452) wifi:Haven't to connect to a suitable AP now! +I (60602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (60820) BME688: RTC is uninitialized, using current time as timestamp +I (61102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (61198) SEN66: RTC is uninitialized, using current time as timestamp +W (61453) wifi:Haven't to connect to a suitable AP now! +I (61602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (61820) BME688: RTC is uninitialized, using current time as timestamp +I (62102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (62198) SEN66: RTC is uninitialized, using current time as timestamp +W (62454) wifi:Haven't to connect to a suitable AP now! +I (62602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (62820) BME688: RTC is uninitialized, using current time as timestamp +W (62927) wifi:Haven't to connect to a suitable AP now! +I (62927) KOTA: health up=61s status=CONNECTION_ERROR wifi=disconnected rssi=0 time_valid=0 sd=ok buffer=764 heap=46611 psram=7.3M last_upload=never ota=unregistered +I (63102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (63198) SEN66: RTC is uninitialized, using current time as timestamp +W (63455) wifi:Haven't to connect to a suitable AP now! +I (63602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +E (63767) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (63767) esp-tls: Failed to open new connection +E (63769) transport_base: Failed to open a new connection +E (63775) HTTP_CLIENT: Connection failed, sock < 0 +E (63779) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 +E (63787) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT +W (63820) BME688: RTC is uninitialized, using current time as timestamp +I (64102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (64198) SEN66: RTC is uninitialized, using current time as timestamp +W (64456) wifi:Haven't to connect to a suitable AP now! +I (64602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (64634) SEN66: First measurement, waiting additional 120 seconds for sensor to stabilize... +W (64820) BME688: RTC is uninitialized, using current time as timestamp +I (65102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (65198) SEN66: RTC is uninitialized, using current time as timestamp +W (65457) wifi:Haven't to connect to a suitable AP now! +I (65602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (65820) BME688: RTC is uninitialized, using current time as timestamp +I (66102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (66198) SEN66: RTC is uninitialized, using current time as timestamp +W (66458) wifi:Haven't to connect to a suitable AP now! +I (66602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (66820) BME688: RTC is uninitialized, using current time as timestamp +I (67102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (67198) SEN66: RTC is uninitialized, using current time as timestamp +W (67459) wifi:Haven't to connect to a suitable AP now! +I (67602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (67820) BME688: RTC is uninitialized, using current time as timestamp +I (68102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (68198) SEN66: RTC is uninitialized, using current time as timestamp +W (68460) wifi:Haven't to connect to a suitable AP now! +I (68602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (68820) BME688: RTC is uninitialized, using current time as timestamp +I (69102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (69198) SEN66: RTC is uninitialized, using current time as timestamp +W (69461) wifi:Haven't to connect to a suitable AP now! +I (69602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (69820) BME688: RTC is uninitialized, using current time as timestamp +I (70102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (70198) SEN66: RTC is uninitialized, using current time as timestamp +W (70462) wifi:Haven't to connect to a suitable AP now! +I (70602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (70820) BME688: RTC is uninitialized, using current time as timestamp +I (71102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (71198) SEN66: RTC is uninitialized, using current time as timestamp +W (71463) wifi:Haven't to connect to a suitable AP now! +I (71602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (71820) BME688: RTC is uninitialized, using current time as timestamp +I (72102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (72198) SEN66: RTC is uninitialized, using current time as timestamp +W (72464) wifi:Haven't to connect to a suitable AP now! +I (72602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (72820) BME688: RTC is uninitialized, using current time as timestamp +I (73102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (73198) SEN66: RTC is uninitialized, using current time as timestamp +W (73465) wifi:Haven't to connect to a suitable AP now! +I (73602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (73794) OTA_CLIENT: Device is not registered, attempting to register... +I (73796) OTA_CLIENT: HTTP client handle: 0x3fcf03a8 +W (73820) BME688: RTC is uninitialized, using current time as timestamp +I (74102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (74198) SEN66: RTC is uninitialized, using current time as timestamp +W (74466) wifi:Haven't to connect to a suitable AP now! +I (74602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (74609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (74820) BME688: RTC is uninitialized, using current time as timestamp +I (75102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (75109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (75198) SEN66: RTC is uninitialized, using current time as timestamp +W (75467) wifi:Haven't to connect to a suitable AP now! +I (75602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (75609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (75820) BME688: RTC is uninitialized, using current time as timestamp +I (76102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (76109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (76198) SEN66: RTC is uninitialized, using current time as timestamp +W (76468) wifi:Haven't to connect to a suitable AP now! +I (76602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (76609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (76820) BME688: RTC is uninitialized, using current time as timestamp +I (77102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (77109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (77198) SEN66: RTC is uninitialized, using current time as timestamp +W (77469) wifi:Haven't to connect to a suitable AP now! +I (77602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (77609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (77820) BME688: RTC is uninitialized, using current time as timestamp +I (78102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (78109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (78198) SEN66: RTC is uninitialized, using current time as timestamp +W (78470) wifi:Haven't to connect to a suitable AP now! +I (78602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (78609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (78820) BME688: RTC is uninitialized, using current time as timestamp +I (79102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (79109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (79198) SEN66: RTC is uninitialized, using current time as timestamp +W (79471) wifi:Haven't to connect to a suitable AP now! +I (79602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (79609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (79820) BME688: RTC is uninitialized, using current time as timestamp +I (80102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (80109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (80198) SEN66: RTC is uninitialized, using current time as timestamp +W (80472) wifi:Haven't to connect to a suitable AP now! +I (80602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (80609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +E (80797) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (80798) esp-tls: Failed to open new connection +E (80799) transport_base: Failed to open a new connection +E (80805) HTTP_CLIENT: Connection failed, sock < 0 +E (80809) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 +E (80817) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT +W (80820) BME688: RTC is uninitialized, using current time as timestamp +I (81102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (81109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (81198) SEN66: RTC is uninitialized, using current time as timestamp +W (81473) wifi:Haven't to connect to a suitable AP now! +I (81602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (81609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (81831) BME688: RTC is uninitialized, using current time as timestamp +I (82102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (82109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (82198) SEN66: RTC is uninitialized, using current time as timestamp +W (82474) wifi:Haven't to connect to a suitable AP now! +I (82602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (82609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (82831) BME688: RTC is uninitialized, using current time as timestamp +I (83102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (83109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (83198) SEN66: RTC is uninitialized, using current time as timestamp +W (83475) wifi:Haven't to connect to a suitable AP now! +I (83602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (83609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (83831) BME688: RTC is uninitialized, using current time as timestamp +I (84102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (84109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (84198) SEN66: RTC is uninitialized, using current time as timestamp +W (84476) wifi:Haven't to connect to a suitable AP now! +I (84602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (84609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (84831) BME688: RTC is uninitialized, using current time as timestamp +I (85102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (85109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (85198) SEN66: RTC is uninitialized, using current time as timestamp +W (85477) wifi:Haven't to connect to a suitable AP now! +I (85602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (85609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (85831) BME688: RTC is uninitialized, using current time as timestamp +I (86102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (86109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (86198) SEN66: RTC is uninitialized, using current time as timestamp +W (86478) wifi:Haven't to connect to a suitable AP now! +I (86602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (86609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (86831) BME688: RTC is uninitialized, using current time as timestamp +I (87102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (87109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (87198) SEN66: RTC is uninitialized, using current time as timestamp +W (87479) wifi:Haven't to connect to a suitable AP now! +I (87602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (87609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (87831) BME688: RTC is uninitialized, using current time as timestamp +I (88102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (88109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (88198) SEN66: RTC is uninitialized, using current time as timestamp +W (88480) wifi:Haven't to connect to a suitable AP now! +I (88602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (88609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (88831) BME688: RTC is uninitialized, using current time as timestamp +I (89102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (89109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (89198) SEN66: RTC is uninitialized, using current time as timestamp +W (89481) wifi:Haven't to connect to a suitable AP now! +I (89602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (89609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (89831) BME688: RTC is uninitialized, using current time as timestamp +I (90102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (90109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (90198) SEN66: RTC is uninitialized, using current time as timestamp +W (90482) wifi:Haven't to connect to a suitable AP now! +I (90602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (90609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +I (90824) OTA_CLIENT: Device is not registered, attempting to register... +I (90826) OTA_CLIENT: HTTP client handle: 0x3fcf03a8 +W (90831) BME688: RTC is uninitialized, using current time as timestamp +I (91102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (91109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (91198) SEN66: RTC is uninitialized, using current time as timestamp +W (91483) wifi:Haven't to connect to a suitable AP now! +I (91602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (91609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (91831) BME688: RTC is uninitialized, using current time as timestamp +I (92102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (92109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (92198) SEN66: RTC is uninitialized, using current time as timestamp +W (92484) wifi:Haven't to connect to a suitable AP now! +I (92602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (92609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (92831) BME688: RTC is uninitialized, using current time as timestamp +I (93102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (93109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (93198) SEN66: RTC is uninitialized, using current time as timestamp +W (93485) wifi:Haven't to connect to a suitable AP now! +I (93602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (93609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (93831) BME688: RTC is uninitialized, using current time as timestamp +I (94102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (94109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (94198) SEN66: RTC is uninitialized, using current time as timestamp +W (94486) wifi:Haven't to connect to a suitable AP now! +I (94602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (94609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (94831) BME688: RTC is uninitialized, using current time as timestamp +I (95102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (95109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (95198) SEN66: RTC is uninitialized, using current time as timestamp +W (95487) wifi:Haven't to connect to a suitable AP now! +I (95602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (95609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (95831) BME688: RTC is uninitialized, using current time as timestamp +I (96102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (96109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (96198) SEN66: RTC is uninitialized, using current time as timestamp +W (96488) wifi:Haven't to connect to a suitable AP now! +I (96602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (96609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (96831) BME688: RTC is uninitialized, using current time as timestamp +I (97102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (97109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (97198) SEN66: RTC is uninitialized, using current time as timestamp +W (97489) wifi:Haven't to connect to a suitable AP now! +I (97602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (97609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +E (97827) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (97827) esp-tls: Failed to open new connection +E (97829) transport_base: Failed to open a new connection +W (97831) BME688: RTC is uninitialized, using current time as timestamp +E (97835) HTTP_CLIENT: Connection failed, sock < 0 +E (97845) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 +E (97854) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT +I (98102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (98109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (98198) SEN66: RTC is uninitialized, using current time as timestamp +W (98490) wifi:Haven't to connect to a suitable AP now! +I (98602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (98609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (98841) BME688: RTC is uninitialized, using current time as timestamp +I (99102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (99109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (99198) SEN66: RTC is uninitialized, using current time as timestamp +W (99491) wifi:Haven't to connect to a suitable AP now! +I (99602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (99609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (99841) BME688: RTC is uninitialized, using current time as timestamp +I (100102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (100109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (100198) SEN66: RTC is uninitialized, using current time as timestamp +W (100492) wifi:Haven't to connect to a suitable AP now! +I (100602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (100609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (100841) BME688: RTC is uninitialized, using current time as timestamp +I (101102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (101109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (101198) SEN66: RTC is uninitialized, using current time as timestamp +W (101493) wifi:Haven't to connect to a suitable AP now! +I (101602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (101609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (101841) BME688: RTC is uninitialized, using current time as timestamp +I (102102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (102109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (102198) SEN66: RTC is uninitialized, using current time as timestamp +W (102494) wifi:Haven't to connect to a suitable AP now! +I (102602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (102609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (102841) BME688: RTC is uninitialized, using current time as timestamp +I (103102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (103109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (103198) SEN66: RTC is uninitialized, using current time as timestamp +W (103495) wifi:Haven't to connect to a suitable AP now! +I (103602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (103609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (103841) BME688: RTC is uninitialized, using current time as timestamp +I (104102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (104109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (104198) SEN66: RTC is uninitialized, using current time as timestamp +W (104496) wifi:Haven't to connect to a suitable AP now! +I (104602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (104609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (104841) BME688: RTC is uninitialized, using current time as timestamp +I (105102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (105109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (105198) SEN66: RTC is uninitialized, using current time as timestamp +W (105497) wifi:Haven't to connect to a suitable AP now! +I (105602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (105609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (105841) BME688: RTC is uninitialized, using current time as timestamp +I (106102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (106109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (106198) SEN66: RTC is uninitialized, using current time as timestamp +W (106498) wifi:Haven't to connect to a suitable AP now! +I (106602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (106609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (106841) BME688: RTC is uninitialized, using current time as timestamp +I (107102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (107109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (107198) SEN66: RTC is uninitialized, using current time as timestamp +W (107499) wifi:Haven't to connect to a suitable AP now! +I (107602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (107609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (107841) BME688: RTC is uninitialized, using current time as timestamp +I (107861) OTA_CLIENT: Device is not registered, attempting to register... +I (107863) OTA_CLIENT: HTTP client handle: 0x3fcf03a8 +I (108102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (108109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (108198) SEN66: RTC is uninitialized, using current time as timestamp +W (108500) wifi:Haven't to connect to a suitable AP now! +I (108602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (108609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (108841) BME688: RTC is uninitialized, using current time as timestamp +I (109102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (109109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (109198) SEN66: RTC is uninitialized, using current time as timestamp +W (109501) wifi:Haven't to connect to a suitable AP now! +I (109602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (109609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (109841) BME688: RTC is uninitialized, using current time as timestamp +I (110102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (110109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (110198) SEN66: RTC is uninitialized, using current time as timestamp +W (110502) wifi:Haven't to connect to a suitable AP now! +I (110602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (110609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (110841) BME688: RTC is uninitialized, using current time as timestamp +I (111102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (111109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (111198) SEN66: RTC is uninitialized, using current time as timestamp +W (111503) wifi:Haven't to connect to a suitable AP now! +I (111602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (111609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (111841) BME688: RTC is uninitialized, using current time as timestamp +I (112102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (112109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (112198) SEN66: RTC is uninitialized, using current time as timestamp +W (112504) wifi:Haven't to connect to a suitable AP now! +I (112602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (112609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (112841) BME688: RTC is uninitialized, using current time as timestamp +I (113102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (113109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (113198) SEN66: RTC is uninitialized, using current time as timestamp +W (113505) wifi:Haven't to connect to a suitable AP now! +I (113602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (113609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (113841) BME688: RTC is uninitialized, using current time as timestamp +I (114102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (114109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (114198) SEN66: RTC is uninitialized, using current time as timestamp +W (114506) wifi:Haven't to connect to a suitable AP now! +I (114602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (114609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (114841) BME688: RTC is uninitialized, using current time as timestamp +E (114864) esp-tls: couldn't get hostname for :bin.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (114864) esp-tls: Failed to open new connection +E (114867) transport_base: Failed to open a new connection +E (114872) HTTP_CLIENT: Connection failed, sock < 0 +E (114876) OTA_CLIENT: [E-OTA-01] Error ESP_ERR_HTTP_CONNECT at ./main/ota_client.cpp:228 +E (114885) OTA_CLIENT: [E-OTA-12] Device registration failed: ESP_ERR_HTTP_CONNECT +I (115102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (115109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (115198) SEN66: RTC is uninitialized, using current time as timestamp +W (115507) wifi:Haven't to connect to a suitable AP now! +I (115602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (115609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (115841) BME688: RTC is uninitialized, using current time as timestamp +I (116102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (116109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (116198) SEN66: RTC is uninitialized, using current time as timestamp +W (116508) wifi:Haven't to connect to a suitable AP now! +I (116602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (116609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (116841) BME688: RTC is uninitialized, using current time as timestamp +I (117102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (117109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (117198) SEN66: RTC is uninitialized, using current time as timestamp +W (117509) wifi:Haven't to connect to a suitable AP now! +I (117602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (117609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (117841) BME688: RTC is uninitialized, using current time as timestamp +I (118102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (118109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (118198) SEN66: RTC is uninitialized, using current time as timestamp +W (118510) wifi:Haven't to connect to a suitable AP now! +I (118602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (118609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (118841) BME688: RTC is uninitialized, using current time as timestamp +I (119102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (119109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (119198) SEN66: RTC is uninitialized, using current time as timestamp +W (119511) wifi:Haven't to connect to a suitable AP now! +I (119602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (119609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +I (119817) INFLUX_BUFFER: Flushing chunk starting at line 10: +I (119818) INFLUX: Writing to InfluxDB +W (119841) BME688: RTC is uninitialized, using current time as timestamp +I (120102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (120109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (120198) SEN66: RTC is uninitialized, using current time as timestamp +W (120512) wifi:Haven't to connect to a suitable AP now! +I (120602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (120609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (120841) BME688: RTC is uninitialized, using current time as timestamp +I (121102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (121109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (121198) SEN66: RTC is uninitialized, using current time as timestamp +W (121513) wifi:Haven't to connect to a suitable AP now! +I (121602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (121609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (121841) BME688: RTC is uninitialized, using current time as timestamp +I (122102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (122109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (122198) SEN66: RTC is uninitialized, using current time as timestamp +W (122514) wifi:Haven't to connect to a suitable AP now! +I (122602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (122609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (122841) BME688: RTC is uninitialized, using current time as timestamp +W (122935) wifi:Haven't to connect to a suitable AP now! +I (122935) KOTA: health up=121s status=CONNECTION_ERROR wifi=disconnected rssi=0 time_valid=0 sd=ok buffer=764 heap=43167 psram=7.3M last_upload=never ota=unregistered +I (123102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (123109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (123198) SEN66: RTC is uninitialized, using current time as timestamp +W (123515) wifi:Haven't to connect to a suitable AP now! +I (123602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (123609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (123841) BME688: RTC is uninitialized, using current time as timestamp +I (124102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (124109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (124198) SEN66: RTC is uninitialized, using current time as timestamp +W (124516) wifi:Haven't to connect to a suitable AP now! +I (124602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (124609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (124841) BME688: RTC is uninitialized, using current time as timestamp +I (124892) OTA_CLIENT: Device is not registered, attempting to register... +I (124894) OTA_CLIENT: HTTP client handle: 0x3fcf1210 +I (125102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (125109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (125198) SEN66: RTC is uninitialized, using current time as timestamp +W (125517) wifi:Haven't to connect to a suitable AP now! +I (125602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (125609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (125841) BME688: RTC is uninitialized, using current time as timestamp +I (126102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (126109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (126198) SEN66: RTC is uninitialized, using current time as timestamp +W (126518) wifi:Haven't to connect to a suitable AP now! +I (126602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (126609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +E (126819) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (126820) esp-tls: Failed to open new connection +E (126822) transport_base: Failed to open a new connection +E (126828) HTTP_CLIENT: Connection failed, sock < 0 +I (126832) INFLUX: HTTP request performed, status code: 0 +E (126837) INFLUX: [E-INF-03] Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT +E (126845) INFLUX_BUFFER: [E-INF-17] Failed to write chunk to Influx: ESP_FAIL +W (126841) BME688: RTC is uninitialized, using current time as timestamp +I (127102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (127109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (127198) SEN66: RTC is uninitialized, using current time as timestamp +W (127519) wifi:Haven't to connect to a suitable AP now! +I (127602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (127609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (127858) BME688: RTC is uninitialized, using current time as timestamp +I (128102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (128109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (128198) SEN66: RTC is uninitialized, using current time as timestamp +W (128520) wifi:Haven't to connect to a suitable AP now! +I (128602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (128609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +I (128852) INFLUX: Writing to InfluxDB +W (128858) BME688: RTC is uninitialized, using current time as timestamp +I (129102) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (129109) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (129198) SEN66: RTC is uninitialized, using current time as timestamp +W (129521) wifi:Haven't to connect to a suitable AP now! +I (129602) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (129609) SENSOR_GATHERER: 2/4 tasks still running, waiting... +W (129858) BME688: RTC is uninitialized, using current time as timestamp --- disconnected --- \ No newline at end of file diff --git a/tests/fixtures/live_brownout_loop_A4CB8FC946F8_fw1.0.txt b/tests/fixtures/live_brownout_loop_A4CB8FC946F8_fw1.0.txt index bdff502..0fc1d3a 100644 --- a/tests/fixtures/live_brownout_loop_A4CB8FC946F8_fw1.0.txt +++ b/tests/fixtures/live_brownout_loop_A4CB8FC946F8_fw1.0.txt @@ -1,1936 +1,1936 @@ ---- connected to COM5 --- -I (16926) SENSOR_GATHERER: Sensor should now be asleep ---- reset issued by tool --- -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x15 (USB_UART_CHIP_RESET),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x4037f34a -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (25) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (28) boot: efuse block revision: v1.4 -I (32) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (43) boot: Enabling RNG early entropy source... -I (48) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (63) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (76) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (89) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (102) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (109) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (191) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (203) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (469) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (485) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (495) boot: Loaded app from partition at offset 0x40000 -I (495) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (506) octal_psram: density : 0x03 (64 Mbit) -I (508) octal_psram: good-die : 0x01 (Pass) -I (512) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (535) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (540) octal_psram: DriveStrength: 0x00 (1/1) -I (544) esp_psram: Found 8MB PSRAM device -I (548) esp_psram: Speed: 40MHz -I (551) cpu_start: Multicore app -I (1285) esp_psram: SPI SRAM memory test OK -I (1293) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1294) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1304) app_init: App version: 1.0 -I (1308) app_init: Compile time: Jun 29 2026 11:03:32 -I (1313) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1322) efuse_init: Min chip rev: v0.0 -I (1326) efuse_init: Max chip rev: v0.99 -I (1330) efuse_init: Chip rev: v0.2 -I (1334) heap_init: Initializing. RAM available for dynamic allocation: -I (1340) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1356) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1369) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1377) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1397) main_task: Started on CPU0 -I (1400) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1408) main_task: Calling app_main() -E (1411) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1436) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1441) MAIN: Matter node created successfully -W (1442) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1445) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1454) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1463) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1472) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1480) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1489) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1498) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1507) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1515) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1524) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1533) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1542) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1551) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1559) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1568) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1577) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1586) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1596) pp: pp rom version: e7ae62f -I (1598) net80211: net80211 rom version: e7ae62f -I (1603) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 -I (1621) wifi:wifi firmware version: 4df78f2 -I (1621) wifi:wifi certification version: v7.0 -I (1621) wifi:config NVS flash: enabled -I (1622) wifi:config nano formatting: disabled -I (1626) wifi:Init data frame dynamic rx buffer num: 32 -I (1631) wifi:Init static rx mgmt buffer num: 5 -I (1635) wifi:Init management short buffer num: 32 -I (1639) wifi:Init dynamic tx buffer num: 32 -I (1644) wifi:Init static tx FG buffer num: 2 -I (1648) wifi:Init static rx buffer size: 1600 -I (1652) wifi:Init static rx buffer num: 10 -I (1656) wifi:Init dynamic rx buffer num: 32 -I (1660) wifi_init: rx ba win: 6 -I (1663) wifi_init: accept mbox: 6 -I (1666) wifi_init: tcpip mbox: 32 -I (1669) wifi_init: udp mbox: 6 -I (1672) wifi_init: tcp mbox: 6 -I (1675) wifi_init: tcp tx win: 5760 -I (1678) wifi_init: tcp rx win: 5760 -I (1681) wifi_init: tcp mss: 1440 -I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15834 (0x3DDA) -I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1698) BLE_INIT: BT controller compile version [1bb2f50] -I (1703) BLE_INIT: Using main XTAL as clock source -I (1707) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1716) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1721) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -E BOD: Brownout detector was triggered - - -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x40376756 -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (24) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (27) boot: efuse block revision: v1.4 -I (31) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (42) boot: Enabling RNG early entropy source... -I (47) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (108) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (494) boot: Loaded app from partition at offset 0x40000 -I (495) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (505) octal_psram: density : 0x03 (64 Mbit) -I (507) octal_psram: good-die : 0x01 (Pass) -I (511) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (539) octal_psram: DriveStrength: 0x00 (1/1) -I (544) esp_psram: Found 8MB PSRAM device -I (547) esp_psram: Speed: 40MHz -I (550) cpu_start: Multicore app -I (1284) esp_psram: SPI SRAM memory test OK -I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1293) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1304) app_init: App version: 1.0 -I (1307) app_init: Compile time: Jun 29 2026 11:03:32 -I (1312) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1321) efuse_init: Min chip rev: v0.0 -I (1325) efuse_init: Max chip rev: v0.99 -I (1329) efuse_init: Chip rev: v0.2 -I (1333) heap_init: Initializing. RAM available for dynamic allocation: -I (1340) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1368) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1377) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1396) main_task: Started on CPU0 -I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1407) main_task: Calling app_main() -E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1439) MAIN: Matter node created successfully -W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1595) pp: pp rom version: e7ae62f -I (1597) net80211: net80211 rom version: e7ae62f -I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 -I (1619) wifi:wifi firmware version: 4df78f2 -I (1620) wifi:wifi certification version: v7.0 -I (1620) wifi:config NVS flash: enabled -I (1621) wifi:config nano formatting: disabled -I (1625) wifi:Init data frame dynamic rx buffer num: 32 -I (1630) wifi:Init static rx mgmt buffer num: 5 -I (1634) wifi:Init management short buffer num: 32 -I (1638) wifi:Init dynamic tx buffer num: 32 -I (1642) wifi:Init static tx FG buffer num: 2 -I (1647) wifi:Init static rx buffer size: 1600 -I (1651) wifi:Init static rx buffer num: 10 -I (1655) wifi:Init dynamic rx buffer num: 32 -I (1659) wifi_init: rx ba win: 6 -I (1661) wifi_init: accept mbox: 6 -I (1665) wifi_init: tcpip mbox: 32 -I (1668) wifi_init: udp mbox: 6 -I (1671) wifi_init: tcp mbox: 6 -I (1673) wifi_init: tcp tx win: 5760 -I (1677) wifi_init: tcp rx win: 5760 -I (1680) wifi_init: tcp mss: 1440 -I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15835 (0x3DDB) -I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1697) BLE_INIT: BT controller compile version [1bb2f50] -I (1702) BLE_INIT: Using main XTAL as clock source -I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -E BOD: Brownout detector was triggered - - -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x40376756 -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (24) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (27) boot: efuse block revision: v1.4 -I (31) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (42) boot: Enabling RNG early entropy source... -I (47) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (108) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (494) boot: Loaded app from partition at offset 0x40000 -I (494) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (505) octal_psram: density : 0x03 (64 Mbit) -I (507) octal_psram: good-die : 0x01 (Pass) -I (511) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (539) octal_psram: DriveStrength: 0x00 (1/1) -I (543) esp_psram: Found 8MB PSRAM device -I (547) esp_psram: Speed: 40MHz -I (550) cpu_start: Multicore app -I (1284) esp_psram: SPI SRAM memory test OK -I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1293) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1303) app_init: App version: 1.0 -I (1307) app_init: Compile time: Jun 29 2026 11:03:32 -I (1312) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1321) efuse_init: Min chip rev: v0.0 -I (1325) efuse_init: Max chip rev: v0.99 -I (1329) efuse_init: Chip rev: v0.2 -I (1333) heap_init: Initializing. RAM available for dynamic allocation: -I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1368) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1396) main_task: Started on CPU0 -I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1407) main_task: Calling app_main() -E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1439) MAIN: Matter node created successfully -W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1550) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1595) pp: pp rom version: e7ae62f -I (1597) net80211: net80211 rom version: e7ae62f -I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 -I (1619) wifi:wifi firmware version: 4df78f2 -I (1620) wifi:wifi certification version: v7.0 -I (1620) wifi:config NVS flash: enabled -I (1621) wifi:config nano formatting: disabled -I (1625) wifi:Init data frame dynamic rx buffer num: 32 -I (1630) wifi:Init static rx mgmt buffer num: 5 -I (1634) wifi:Init management short buffer num: 32 -I (1638) wifi:Init dynamic tx buffer num: 32 -I (1642) wifi:Init static tx FG buffer num: 2 -I (1647) wifi:Init static rx buffer size: 1600 -I (1651) wifi:Init static rx buffer num: 10 -I (1655) wifi:Init dynamic rx buffer num: 32 -I (1659) wifi_init: rx ba win: 6 -I (1661) wifi_init: accept mbox: 6 -I (1665) wifi_init: tcpip mbox: 32 -I (1668) wifi_init: udp mbox: 6 -I (1671) wifi_init: tcp mbox: 6 -I (1673) wifi_init: tcp tx win: 5760 -I (1677) wifi_init: tcp rx win: 5760 -I (1680) wifi_init: tcp mss: 1440 -I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15836 (0x3DDC) -I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1697) BLE_INIT: BT controller compile version [1bb2f50] -I (1702) BLE_INIT: Using main XTAL as clock source -I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -E BOD: Brownout detector was triggered - - -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x40376756 -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (24) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (27) boot: efuse block revision: v1.4 -I (31) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (42) boot: Enabling RNG early entropy source... -I (47) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (108) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (494) boot: Loaded app from partition at offset 0x40000 -I (494) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (505) octal_psram: density : 0x03 (64 Mbit) -I (507) octal_psram: good-die : 0x01 (Pass) -I (511) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (539) octal_psram: DriveStrength: 0x00 (1/1) -I (543) esp_psram: Found 8MB PSRAM device -I (547) esp_psram: Speed: 40MHz -I (550) cpu_start: Multicore app -I (1284) esp_psram: SPI SRAM memory test OK -I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1293) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1303) app_init: App version: 1.0 -I (1307) app_init: Compile time: Jun 29 2026 11:03:32 -I (1312) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1321) efuse_init: Min chip rev: v0.0 -I (1325) efuse_init: Max chip rev: v0.99 -I (1329) efuse_init: Chip rev: v0.2 -I (1333) heap_init: Initializing. RAM available for dynamic allocation: -I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1368) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1396) main_task: Started on CPU0 -I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1407) main_task: Calling app_main() -E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1439) MAIN: Matter node created successfully -W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1595) pp: pp rom version: e7ae62f -I (1597) net80211: net80211 rom version: e7ae62f -I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 -I (1619) wifi:wifi firmware version: 4df78f2 -I (1620) wifi:wifi certification version: v7.0 -I (1620) wifi:config NVS flash: enabled -I (1621) wifi:config nano formatting: disabled -I (1625) wifi:Init data frame dynamic rx buffer num: 32 -I (1630) wifi:Init static rx mgmt buffer num: 5 -I (1634) wifi:Init management short buffer num: 32 -I (1638) wifi:Init dynamic tx buffer num: 32 -I (1642) wifi:Init static tx FG buffer num: 2 -I (1647) wifi:Init static rx buffer size: 1600 -I (1651) wifi:Init static rx buffer num: 10 -I (1655) wifi:Init dynamic rx buffer num: 32 -I (1659) wifi_init: rx ba win: 6 -I (1662) wifi_init: accept mbox: 6 -I (1665) wifi_init: tcpip mbox: 32 -I (1668) wifi_init: udp mbox: 6 -I (1671) wifi_init: tcp mbox: 6 -I (1673) wifi_init: tcp tx win: 5760 -I (1677) wifi_init: tcp rx win: 5760 -I (1680) wifi_init: tcp mss: 1440 -I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15837 (0x3DDD) -I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1697) BLE_INIT: BT controller compile version [1bb2f50] -I (1702) BLE_INIT: Using main XTAL as clock source -I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -E BOD: Brownout detector was triggered - - -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x40376756 -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (24) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (27) boot: efuse block revision: v1.4 -I (31) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (42) boot: Enabling RNG early entropy source... -I (47) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (108) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (494) boot: Loaded app from partition at offset 0x40000 -I (494) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (505) octal_psram: density : 0x03 (64 Mbit) -I (507) octal_psram: good-die : 0x01 (Pass) -I (511) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (539) octal_psram: DriveStrength: 0x00 (1/1) -I (543) esp_psram: Found 8MB PSRAM device -I (547) esp_psram: Speed: 40MHz -I (550) cpu_start: Multicore app -I (1284) esp_psram: SPI SRAM memory test OK -I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1293) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1303) app_init: App version: 1.0 -I (1307) app_init: Compile time: Jun 29 2026 11:03:32 -I (1312) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1321) efuse_init: Min chip rev: v0.0 -I (1325) efuse_init: Max chip rev: v0.99 -I (1329) efuse_init: Chip rev: v0.2 -I (1333) heap_init: Initializing. RAM available for dynamic allocation: -I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1368) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1396) main_task: Started on CPU0 -I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1407) main_task: Calling app_main() -E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1439) MAIN: Matter node created successfully -W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1595) pp: pp rom version: e7ae62f -I (1597) net80211: net80211 rom version: e7ae62f -I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 -I (1619) wifi:wifi firmware version: 4df78f2 -I (1620) wifi:wifi certification version: v7.0 -I (1620) wifi:config NVS flash: enabled -I (1621) wifi:config nano formatting: disabled -I (1625) wifi:Init data frame dynamic rx buffer num: 32 -I (1630) wifi:Init static rx mgmt buffer num: 5 -I (1634) wifi:Init management short buffer num: 32 -I (1638) wifi:Init dynamic tx buffer num: 32 -I (1642) wifi:Init static tx FG buffer num: 2 -I (1647) wifi:Init static rx buffer size: 1600 -I (1651) wifi:Init static rx buffer num: 10 -I (1655) wifi:Init dynamic rx buffer num: 32 -I (1659) wifi_init: rx ba win: 6 -I (1661) wifi_init: accept mbox: 6 -I (1665) wifi_init: tcpip mbox: 32 -I (1668) wifi_init: udp mbox: 6 -I (1671) wifi_init: tcp mbox: 6 -I (1673) wifi_init: tcp tx win: 5760 -I (1677) wifi_init: tcp rx win: 5760 -I (1680) wifi_init: tcp mss: 1440 -I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15838 (0x3DDE) -I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1697) BLE_INIT: BT controller compile version [1bb2f50] -I (1702) BLE_INIT: Using main XTAL as clock source -I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -I (1768) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0) -I (1785) NimBLE: GAP procedure initiated: stop advertising. - -I (1787) CHIP[DL]: BLE host-controller synced -I (2287) chip[DL]: Starting ESP WiFi layer -I (2289) wifi:mode : sta (a4:cb:8f:c9:46:f8) -I (2290) wifi:enable tsf -W (2290) wifi:Haven't to connect to a suitable AP now! -I (2291) chip[DL]: -I (2291) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43 -Done driving station state, nothing else to do... -I (2300) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 -W (2304) wifi:Haven't to connect to a suitable AP now! -I (2315) chip[DL]: Done driving station state, nothing else to do... -I (2321) chip[SVR]: Initializing subscription resumption storage... -I (2328) chip[SVR]: Server initializing... -I (2331) chip[TS]: Last Known Good Time: 2026-08-04T05:56:06 -I (2337) chip[DMG]: AccessControl: initializing -I (2340) chip[DMG]: Examples::AccessControlDelegate::Init -I (2345) chip[DMG]: AccessControl: setting -I (2349) chip[DMG]: DefaultAclStorage: initializing -I (2354) chip[DMG]: DefaultAclStorage: 0 entries loaded -I (2359) chip[SVR]: WARNING: mTestEventTriggerDelegate is null -I (2378) chip[ZCL]: Using ZAP configuration... -I (2378) esp_matter_cluster: Cluster plugin init common callback -I (2379) chip[DMG]: AccessControlCluster: initializing -I (2382) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported -I (2392) chip[DIS]: Updating services using commissioning mode 1 -I (2396) chip[DIS]: CHIP minimal mDNS started advertising. -I (2401) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 -I (2411) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: 37BE0E5B446CAB54. -I (2421) chip[DIS]: mDNS service published: _matterc._udp -I (2426) chip[IN]: CASE Server enabling CASE session setups -I (2432) chip[SVR]: Joining Multicast groups -I (2435) chip[SVR]: Server Listening... -I (2440) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** -I (2450) data_model: Dynamic endpoint 0 added -E (2453) data_model: Cluster cannot be NULL. -I (2458) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** -I (2468) data_model: Dynamic endpoint 1 added -E (2472) data_model: Cluster cannot be NULL. -I (2477) chip[DL]: WIFI_EVENT_STA_START -I (2478) MAIN: ESP-Matter started successfully -W (2480) wifi:Haven't to connect to a suitable AP now! -I (2489) chip[DL]: Done driving station state, nothing else to do... -I (2496) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) -I (2506) NimBLE: GAP procedure initiated: advertise; -I (2506) NimBLE: disc_mode=2 - -I (2507) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 -I (2511) NimBLE: - -I (2513) chip[DL]: CHIPoBLE advertising started -I (2514) MAIN: Commissioning window opened -> -I (4060) I2C: Creating I2C mutex -I (14061) SPI: Creating SPI mutex -I (14103) INFLUX_BUFFER: SD card mounted successfully -I (14104) INFLUX_BUFFER: /sdcard exists, SD card initialized successfully -I (14107) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -W (14121) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -I (14145) BME688: Delaying for 10000 microseconds -I (14763) Flicker: Flicker sensor initialized on pin 14 -I (14765) temperature_sensor: Range [20°C ~ 100°C], error < 2°C -I (14767) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data -I (14767) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data -W (14772) wifi:Haven't to connect to a suitable AP now! -I (14768) SEN66: Creating sensor mutex -W (14782) AS7341: Reading spectral data from AS7341 -I (14775) INFLUX_BUFFER: Flushing chunk starting at line 10: -I (14796) INFLUX: Writing to InfluxDB -I (14784) SEN66: Waking up sensor by setting power GPIO to enabled level... -I (14783) DeviceStatus: Active tasks: 26 -I (14813) DeviceStatus: Task list: -gather_sensor_t X 1 1216 22 -IDLE1 R 0 812 6 -IDLE0 R 0 692 5 -gather_sensor_t B 1 2552 21 -ds1307_update B 1 800 17 -gather_task B 1 1756 19 -gather_task B 1 14032 20 -alive_diode_tas B 5 352 8 -reset_task B 0 3432 15 -tiT B 18 2420 10 -gather_sensor_t B 1 1688 24 -ota_task B 1 7516 18 -CHIP B 1 3700 12 -Tmr Svc B 1 1364 7 -gather_sensor_t B 1 1092 25 -sys_evt B 20 2684 9 -ipc0 S 24 372 1 -ipc1 S 24 524 2 -console B 5 648 16 -esp_timer S 22 3332 3 -gather_sensor_t B 1 2284 23 -gather_sensor_t B 1 2544 26 -wifi B 23 4276 11 -btController B 23 1960 13 -main B 1 876 4 -nimble_host B 21 1480 14 - -W (14878) DeviceStatus: RTC is uninitialized, using current time as timestamp -W (15105) AS7341: Reading basic counts data from AS7341 -W (15121) AS7341: Reading flicker detection state from AS7341 -W (15339) as7341: FD Status Register: 0x20 (0b00100000) -I (15345) AS7341: Updating Matter attributes with AS7341 data: 16909 -I (15345) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms -I (15359) Flicker: Starting to collect samples in gather task -I (15355) BME688: Delaying for 199517 microseconds -I (15347) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** -I (15374) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 16909 ********** -E (15386) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -E (15398) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -W (15400) AS7341: Formatting AS7341 data in line protocol -AS7341 line protocol: -as7341_adc,device=A4CB8FC946F8 f1=1,f2=2,f3=2,f4=3,f5=3,f6=4,f7=7,f8=9,clear=22,nir=5 946684801000000000 -as7341_basic_counts,device=A4CB8FC946F8 f1=2.23,f2=4.46,f3=4.46,f4=6.69,f5=6.69,f6=8.92,f7=15.62,f8=20.08,clear=49.08,nir=11.15 946684801000000000 -as7341_flicker,device=A4CB8FC946F8 state="unknown" 946684801000000000 -W (15890) DeviceStatus: RTC is uninitialized, using current time as timestamp -I (16383) Flicker: Finished collecting samples, notifying main task -I (16385) Flicker: Collected 1024 samples for dominant frequency analysis -I (16392) SENSOR_GATHERER: Sensor should now be asleep -I (16404) SENSOR_GATHERER: Sensor should now be asleep -I (16444) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** -I (16446) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** -E (16459) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -E (16471) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -W (16473) BME688: RTC is uninitialized, using current time as timestamp -I (16891) SENSOR_GATHERER: Sensor should now be asleep -W (17484) BME688: RTC is uninitialized, using current time as timestamp -W (18485) BME688: RTC is uninitialized, using current time as timestamp -W (19486) BME688: RTC is uninitialized, using current time as timestamp -I (19809) SEN66: Sent start measurement command, waiting for sensor to be ready... -I (19905) MICROPHONE: Read 300000 bytes in 4936 ms -I (19906) MICROPHONE: Read 300000 bytes of audio data -I (20098) MICROPHONE: Sum SQ: 0.049147, RMS pressure: 0.000701, Normalized: 0.000701, dB peak: -63.09 -W (20100) SEN66: RTC is uninitialized, using current time as timestamp -W (20487) BME688: RTC is uninitialized, using current time as timestamp -W (21112) SEN66: RTC is uninitialized, using current time as timestamp -W (21488) BME688: RTC is uninitialized, using current time as timestamp -E (21798) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (21800) esp-tls: Failed to open new connection -E (21801) transport_base: Failed to open a new connection -E (21812) HTTP_CLIENT: Connection failed, sock < 0 -I (21813) INFLUX: HTTP request performed, status code: 0 -E (21824) INFLUX: Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT -E (21826) INFLUX_BUFFER: Failed to write chunk to Influx: ESP_FAIL -W (22113) SEN66: RTC is uninitialized, using current time as timestamp -W (22489) BME688: RTC is uninitialized, using current time as timestamp -I (22769) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (23114) SEN66: RTC is uninitialized, using current time as timestamp -I (23270) SENSOR_GATHERER: 1/2 tasks still running, waiting... -E BOD: Brownout detector was triggered - - -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x40376756 -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (24) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (27) boot: efuse block revision: v1.4 -I (31) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (42) boot: Enabling RNG early entropy source... -I (47) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (108) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (494) boot: Loaded app from partition at offset 0x40000 -I (494) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (505) octal_psram: density : 0x03 (64 Mbit) -I (507) octal_psram: good-die : 0x01 (Pass) -I (511) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (539) octal_psram: DriveStrength: 0x00 (1/1) -I (543) esp_psram: Found 8MB PSRAM device -I (547) esp_psram: Speed: 40MHz -I (550) cpu_start: Multicore app -I (1284) esp_psram: SPI SRAM memory test OK -I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1293) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1303) app_init: App version: 1.0 -I (1307) app_init: Compile time: Jun 29 2026 11:03:32 -I (1312) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1321) efuse_init: Min chip rev: v0.0 -I (1325) efuse_init: Max chip rev: v0.99 -I (1329) efuse_init: Chip rev: v0.2 -I (1333) heap_init: Initializing. RAM available for dynamic allocation: -I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1368) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1396) main_task: Started on CPU0 -I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1407) main_task: Calling app_main() -E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1439) MAIN: Matter node created successfully -W (1440) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1470) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1505) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1584) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1595) pp: pp rom version: e7ae62f -I (1596) net80211: net80211 rom version: e7ae62f -I (1602) wifi:wifi driver task: 3fcc524c, prio:23, stack:6656, core=0 -I (1619) wifi:wifi firmware version: 4df78f2 -I (1620) wifi:wifi certification version: v7.0 -I (1620) wifi:config NVS flash: enabled -I (1620) wifi:config nano formatting: disabled -I (1625) wifi:Init data frame dynamic rx buffer num: 32 -I (1630) wifi:Init static rx mgmt buffer num: 5 -I (1634) wifi:Init management short buffer num: 32 -I (1638) wifi:Init dynamic tx buffer num: 32 -I (1642) wifi:Init static tx FG buffer num: 2 -I (1646) wifi:Init static rx buffer size: 1600 -I (1651) wifi:Init static rx buffer num: 10 -I (1654) wifi:Init dynamic rx buffer num: 32 -I (1659) wifi_init: rx ba win: 6 -I (1661) wifi_init: accept mbox: 6 -I (1664) wifi_init: tcpip mbox: 32 -I (1667) wifi_init: udp mbox: 6 -I (1670) wifi_init: tcp mbox: 6 -I (1673) wifi_init: tcp tx win: 5760 -I (1677) wifi_init: tcp rx win: 5760 -I (1680) wifi_init: tcp mss: 1440 -I (1691) chip[DL]: NVS set: chip-counters/reboot-count = 15839 (0x3DDF) -I (1694) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1697) BLE_INIT: BT controller compile version [1bb2f50] -I (1701) BLE_INIT: Using main XTAL as clock source -I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -E BOD: Brownout detector was triggered - - -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x40376756 -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (24) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (27) boot: efuse block revision: v1.4 -I (31) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (42) boot: Enabling RNG early entropy source... -I (47) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (108) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (469) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (494) boot: Loaded app from partition at offset 0x40000 -I (495) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (505) octal_psram: density : 0x03 (64 Mbit) -I (507) octal_psram: good-die : 0x01 (Pass) -I (511) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (539) octal_psram: DriveStrength: 0x00 (1/1) -I (544) esp_psram: Found 8MB PSRAM device -I (547) esp_psram: Speed: 40MHz -I (550) cpu_start: Multicore app -I (1284) esp_psram: SPI SRAM memory test OK -I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1293) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1304) app_init: App version: 1.0 -I (1307) app_init: Compile time: Jun 29 2026 11:03:32 -I (1312) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1321) efuse_init: Min chip rev: v0.0 -I (1325) efuse_init: Max chip rev: v0.99 -I (1329) efuse_init: Chip rev: v0.2 -I (1333) heap_init: Initializing. RAM available for dynamic allocation: -I (1340) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1368) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1377) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1396) main_task: Started on CPU0 -I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1407) main_task: Calling app_main() -E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1439) MAIN: Matter node created successfully -W (1440) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1461) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1470) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1505) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1540) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1575) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1584) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1595) pp: pp rom version: e7ae62f -I (1596) net80211: net80211 rom version: e7ae62f -I (1602) wifi:wifi driver task: 3fcc524c, prio:23, stack:6656, core=0 -I (1619) wifi:wifi firmware version: 4df78f2 -I (1620) wifi:wifi certification version: v7.0 -I (1620) wifi:config NVS flash: enabled -I (1620) wifi:config nano formatting: disabled -I (1625) wifi:Init data frame dynamic rx buffer num: 32 -I (1630) wifi:Init static rx mgmt buffer num: 5 -I (1634) wifi:Init management short buffer num: 32 -I (1638) wifi:Init dynamic tx buffer num: 32 -I (1642) wifi:Init static tx FG buffer num: 2 -I (1646) wifi:Init static rx buffer size: 1600 -I (1650) wifi:Init static rx buffer num: 10 -I (1654) wifi:Init dynamic rx buffer num: 32 -I (1659) wifi_init: rx ba win: 6 -I (1661) wifi_init: accept mbox: 6 -I (1664) wifi_init: tcpip mbox: 32 -I (1667) wifi_init: udp mbox: 6 -I (1670) wifi_init: tcp mbox: 6 -I (1673) wifi_init: tcp tx win: 5760 -I (1676) wifi_init: tcp rx win: 5760 -I (1680) wifi_init: tcp mss: 1440 -I (1691) chip[DL]: NVS set: chip-counters/reboot-count = 15840 (0x3DE0) -I (1694) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1696) BLE_INIT: BT controller compile version [1bb2f50] -I (1701) BLE_INIT: Using main XTAL as clock source -I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1714) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -I (1767) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0) -I (1804) NimBLE: GAP procedure initiated: stop advertising. - -I (1806) CHIP[DL]: BLE host-controller synced -I (2306) chip[DL]: Starting ESP WiFi layer -I (2308) wifi:mode : sta (a4:cb:8f:c9:46:f8) -I (2309) wifi:enable tsf -W (2309) wifi:Haven't to connect to a suitable AP now! -I (2310) chip[DL]: -I (2310) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43 -Done driving station state, nothing else to do... -I (2319) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 -W (2323) wifi:Haven't to connect to a suitable AP now! -I (2334) chip[DL]: Done driving station state, nothing else to do... -I (2340) chip[SVR]: Initializing subscription resumption storage... -I (2347) chip[SVR]: Server initializing... -I (2350) chip[TS]: Last Known Good Time: 2026-08-04T05:56:06 -I (2356) chip[DMG]: AccessControl: initializing -I (2359) chip[DMG]: Examples::AccessControlDelegate::Init -I (2364) chip[DMG]: AccessControl: setting -I (2368) chip[DMG]: DefaultAclStorage: initializing -I (2373) chip[DMG]: DefaultAclStorage: 0 entries loaded -I (2378) chip[SVR]: WARNING: mTestEventTriggerDelegate is null -I (2397) chip[ZCL]: Using ZAP configuration... -I (2397) esp_matter_cluster: Cluster plugin init common callback -I (2398) chip[DMG]: AccessControlCluster: initializing -I (2401) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported -I (2411) chip[DIS]: Updating services using commissioning mode 1 -I (2415) chip[DIS]: CHIP minimal mDNS started advertising. -I (2420) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 -I (2430) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: 67895F766C4DDEDA. -I (2440) chip[DIS]: mDNS service published: _matterc._udp -I (2445) chip[IN]: CASE Server enabling CASE session setups -I (2451) chip[SVR]: Joining Multicast groups -I (2454) chip[SVR]: Server Listening... -I (2459) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** -I (2469) data_model: Dynamic endpoint 0 added -E (2472) data_model: Cluster cannot be NULL. -I (2477) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** -I (2487) data_model: Dynamic endpoint 1 added -E (2491) data_model: Cluster cannot be NULL. -I (2496) chip[DL]: WIFI_EVENT_STA_START -I (2497) MAIN: ESP-Matter started successfully -W (2499) wifi:Haven't to connect to a suitable AP now! -I (2508) chip[DL]: Done driving station state, nothing else to do... -I (2515) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) -I (2525) NimBLE: GAP procedure initiated: advertise; -I (2525) NimBLE: disc_mode=2 - -I (2526) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 -I (2530) NimBLE: - -I (2532) chip[DL]: CHIPoBLE advertising started -I (2533) MAIN: Commissioning window opened -> -I (4079) I2C: Creating I2C mutex -I (14080) SPI: Creating SPI mutex -I (14122) INFLUX_BUFFER: SD card mounted successfully -I (14123) INFLUX_BUFFER: /sdcard exists, SD card initialized successfully -I (14126) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -W (14140) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -I (14164) BME688: Delaying for 10000 microseconds -I (14782) Flicker: Flicker sensor initialized on pin 14 -I (14784) temperature_sensor: Range [20°C ~ 100°C], error < 2°C -I (14786) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data -W (14791) wifi:Haven't to connect to a suitable AP now! -I (14794) INFLUX_BUFFER: Flushing chunk starting at line 10: -I (14786) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data -W (14799) AS7341: Reading spectral data from AS7341 -I (14787) SEN66: Creating sensor mutex -I (14815) SEN66: Waking up sensor by setting power GPIO to enabled level... -I (14802) INFLUX: Writing to InfluxDB -I (14802) DeviceStatus: Active tasks: 26 -I (14832) DeviceStatus: Task list: -main R 1 876 4 -gather_sensor_t X 1 1352 24 -IDLE1 R 0 700 6 -IDLE0 R 0 692 5 -gather_sensor_t B 1 2544 21 -ds1307_update B 1 800 17 -gather_task B 1 1756 19 -gather_task B 1 14032 20 -alive_diode_tas B 5 356 8 -reset_task B 0 3432 15 -tiT B 18 2424 10 -gather_sensor_t B 1 1724 23 -ota_task B 1 7516 18 -CHIP B 1 3708 12 -Tmr Svc B 1 1364 7 -gather_sensor_t B 1 1300 25 -sys_evt B 20 2672 9 -ipc0 S 24 372 1 -ipc1 S 24 524 2 -console B 5 648 16 -esp_timer S 22 3332 3 -gather_sensor_t B 1 2332 22 -gather_sensor_t B 1 2544 26 -wifi B 23 4280 11 -btController B 23 1952 13 -nimble_host B 21 1480 14 - -W (14898) DeviceStatus: RTC is uninitialized, using current time as timestamp -W (15125) AS7341: Reading basic counts data from AS7341 -W (15141) AS7341: Reading flicker detection state from AS7341 -W (15359) as7341: FD Status Register: 0x20 (0b00100000) -I (15365) AS7341: Updating Matter attributes with AS7341 data: 16909 -I (15365) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms -I (15379) Flicker: Starting to collect samples in gather task -I (15375) BME688: Delaying for 199517 microseconds -I (15366) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** -I (15394) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 16909 ********** -E (15406) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -E (15418) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -W (15420) AS7341: Formatting AS7341 data in line protocol -AS7341 line protocol: -as7341_adc,device=A4CB8FC946F8 f1=1,f2=2,f3=3,f4=4,f5=3,f6=4,f7=7,f8=9,clear=22,nir=5 946684801000000000 -as7341_basic_counts,device=A4CB8FC946F8 f1=2.23,f2=4.46,f3=6.69,f4=8.92,f5=6.69,f6=8.92,f7=15.62,f8=20.08,clear=49.08,nir=11.15 946684801000000000 -as7341_flicker,device=A4CB8FC946F8 state="unknown" 946684801000000000 -W (15910) DeviceStatus: RTC is uninitialized, using current time as timestamp -I (16403) Flicker: Finished collecting samples, notifying main task -I (16405) Flicker: Collected 1024 samples for dominant frequency analysis -I (16412) SENSOR_GATHERER: Sensor should now be asleep -I (16424) SENSOR_GATHERER: Sensor should now be asleep -I (16464) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** -I (16466) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** -E (16479) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -E (16491) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -W (16493) BME688: RTC is uninitialized, using current time as timestamp -I (16911) SENSOR_GATHERER: Sensor should now be asleep -W (17504) BME688: RTC is uninitialized, using current time as timestamp -W (18505) BME688: RTC is uninitialized, using current time as timestamp -W (19506) BME688: RTC is uninitialized, using current time as timestamp -I (19826) SEN66: Sent start measurement command, waiting for sensor to be ready... -I (19923) MICROPHONE: Read 300000 bytes in 4936 ms -I (19924) MICROPHONE: Read 300000 bytes of audio data -I (20116) MICROPHONE: Sum SQ: 0.135236, RMS pressure: 0.001163, Normalized: 0.001163, dB peak: -58.69 -W (20118) SEN66: RTC is uninitialized, using current time as timestamp -W (20507) BME688: RTC is uninitialized, using current time as timestamp -W (21130) SEN66: RTC is uninitialized, using current time as timestamp -W (21508) BME688: RTC is uninitialized, using current time as timestamp -E (21833) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (21835) esp-tls: Failed to open new connection -E (21846) transport_base: Failed to open a new connection -E (21847) HTTP_CLIENT: Connection failed, sock < 0 -I (21848) INFLUX: HTTP request performed, status code: 0 -E (21859) INFLUX: Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT -E (21861) INFLUX_BUFFER: Failed to write chunk to Influx: ESP_FAIL -W (22131) SEN66: RTC is uninitialized, using current time as timestamp -W (22509) BME688: RTC is uninitialized, using current time as timestamp -I (22799) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (23132) SEN66: RTC is uninitialized, using current time as timestamp -I (23300) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (23510) BME688: RTC is uninitialized, using current time as timestamp -E BOD: Brownout detector was triggered - - -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x40376756 -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (24) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (27) boot: efuse block revision: v1.4 -I (31) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (42) boot: Enabling RNG early entropy source... -I (47) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (108) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (494) boot: Loaded app from partition at offset 0x40000 -I (494) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (505) octal_psram: density : 0x03 (64 Mbit) -I (507) octal_psram: good-die : 0x01 (Pass) -I (511) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (539) octal_psram: DriveStrength: 0x00 (1/1) -I (543) esp_psram: Found 8MB PSRAM device -I (547) esp_psram: Speed: 40MHz -I (550) cpu_start: Multicore app -I (1284) esp_psram: SPI SRAM memory test OK -I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1293) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1303) app_init: App version: 1.0 -I (1307) app_init: Compile time: Jun 29 2026 11:03:32 -I (1312) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1321) efuse_init: Min chip rev: v0.0 -I (1325) efuse_init: Max chip rev: v0.99 -I (1329) efuse_init: Chip rev: v0.2 -I (1333) heap_init: Initializing. RAM available for dynamic allocation: -I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1368) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1396) main_task: Started on CPU0 -I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1407) main_task: Calling app_main() -E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1439) MAIN: Matter node created successfully -W (1440) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1470) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1505) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1540) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1575) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1584) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1595) pp: pp rom version: e7ae62f -I (1596) net80211: net80211 rom version: e7ae62f -I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 -I (1619) wifi:wifi firmware version: 4df78f2 -I (1620) wifi:wifi certification version: v7.0 -I (1620) wifi:config NVS flash: enabled -I (1620) wifi:config nano formatting: disabled -I (1625) wifi:Init data frame dynamic rx buffer num: 32 -I (1630) wifi:Init static rx mgmt buffer num: 5 -I (1634) wifi:Init management short buffer num: 32 -I (1638) wifi:Init dynamic tx buffer num: 32 -I (1642) wifi:Init static tx FG buffer num: 2 -I (1646) wifi:Init static rx buffer size: 1600 -I (1650) wifi:Init static rx buffer num: 10 -I (1654) wifi:Init dynamic rx buffer num: 32 -I (1659) wifi_init: rx ba win: 6 -I (1661) wifi_init: accept mbox: 6 -I (1664) wifi_init: tcpip mbox: 32 -I (1667) wifi_init: udp mbox: 6 -I (1670) wifi_init: tcp mbox: 6 -I (1673) wifi_init: tcp tx win: 5760 -I (1676) wifi_init: tcp rx win: 5760 -I (1680) wifi_init: tcp mss: 1440 -I (1691) chip[DL]: NVS set: chip-counters/reboot-count = 15841 (0x3DE1) -I (1694) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1696) BLE_INIT: BT controller compile version [1bb2f50] -I (1701) BLE_INIT: Using main XTAL as clock source -I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1714) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -I (1766) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0) -I (1806) NimBLE: GAP procedure initiated: stop advertising. - -I (1807) CHIP[DL]: BLE host-controller synced -I (2308) chip[DL]: Starting ESP WiFi layer -I (2310) wifi:mode : sta (a4:cb:8f:c9:46:f8) -I (2311) wifi:enable tsf -W (2311) wifi:Haven't to connect to a suitable AP now! -I (2312) chip[DL]: -I (2312) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43 -Done driving station state, nothing else to do... -I (2321) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 -W (2325) wifi:Haven't to connect to a suitable AP now! -I (2336) chip[DL]: Done driving station state, nothing else to do... -I (2342) chip[SVR]: Initializing subscription resumption storage... -I (2349) chip[SVR]: Server initializing... -I (2352) chip[TS]: Last Known Good Time: 2026-08-04T05:56:06 -I (2358) chip[DMG]: AccessControl: initializing -I (2361) chip[DMG]: Examples::AccessControlDelegate::Init -I (2366) chip[DMG]: AccessControl: setting -I (2370) chip[DMG]: DefaultAclStorage: initializing -I (2375) chip[DMG]: DefaultAclStorage: 0 entries loaded -I (2380) chip[SVR]: WARNING: mTestEventTriggerDelegate is null -I (2399) chip[ZCL]: Using ZAP configuration... -I (2399) esp_matter_cluster: Cluster plugin init common callback -I (2400) chip[DMG]: AccessControlCluster: initializing -I (2403) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported -I (2413) chip[DIS]: Updating services using commissioning mode 1 -I (2417) chip[DIS]: CHIP minimal mDNS started advertising. -I (2422) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 -I (2432) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: 3B5442BDAD98C80C. -I (2442) chip[DIS]: mDNS service published: _matterc._udp -I (2447) chip[IN]: CASE Server enabling CASE session setups -I (2453) chip[SVR]: Joining Multicast groups -I (2456) chip[SVR]: Server Listening... -I (2461) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** -I (2471) data_model: Dynamic endpoint 0 added -E (2474) data_model: Cluster cannot be NULL. -I (2479) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** -I (2489) data_model: Dynamic endpoint 1 added -E (2493) data_model: Cluster cannot be NULL. -I (2498) chip[DL]: WIFI_EVENT_STA_START -I (2499) MAIN: ESP-Matter started successfully -W (2501) wifi:Haven't to connect to a suitable AP now! -I (2510) chip[DL]: Done driving station state, nothing else to do... -I (2517) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) -I (2527) NimBLE: GAP procedure initiated: advertise; -I (2527) NimBLE: disc_mode=2 - -I (2528) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 -I (2532) NimBLE: - -I (2534) chip[DL]: CHIPoBLE advertising started -I (2535) MAIN: Commissioning window opened -> -I (4082) I2C: Creating I2C mutex -I (14083) SPI: Creating SPI mutex -I (14125) INFLUX_BUFFER: SD card mounted successfully -I (14126) INFLUX_BUFFER: /sdcard exists, SD card initialized successfully -I (14129) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -W (14143) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -I (14167) BME688: Delaying for 10000 microseconds -I (14785) Flicker: Flicker sensor initialized on pin 14 -I (14787) temperature_sensor: Range [20°C ~ 100°C], error < 2°C -I (14789) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data -I (14789) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data -W (14794) wifi: -I (14790) SEN66: Creating sensor mutex -Haven't to connect to a suitable AP now! -I (14805) SEN66: Waking up sensor by setting power GPIO to enabled level... - -I (14803) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms -I (14830) Flicker: Starting to collect samples in gather task -I (14818) DeviceStatus: Active tasks: 26 -I (14797) INFLUX_BUFFER: Flushing chunk starting at line 10: -I (14847) INFLUX: Writing to InfluxDB -I (14846) DeviceStatus: Task list: -gather_sensor_t X 1 1140 22 -main R 1 876 4 -IDLE1 R 0 700 6 -IDLE0 R 0 692 5 -ds1307_update B 1 808 17 -gather_task B 1 1860 19 -gather_task B 1 14040 20 -alive_diode_tas B 5 352 8 -reset_task B 0 3424 15 -tiT B 18 2420 10 -gather_sensor_t B 23 1720 24 -ota_task B 1 7508 18 -CHIP B 1 3636 12 -Tmr Svc B 1 1364 7 -gather_sensor_t B 1 2544 21 -sys_evt B 20 2684 9 -ipc0 S 24 372 1 -ipc1 S 24 524 2 -console B 5 640 16 -esp_timer S 22 3332 3 -gather_sensor_t B 1 2284 23 -gather_sensor_t B 1 2324 25 -wifi B 23 4324 11 -btController B 23 1960 13 -gather_sensor_t S 1 1344 26 -flicker_gather_ S 24 2060 27 -nimble_host B 21 1472 14 - -W (14924) DeviceStatus: RTC is uninitialized, using current time as timestamp -I (15857) Flicker: Finished collecting samples, notifying main task -I (15859) Flicker: Collected 1024 samples for dominant frequency analysis -W (15866) AS7341: Reading spectral data from AS7341 -I (15867) SENSOR_GATHERER: Sensor should now be asleep -I (15876) BME688: Delaying for 199517 microseconds -W (15926) DeviceStatus: RTC is uninitialized, using current time as timestamp -W (16176) AS7341: Reading basic counts data from AS7341 -W (16192) AS7341: Reading flicker detection state from AS7341 -W (16410) as7341: FD Status Register: 0x20 (0b00100000) -I (16416) AS7341: Updating Matter attributes with AS7341 data: 16707 -I (16417) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** -I (16430) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 16707 ********** -E (16443) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -E (16445) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -W (16457) AS7341: RTC is uninitialized, using current time as timestamp -I (16458) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** -I (16471) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** -E (16484) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -E (16496) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -W (16497) BME688: RTC is uninitialized, using current time as timestamp -I (16927) SENSOR_GATHERER: Sensor should now be asleep -W (17458) AS7341: RTC is uninitialized, using current time as timestamp -W (17509) BME688: RTC is uninitialized, using current time as timestamp -W (18459) AS7341: RTC is uninitialized, using current time as timestamp -W (18510) BME688: RTC is uninitialized, using current time as timestamp -W (19460) AS7341: RTC is uninitialized, using current time as timestamp -W (19511) BME688: RTC is uninitialized, using current time as timestamp -I (19817) SEN66: Sent start measurement command, waiting for sensor to be ready... -I (19927) MICROPHONE: Read 300000 bytes in 4936 ms -I (19928) MICROPHONE: Read 300000 bytes of audio data -I (20119) MICROPHONE: Sum SQ: 0.185352, RMS pressure: 0.001361, Normalized: 0.001361, dB peak: -57.32 -W (20121) SEN66: RTC is uninitialized, using current time as timestamp -W (20461) AS7341: RTC is uninitialized, using current time as timestamp -W (20512) BME688: RTC is uninitialized, using current time as timestamp -W (21133) SEN66: RTC is uninitialized, using current time as timestamp -W (21462) AS7341: RTC is uninitialized, using current time as timestamp -W (21513) BME688: RTC is uninitialized, using current time as timestamp -E (21849) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 -E (21851) esp-tls: Failed to open new connection -E (21852) transport_base: Failed to open a new connection -E (21863) HTTP_CLIENT: Connection failed, sock < 0 -I (21864) INFLUX: HTTP request performed, status code: 0 -E (21875) INFLUX: Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT -E (21877) INFLUX_BUFFER: Failed to write chunk to Influx: ESP_FAIL -W (22134) SEN66: RTC is uninitialized, using current time as timestamp -W (22463) AS7341: RTC is uninitialized, using current time as timestamp -W (22514) BME688: RTC is uninitialized, using current time as timestamp -I (22791) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (23135) SEN66: RTC is uninitialized, using current time as timestamp -I (23292) SENSOR_GATHERER: 1/2 tasks still running, waiting... -W (23464) AS7341: RTC is uninitialized, using current time as timestamp -W (23515) BME688: RTC is uninitialized, using current time as timestamp -I (23793) SENSOR_GATHERER: 1/2 tasks still running, waiting... -I (23888) INFLUX: Writing to InfluxDB -E BOD: Brownout detector was triggered - -Guru Meditation Error: Core 0 panic'ed (Interrupt wdt timeout on CPU0). - -Core 0 register dump: -PC : 0x4037f211 PS : 0x00060d34 A0 : 0x80382ff0 A1 : 0x3fc9ec40 -A2 : 0x3fc9c7c8 A3 : 0xb33fffff A4 : 0x0000cdcd A5 : 0x00060923 -A6 : 0x3fceff68 A7 : 0x0000cdcd A8 : 0xfffffff1 A9 : 0x3fc9c7c8 -A10 : 0x01ffffff A11 : 0x0000cdcd A12 : 0x00001eae A13 : 0x40383206 -A14 : 0x3fcef74c A15 : 0x3fc9e9a0 SAR : 0x0000000c EXCCAUSE: 0x00000005 -EXCVADDR: 0x00000000 LBEG : 0x00000000 LEND : 0x00000000 LCOUNT : 0x00000000 -Core 0 was running in ISR context: -EPC1 : 0x42156637 EPC2 : 0x4037f34a EPC3 : 0x40041a76 EPC4 : 0x4037f211 - - -Backtrace: 0x4037f20e:0x3fc9ec40 0x40382fed:0x3fc9ec60 0x40384c1c:0x3fc9ec90 0x40382a9d:0x3fc9ecb0 0x40377011:0x3fc9ecd0 0x4037b2f9:0x3fc9ecf0 0x4037b32b:0x3fc9ed20 0x4037768d:0x3fc9ed40 0x4037b965:0x3fc9ed60 0x40041a73:0x3fc9ee20 0x40049059:0x3fc9ee40 0x40048818:0x3fc9ee60 0x40048c4b:0x3fc9ee80 0x40043cf3:0x3fc9eea0 0x40043cd5:0x3fc9eec0 0x40044183:0x3fc9eee0 0x40044281:0x3fc9ef60 0x40376715:0x3fc9efb0 0x4037664b:0x3fc9efd0 0x4037b819:0x3fc9eff0 0x4037f347:0x3fcb80e0 0x4216eb41:0x3fcb8100 0x4038429b:0x3fcb8120 0x40382e6d:0x3fcb8140 - - - - -ELF file SHA256: 45fa9d5b9 - -Rebooting... -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0xc (RTC_SW_CPU_RST),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x4038236c -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (24) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (27) boot: efuse block revision: v1.4 -I (31) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (42) boot: Enabling RNG early entropy source... -I (47) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (108) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (494) boot: Loaded app from partition at offset 0x40000 -I (494) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (505) octal_psram: density : 0x03 (64 Mbit) -I (507) octal_psram: good-die : 0x01 (Pass) -I (511) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (539) octal_psram: DriveStrength: 0x00 (1/1) -I (543) esp_psram: Found 8MB PSRAM device -I (547) esp_psram: Speed: 40MHz -I (550) cpu_start: Multicore app -I (1284) esp_psram: SPI SRAM memory test OK -I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1293) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1303) app_init: App version: 1.0 -I (1307) app_init: Compile time: Jun 29 2026 11:03:32 -I (1312) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1321) efuse_init: Min chip rev: v0.0 -I (1325) efuse_init: Max chip rev: v0.99 -I (1329) efuse_init: Chip rev: v0.2 -I (1333) heap_init: Initializing. RAM available for dynamic allocation: -I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1368) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1396) main_task: Started on CPU0 -I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1407) main_task: Calling app_main() -E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1440) MAIN: Matter node created successfully -W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1550) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1595) pp: pp rom version: e7ae62f -I (1597) net80211: net80211 rom version: e7ae62f -I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 -I (1620) wifi:wifi firmware version: 4df78f2 -I (1620) wifi:wifi certification version: v7.0 -I (1620) wifi:config NVS flash: enabled -I (1621) wifi:config nano formatting: disabled -I (1625) wifi:Init data frame dynamic rx buffer num: 32 -I (1630) wifi:Init static rx mgmt buffer num: 5 -I (1634) wifi:Init management short buffer num: 32 -I (1638) wifi:Init dynamic tx buffer num: 32 -I (1643) wifi:Init static tx FG buffer num: 2 -I (1647) wifi:Init static rx buffer size: 1600 -I (1651) wifi:Init static rx buffer num: 10 -I (1655) wifi:Init dynamic rx buffer num: 32 -I (1659) wifi_init: rx ba win: 6 -I (1662) wifi_init: accept mbox: 6 -I (1665) wifi_init: tcpip mbox: 32 -I (1668) wifi_init: udp mbox: 6 -I (1671) wifi_init: tcp mbox: 6 -I (1674) wifi_init: tcp tx win: 5760 -I (1677) wifi_init: tcp rx win: 5760 -I (1680) wifi_init: tcp mss: 1440 -I (1691) chip[DL]: NVS set: chip-counters/reboot-count = 15842 (0x3DE2) -I (1694) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1697) BLE_INIT: BT controller compile version [1bb2f50] -I (1702) BLE_INIT: Using main XTAL as clock source -I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -E BOD: Brownout detector was triggered - - -ESP-ROM:esp32s3-20210327 -Build:Mar 27 2021 -rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) -Saved PC:0x40376756 -SPIWP:0xee -mode:DIO, clock div:1 -load:0x3fce2820,len:0x14f0 -load:0x403c8700,len:0xd24 -load:0x403cb700,len:0x2f48 -entry 0x403c8924 -I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader -I (24) boot: compile time Jun 29 2026 11:03:24 -I (24) boot: Multicore bootloader -I (25) boot: chip revision: v0.2 -I (27) boot: efuse block revision: v1.4 -I (31) boot.esp32s3: Boot SPI Speed : 80MHz -I (35) boot.esp32s3: SPI Mode : DIO -I (39) boot.esp32s3: SPI Flash Size : 8MB -I (42) boot: Enabling RNG early entropy source... -I (47) boot: Partition Table: -I (50) boot: ## Label Usage Type ST Offset Length -I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 -I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 -I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 -I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 -I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 -I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 -I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 -I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 -I (108) boot: End of partition table -I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map -I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load -I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load -I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map -I (469) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load -I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load -I (494) boot: Loaded app from partition at offset 0x40000 -I (495) boot: Disabling RNG early entropy source... -I (505) octal_psram: vendor id : 0x0d (AP) -I (505) octal_psram: dev id : 0x02 (generation 3) -I (505) octal_psram: density : 0x03 (64 Mbit) -I (507) octal_psram: good-die : 0x01 (Pass) -I (511) octal_psram: Latency : 0x01 (Fixed) -I (516) octal_psram: VCC : 0x01 (3V) -I (520) octal_psram: SRF : 0x01 (Fast Refresh) -I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) -I (530) octal_psram: BurstLen : 0x01 (32 Byte) -I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) -I (539) octal_psram: DriveStrength: 0x00 (1/1) -I (544) esp_psram: Found 8MB PSRAM device -I (547) esp_psram: Speed: 40MHz -I (550) cpu_start: Multicore app -I (1284) esp_psram: SPI SRAM memory test OK -I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins -I (1293) cpu_start: Pro cpu start user code -I (1293) cpu_start: cpu freq: 160000000 Hz -I (1295) app_init: Application information: -I (1299) app_init: Project name: kota-sensorboard -I (1304) app_init: App version: 1.0 -I (1307) app_init: Compile time: Jun 29 2026 11:03:32 -I (1312) app_init: ELF file SHA256: 45fa9d5b9... -I (1317) app_init: ESP-IDF: v5.5.3-dirty -I (1321) efuse_init: Min chip rev: v0.0 -I (1325) efuse_init: Max chip rev: v0.99 -I (1329) efuse_init: Chip rev: v0.2 -I (1333) heap_init: Initializing. RAM available for dynamic allocation: -I (1340) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM -I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM -I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM -I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM -I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator -I (1368) spi_flash: detected chip: generic -I (1371) spi_flash: flash io: dio -I (1377) sleep_gpio: Configure to isolate all GPIO pins in sleep state -I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration -I (1396) main_task: Started on CPU0 -I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations -I (1407) main_task: Calling app_main() -E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 -I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device -I (1439) MAIN: Matter node created successfully -W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. -W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. -W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. -W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. -W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. -W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. -I (1595) pp: pp rom version: e7ae62f -I (1597) net80211: net80211 rom version: e7ae62f -I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 -I (1619) wifi:wifi firmware version: 4df78f2 -I (1620) wifi:wifi certification version: v7.0 -I (1620) wifi:config NVS flash: enabled -I (1621) wifi:config nano formatting: disabled -I (1625) wifi:Init data frame dynamic rx buffer num: 32 -I (1630) wifi:Init static rx mgmt buffer num: 5 -I (1634) wifi:Init management short buffer num: 32 -I (1638) wifi:Init dynamic tx buffer num: 32 -I (1642) wifi:Init static tx FG buffer num: 2 -I (1647) wifi:Init static rx buffer size: 1600 -I (1651) wifi:Init static rx buffer num: 10 -I (1655) wifi:Init dynamic rx buffer num: 32 -I (1659) wifi_init: rx ba win: 6 -I (1661) wifi_init: accept mbox: 6 -I (1665) wifi_init: tcpip mbox: 32 -I (1668) wifi_init: udp mbox: 6 -I (1671) wifi_init: tcp mbox: 6 -I (1673) wifi_init: tcp tx win: 5760 -I (1677) wifi_init: tcp rx win: 5760 -I (1680) wifi_init: tcp mss: 1440 -I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15843 (0x3DE3) -I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) -I (1697) BLE_INIT: BT controller compile version [1bb2f50] -I (1702) BLE_INIT: Using main XTAL as clock source -I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 -I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa -I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 -I (1767) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0) -I (1784) NimBLE: GAP procedure initiated: stop advertising. - -I (1786) CHIP[DL]: BLE host-controller synced -I (2286) chip[DL]: Starting ESP WiFi layer -I (2288) wifi:mode : sta (a4:cb:8f:c9:46:f8) -I (2289) wifi:enable tsf -W (2289) wifi:Haven't to connect to a suitable AP now! -I (2290) chip[DL]: -I (2290) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43 -Done driving station state, nothing else to do... -I (2299) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 -W (2303) wifi:Haven't to connect to a suitable AP now! -I (2314) chip[DL]: Done driving station state, nothing else to do... -I (2320) chip[SVR]: Initializing subscription resumption storage... -I (2327) chip[SVR]: Server initializing... -I (2330) chip[TS]: Last Known Good Time: 2026-08-04T05:56:06 -I (2336) chip[DMG]: AccessControl: initializing -I (2339) chip[DMG]: Examples::AccessControlDelegate::Init -I (2344) chip[DMG]: AccessControl: setting -I (2348) chip[DMG]: DefaultAclStorage: initializing -I (2353) chip[DMG]: DefaultAclStorage: 0 entries loaded -I (2358) chip[SVR]: WARNING: mTestEventTriggerDelegate is null -I (2377) chip[ZCL]: Using ZAP configuration... -I (2378) esp_matter_cluster: Cluster plugin init common callback -I (2378) chip[DMG]: AccessControlCluster: initializing -I (2381) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported -I (2393) chip[DIS]: Updating services using commissioning mode 1 -I (2395) chip[DIS]: CHIP minimal mDNS started advertising. -I (2400) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 -I (2410) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: 73BA585012FF9068. -I (2420) chip[DIS]: mDNS service published: _matterc._udp -I (2425) chip[IN]: CASE Server enabling CASE session setups -I (2431) chip[SVR]: Joining Multicast groups -I (2434) chip[SVR]: Server Listening... -I (2439) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** -I (2449) data_model: Dynamic endpoint 0 added -E (2452) data_model: Cluster cannot be NULL. -I (2457) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** -I (2467) data_model: Dynamic endpoint 1 added -E (2471) data_model: Cluster cannot be NULL. -I (2476) chip[DL]: WIFI_EVENT_STA_START -I (2477) MAIN: ESP-Matter started successfully -W (2479) wifi:Haven't to connect to a suitable AP now! -I (2488) chip[DL]: Done driving station state, nothing else to do... -I (2495) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) -I (2505) NimBLE: GAP procedure initiated: advertise; -I (2506) NimBLE: disc_mode=2 - -I (2506) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 -I (2511) NimBLE: - -I (2512) chip[DL]: CHIPoBLE advertising started -I (2513) MAIN: Commissioning window opened -> -I (4060) I2C: Creating I2C mutex -I (14061) SPI: Creating SPI mutex -I (14103) INFLUX_BUFFER: SD card mounted successfully -I (14104) INFLUX_BUFFER: /sdcard exists, SD card initialized successfully -I (14107) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -W (14121) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota -I (14145) BME688: Delaying for 10000 microseconds -I (14763) Flicker: Flicker sensor initialized on pin 14 -I (14765) temperature_sensor: Range [20°C ~ 100°C], error < 2°C -I (14767) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data -I (14767) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data -W (14772) wifi:Haven't to connect to a suitable AP now! -I (14768) SEN66: Creating sensor mutex -W (14782) AS7341: Reading spectral data from AS7341 -I (14775) INFLUX_BUFFER: Flushing chunk starting at line 10: -I (14796) INFLUX: Writing to InfluxDB -I (14784) SEN66: Waking up sensor by setting power GPIO to enabled level... -I (14783) DeviceStatus: Active tasks: 26 -I (14813) DeviceStatus: Task list: -gather_sensor_t X 1 1344 22 -IDLE1 R 0 812 6 -IDLE0 R 0 692 5 -gather_sensor_t B 1 2552 21 -ds1307_update B 1 800 17 -gather_task B 1 1964 19 -gather_task B 1 14032 20 -alive_diode_tas B 5 352 8 -reset_task B 0 3432 15 -tiT B 18 2420 10 -gather_sensor_t B 1 1720 24 -ota_task B 1 7516 18 -CHIP B 1 3700 12 -Tmr Svc B 1 1364 7 -gather_sensor_t B 1 1092 25 -sys_evt B 20 2684 9 -ipc0 S 24 372 1 -ipc1 S 24 524 2 -console B 5 648 16 -esp_timer S 22 3332 3 -gather_sensor_t B 1 2332 23 -gather_sensor_t B 1 2544 26 -wifi B 23 4324 11 -btController B 23 1960 13 -main B 1 876 4 -nimble_host B 21 1480 14 - -W (14878) DeviceStatus: RTC is uninitialized, using current time as timestamp -W (15105) AS7341: Reading basic counts data from AS7341 -W (15121) AS7341: Reading flicker detection state from AS7341 -W (15339) as7341: FD Status Register: 0x20 (0b00100000) -I (15345) AS7341: Updating Matter attributes with AS7341 data: 16707 -I (15345) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms -I (15359) Flicker: Starting to collect samples in gather task -I (15355) BME688: Delaying for 199517 microseconds -I (15347) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** -I (15374) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 16707 ********** -E (15386) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -E (15398) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 -W (15400) AS7341: Formatting AS7341 data in line protocol -AS7341 line protocol: -as7341_adc,device=A4CB8FC946F8 f1=1,f2=2,f3=2,f4=3,f5=3,f6=4,f7=7,f8=9,clear=21,nir=5 946684801000000000 -as7341_basic_counts,device=A4CB8FC946F8 f1=2.23,f2=4.46,f3=4.46,f4=6.69,f5=6.69,f6=8.92,f7=15.62,f8=20.08,clear=46.85,nir=11.15 946684801000000000 -as7341_flicker,device=A4CB8FC946F8 state="unknown" 946684801000000000 -W (15890) DeviceStatus: RTC is uninitialized, using current time as timestamp -I (16383) Flicker: Finished collecting samples, notifying main task -I (16385) Flicker: Collected 1024 samples for dominant frequency analysis -I (16392) SENSOR_GATHERER: Sensor should now be asleep -I (16404) SENSOR_GATHERER: Sensor should now be asleep -I (16444) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** -I (16446) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** -E (16459) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -E (16471) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 -W (16473) BME688: RTC is uninitialized, using current time as timestamp -I (16891) SENSOR_GATHERER: Sensor should now be asleep -W (17484) BME688: RTC is uninitialized, using current time as timestamp +--- connected to COM5 --- +I (16926) SENSOR_GATHERER: Sensor should now be asleep +--- reset issued by tool --- +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x15 (USB_UART_CHIP_RESET),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x4037f34a +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (25) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (28) boot: efuse block revision: v1.4 +I (32) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (43) boot: Enabling RNG early entropy source... +I (48) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (63) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (76) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (89) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (102) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (109) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (191) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (203) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (469) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (485) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (495) boot: Loaded app from partition at offset 0x40000 +I (495) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (506) octal_psram: density : 0x03 (64 Mbit) +I (508) octal_psram: good-die : 0x01 (Pass) +I (512) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (535) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (540) octal_psram: DriveStrength: 0x00 (1/1) +I (544) esp_psram: Found 8MB PSRAM device +I (548) esp_psram: Speed: 40MHz +I (551) cpu_start: Multicore app +I (1285) esp_psram: SPI SRAM memory test OK +I (1293) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1294) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1304) app_init: App version: 1.0 +I (1308) app_init: Compile time: Jun 29 2026 11:03:32 +I (1313) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1322) efuse_init: Min chip rev: v0.0 +I (1326) efuse_init: Max chip rev: v0.99 +I (1330) efuse_init: Chip rev: v0.2 +I (1334) heap_init: Initializing. RAM available for dynamic allocation: +I (1340) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1356) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1369) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1377) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1397) main_task: Started on CPU0 +I (1400) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1408) main_task: Calling app_main() +E (1411) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1436) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1441) MAIN: Matter node created successfully +W (1442) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1445) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1454) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1463) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1472) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1480) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1489) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1498) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1507) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1515) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1524) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1533) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1542) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1551) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1559) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1568) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1577) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1586) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1596) pp: pp rom version: e7ae62f +I (1598) net80211: net80211 rom version: e7ae62f +I (1603) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 +I (1621) wifi:wifi firmware version: 4df78f2 +I (1621) wifi:wifi certification version: v7.0 +I (1621) wifi:config NVS flash: enabled +I (1622) wifi:config nano formatting: disabled +I (1626) wifi:Init data frame dynamic rx buffer num: 32 +I (1631) wifi:Init static rx mgmt buffer num: 5 +I (1635) wifi:Init management short buffer num: 32 +I (1639) wifi:Init dynamic tx buffer num: 32 +I (1644) wifi:Init static tx FG buffer num: 2 +I (1648) wifi:Init static rx buffer size: 1600 +I (1652) wifi:Init static rx buffer num: 10 +I (1656) wifi:Init dynamic rx buffer num: 32 +I (1660) wifi_init: rx ba win: 6 +I (1663) wifi_init: accept mbox: 6 +I (1666) wifi_init: tcpip mbox: 32 +I (1669) wifi_init: udp mbox: 6 +I (1672) wifi_init: tcp mbox: 6 +I (1675) wifi_init: tcp tx win: 5760 +I (1678) wifi_init: tcp rx win: 5760 +I (1681) wifi_init: tcp mss: 1440 +I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15834 (0x3DDA) +I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1698) BLE_INIT: BT controller compile version [1bb2f50] +I (1703) BLE_INIT: Using main XTAL as clock source +I (1707) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1716) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1721) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +E BOD: Brownout detector was triggered + + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x40376756 +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (24) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (27) boot: efuse block revision: v1.4 +I (31) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (42) boot: Enabling RNG early entropy source... +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (108) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (494) boot: Loaded app from partition at offset 0x40000 +I (495) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (505) octal_psram: density : 0x03 (64 Mbit) +I (507) octal_psram: good-die : 0x01 (Pass) +I (511) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (539) octal_psram: DriveStrength: 0x00 (1/1) +I (544) esp_psram: Found 8MB PSRAM device +I (547) esp_psram: Speed: 40MHz +I (550) cpu_start: Multicore app +I (1284) esp_psram: SPI SRAM memory test OK +I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1293) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1304) app_init: App version: 1.0 +I (1307) app_init: Compile time: Jun 29 2026 11:03:32 +I (1312) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1321) efuse_init: Min chip rev: v0.0 +I (1325) efuse_init: Max chip rev: v0.99 +I (1329) efuse_init: Chip rev: v0.2 +I (1333) heap_init: Initializing. RAM available for dynamic allocation: +I (1340) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1368) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1377) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1396) main_task: Started on CPU0 +I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1407) main_task: Calling app_main() +E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1439) MAIN: Matter node created successfully +W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1595) pp: pp rom version: e7ae62f +I (1597) net80211: net80211 rom version: e7ae62f +I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 +I (1619) wifi:wifi firmware version: 4df78f2 +I (1620) wifi:wifi certification version: v7.0 +I (1620) wifi:config NVS flash: enabled +I (1621) wifi:config nano formatting: disabled +I (1625) wifi:Init data frame dynamic rx buffer num: 32 +I (1630) wifi:Init static rx mgmt buffer num: 5 +I (1634) wifi:Init management short buffer num: 32 +I (1638) wifi:Init dynamic tx buffer num: 32 +I (1642) wifi:Init static tx FG buffer num: 2 +I (1647) wifi:Init static rx buffer size: 1600 +I (1651) wifi:Init static rx buffer num: 10 +I (1655) wifi:Init dynamic rx buffer num: 32 +I (1659) wifi_init: rx ba win: 6 +I (1661) wifi_init: accept mbox: 6 +I (1665) wifi_init: tcpip mbox: 32 +I (1668) wifi_init: udp mbox: 6 +I (1671) wifi_init: tcp mbox: 6 +I (1673) wifi_init: tcp tx win: 5760 +I (1677) wifi_init: tcp rx win: 5760 +I (1680) wifi_init: tcp mss: 1440 +I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15835 (0x3DDB) +I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1697) BLE_INIT: BT controller compile version [1bb2f50] +I (1702) BLE_INIT: Using main XTAL as clock source +I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +E BOD: Brownout detector was triggered + + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x40376756 +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (24) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (27) boot: efuse block revision: v1.4 +I (31) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (42) boot: Enabling RNG early entropy source... +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (108) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (494) boot: Loaded app from partition at offset 0x40000 +I (494) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (505) octal_psram: density : 0x03 (64 Mbit) +I (507) octal_psram: good-die : 0x01 (Pass) +I (511) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (539) octal_psram: DriveStrength: 0x00 (1/1) +I (543) esp_psram: Found 8MB PSRAM device +I (547) esp_psram: Speed: 40MHz +I (550) cpu_start: Multicore app +I (1284) esp_psram: SPI SRAM memory test OK +I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1293) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1303) app_init: App version: 1.0 +I (1307) app_init: Compile time: Jun 29 2026 11:03:32 +I (1312) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1321) efuse_init: Min chip rev: v0.0 +I (1325) efuse_init: Max chip rev: v0.99 +I (1329) efuse_init: Chip rev: v0.2 +I (1333) heap_init: Initializing. RAM available for dynamic allocation: +I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1368) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1396) main_task: Started on CPU0 +I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1407) main_task: Calling app_main() +E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1439) MAIN: Matter node created successfully +W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1550) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1595) pp: pp rom version: e7ae62f +I (1597) net80211: net80211 rom version: e7ae62f +I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 +I (1619) wifi:wifi firmware version: 4df78f2 +I (1620) wifi:wifi certification version: v7.0 +I (1620) wifi:config NVS flash: enabled +I (1621) wifi:config nano formatting: disabled +I (1625) wifi:Init data frame dynamic rx buffer num: 32 +I (1630) wifi:Init static rx mgmt buffer num: 5 +I (1634) wifi:Init management short buffer num: 32 +I (1638) wifi:Init dynamic tx buffer num: 32 +I (1642) wifi:Init static tx FG buffer num: 2 +I (1647) wifi:Init static rx buffer size: 1600 +I (1651) wifi:Init static rx buffer num: 10 +I (1655) wifi:Init dynamic rx buffer num: 32 +I (1659) wifi_init: rx ba win: 6 +I (1661) wifi_init: accept mbox: 6 +I (1665) wifi_init: tcpip mbox: 32 +I (1668) wifi_init: udp mbox: 6 +I (1671) wifi_init: tcp mbox: 6 +I (1673) wifi_init: tcp tx win: 5760 +I (1677) wifi_init: tcp rx win: 5760 +I (1680) wifi_init: tcp mss: 1440 +I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15836 (0x3DDC) +I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1697) BLE_INIT: BT controller compile version [1bb2f50] +I (1702) BLE_INIT: Using main XTAL as clock source +I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +E BOD: Brownout detector was triggered + + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x40376756 +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (24) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (27) boot: efuse block revision: v1.4 +I (31) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (42) boot: Enabling RNG early entropy source... +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (108) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (494) boot: Loaded app from partition at offset 0x40000 +I (494) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (505) octal_psram: density : 0x03 (64 Mbit) +I (507) octal_psram: good-die : 0x01 (Pass) +I (511) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (539) octal_psram: DriveStrength: 0x00 (1/1) +I (543) esp_psram: Found 8MB PSRAM device +I (547) esp_psram: Speed: 40MHz +I (550) cpu_start: Multicore app +I (1284) esp_psram: SPI SRAM memory test OK +I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1293) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1303) app_init: App version: 1.0 +I (1307) app_init: Compile time: Jun 29 2026 11:03:32 +I (1312) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1321) efuse_init: Min chip rev: v0.0 +I (1325) efuse_init: Max chip rev: v0.99 +I (1329) efuse_init: Chip rev: v0.2 +I (1333) heap_init: Initializing. RAM available for dynamic allocation: +I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1368) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1396) main_task: Started on CPU0 +I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1407) main_task: Calling app_main() +E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1439) MAIN: Matter node created successfully +W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1595) pp: pp rom version: e7ae62f +I (1597) net80211: net80211 rom version: e7ae62f +I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 +I (1619) wifi:wifi firmware version: 4df78f2 +I (1620) wifi:wifi certification version: v7.0 +I (1620) wifi:config NVS flash: enabled +I (1621) wifi:config nano formatting: disabled +I (1625) wifi:Init data frame dynamic rx buffer num: 32 +I (1630) wifi:Init static rx mgmt buffer num: 5 +I (1634) wifi:Init management short buffer num: 32 +I (1638) wifi:Init dynamic tx buffer num: 32 +I (1642) wifi:Init static tx FG buffer num: 2 +I (1647) wifi:Init static rx buffer size: 1600 +I (1651) wifi:Init static rx buffer num: 10 +I (1655) wifi:Init dynamic rx buffer num: 32 +I (1659) wifi_init: rx ba win: 6 +I (1662) wifi_init: accept mbox: 6 +I (1665) wifi_init: tcpip mbox: 32 +I (1668) wifi_init: udp mbox: 6 +I (1671) wifi_init: tcp mbox: 6 +I (1673) wifi_init: tcp tx win: 5760 +I (1677) wifi_init: tcp rx win: 5760 +I (1680) wifi_init: tcp mss: 1440 +I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15837 (0x3DDD) +I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1697) BLE_INIT: BT controller compile version [1bb2f50] +I (1702) BLE_INIT: Using main XTAL as clock source +I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +E BOD: Brownout detector was triggered + + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x40376756 +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (24) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (27) boot: efuse block revision: v1.4 +I (31) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (42) boot: Enabling RNG early entropy source... +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (108) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (494) boot: Loaded app from partition at offset 0x40000 +I (494) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (505) octal_psram: density : 0x03 (64 Mbit) +I (507) octal_psram: good-die : 0x01 (Pass) +I (511) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (539) octal_psram: DriveStrength: 0x00 (1/1) +I (543) esp_psram: Found 8MB PSRAM device +I (547) esp_psram: Speed: 40MHz +I (550) cpu_start: Multicore app +I (1284) esp_psram: SPI SRAM memory test OK +I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1293) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1303) app_init: App version: 1.0 +I (1307) app_init: Compile time: Jun 29 2026 11:03:32 +I (1312) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1321) efuse_init: Min chip rev: v0.0 +I (1325) efuse_init: Max chip rev: v0.99 +I (1329) efuse_init: Chip rev: v0.2 +I (1333) heap_init: Initializing. RAM available for dynamic allocation: +I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1368) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1396) main_task: Started on CPU0 +I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1407) main_task: Calling app_main() +E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1439) MAIN: Matter node created successfully +W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1595) pp: pp rom version: e7ae62f +I (1597) net80211: net80211 rom version: e7ae62f +I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 +I (1619) wifi:wifi firmware version: 4df78f2 +I (1620) wifi:wifi certification version: v7.0 +I (1620) wifi:config NVS flash: enabled +I (1621) wifi:config nano formatting: disabled +I (1625) wifi:Init data frame dynamic rx buffer num: 32 +I (1630) wifi:Init static rx mgmt buffer num: 5 +I (1634) wifi:Init management short buffer num: 32 +I (1638) wifi:Init dynamic tx buffer num: 32 +I (1642) wifi:Init static tx FG buffer num: 2 +I (1647) wifi:Init static rx buffer size: 1600 +I (1651) wifi:Init static rx buffer num: 10 +I (1655) wifi:Init dynamic rx buffer num: 32 +I (1659) wifi_init: rx ba win: 6 +I (1661) wifi_init: accept mbox: 6 +I (1665) wifi_init: tcpip mbox: 32 +I (1668) wifi_init: udp mbox: 6 +I (1671) wifi_init: tcp mbox: 6 +I (1673) wifi_init: tcp tx win: 5760 +I (1677) wifi_init: tcp rx win: 5760 +I (1680) wifi_init: tcp mss: 1440 +I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15838 (0x3DDE) +I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1697) BLE_INIT: BT controller compile version [1bb2f50] +I (1702) BLE_INIT: Using main XTAL as clock source +I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +I (1768) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0) +I (1785) NimBLE: GAP procedure initiated: stop advertising. + +I (1787) CHIP[DL]: BLE host-controller synced +I (2287) chip[DL]: Starting ESP WiFi layer +I (2289) wifi:mode : sta (a4:cb:8f:c9:46:f8) +I (2290) wifi:enable tsf +W (2290) wifi:Haven't to connect to a suitable AP now! +I (2291) chip[DL]: +I (2291) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43 +Done driving station state, nothing else to do... +I (2300) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 +W (2304) wifi:Haven't to connect to a suitable AP now! +I (2315) chip[DL]: Done driving station state, nothing else to do... +I (2321) chip[SVR]: Initializing subscription resumption storage... +I (2328) chip[SVR]: Server initializing... +I (2331) chip[TS]: Last Known Good Time: 2026-08-04T05:56:06 +I (2337) chip[DMG]: AccessControl: initializing +I (2340) chip[DMG]: Examples::AccessControlDelegate::Init +I (2345) chip[DMG]: AccessControl: setting +I (2349) chip[DMG]: DefaultAclStorage: initializing +I (2354) chip[DMG]: DefaultAclStorage: 0 entries loaded +I (2359) chip[SVR]: WARNING: mTestEventTriggerDelegate is null +I (2378) chip[ZCL]: Using ZAP configuration... +I (2378) esp_matter_cluster: Cluster plugin init common callback +I (2379) chip[DMG]: AccessControlCluster: initializing +I (2382) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported +I (2392) chip[DIS]: Updating services using commissioning mode 1 +I (2396) chip[DIS]: CHIP minimal mDNS started advertising. +I (2401) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 +I (2411) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: 37BE0E5B446CAB54. +I (2421) chip[DIS]: mDNS service published: _matterc._udp +I (2426) chip[IN]: CASE Server enabling CASE session setups +I (2432) chip[SVR]: Joining Multicast groups +I (2435) chip[SVR]: Server Listening... +I (2440) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** +I (2450) data_model: Dynamic endpoint 0 added +E (2453) data_model: Cluster cannot be NULL. +I (2458) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** +I (2468) data_model: Dynamic endpoint 1 added +E (2472) data_model: Cluster cannot be NULL. +I (2477) chip[DL]: WIFI_EVENT_STA_START +I (2478) MAIN: ESP-Matter started successfully +W (2480) wifi:Haven't to connect to a suitable AP now! +I (2489) chip[DL]: Done driving station state, nothing else to do... +I (2496) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) +I (2506) NimBLE: GAP procedure initiated: advertise; +I (2506) NimBLE: disc_mode=2 + +I (2507) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 +I (2511) NimBLE: + +I (2513) chip[DL]: CHIPoBLE advertising started +I (2514) MAIN: Commissioning window opened +> +I (4060) I2C: Creating I2C mutex +I (14061) SPI: Creating SPI mutex +I (14103) INFLUX_BUFFER: SD card mounted successfully +I (14104) INFLUX_BUFFER: /sdcard exists, SD card initialized successfully +I (14107) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +W (14121) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +I (14145) BME688: Delaying for 10000 microseconds +I (14763) Flicker: Flicker sensor initialized on pin 14 +I (14765) temperature_sensor: Range [20°C ~ 100°C], error < 2°C +I (14767) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data +I (14767) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data +W (14772) wifi:Haven't to connect to a suitable AP now! +I (14768) SEN66: Creating sensor mutex +W (14782) AS7341: Reading spectral data from AS7341 +I (14775) INFLUX_BUFFER: Flushing chunk starting at line 10: +I (14796) INFLUX: Writing to InfluxDB +I (14784) SEN66: Waking up sensor by setting power GPIO to enabled level... +I (14783) DeviceStatus: Active tasks: 26 +I (14813) DeviceStatus: Task list: +gather_sensor_t X 1 1216 22 +IDLE1 R 0 812 6 +IDLE0 R 0 692 5 +gather_sensor_t B 1 2552 21 +ds1307_update B 1 800 17 +gather_task B 1 1756 19 +gather_task B 1 14032 20 +alive_diode_tas B 5 352 8 +reset_task B 0 3432 15 +tiT B 18 2420 10 +gather_sensor_t B 1 1688 24 +ota_task B 1 7516 18 +CHIP B 1 3700 12 +Tmr Svc B 1 1364 7 +gather_sensor_t B 1 1092 25 +sys_evt B 20 2684 9 +ipc0 S 24 372 1 +ipc1 S 24 524 2 +console B 5 648 16 +esp_timer S 22 3332 3 +gather_sensor_t B 1 2284 23 +gather_sensor_t B 1 2544 26 +wifi B 23 4276 11 +btController B 23 1960 13 +main B 1 876 4 +nimble_host B 21 1480 14 + +W (14878) DeviceStatus: RTC is uninitialized, using current time as timestamp +W (15105) AS7341: Reading basic counts data from AS7341 +W (15121) AS7341: Reading flicker detection state from AS7341 +W (15339) as7341: FD Status Register: 0x20 (0b00100000) +I (15345) AS7341: Updating Matter attributes with AS7341 data: 16909 +I (15345) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms +I (15359) Flicker: Starting to collect samples in gather task +I (15355) BME688: Delaying for 199517 microseconds +I (15347) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** +I (15374) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 16909 ********** +E (15386) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +E (15398) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +W (15400) AS7341: Formatting AS7341 data in line protocol +AS7341 line protocol: +as7341_adc,device=A4CB8FC946F8 f1=1,f2=2,f3=2,f4=3,f5=3,f6=4,f7=7,f8=9,clear=22,nir=5 946684801000000000 +as7341_basic_counts,device=A4CB8FC946F8 f1=2.23,f2=4.46,f3=4.46,f4=6.69,f5=6.69,f6=8.92,f7=15.62,f8=20.08,clear=49.08,nir=11.15 946684801000000000 +as7341_flicker,device=A4CB8FC946F8 state="unknown" 946684801000000000 +W (15890) DeviceStatus: RTC is uninitialized, using current time as timestamp +I (16383) Flicker: Finished collecting samples, notifying main task +I (16385) Flicker: Collected 1024 samples for dominant frequency analysis +I (16392) SENSOR_GATHERER: Sensor should now be asleep +I (16404) SENSOR_GATHERER: Sensor should now be asleep +I (16444) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** +I (16446) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** +E (16459) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +E (16471) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +W (16473) BME688: RTC is uninitialized, using current time as timestamp +I (16891) SENSOR_GATHERER: Sensor should now be asleep +W (17484) BME688: RTC is uninitialized, using current time as timestamp +W (18485) BME688: RTC is uninitialized, using current time as timestamp +W (19486) BME688: RTC is uninitialized, using current time as timestamp +I (19809) SEN66: Sent start measurement command, waiting for sensor to be ready... +I (19905) MICROPHONE: Read 300000 bytes in 4936 ms +I (19906) MICROPHONE: Read 300000 bytes of audio data +I (20098) MICROPHONE: Sum SQ: 0.049147, RMS pressure: 0.000701, Normalized: 0.000701, dB peak: -63.09 +W (20100) SEN66: RTC is uninitialized, using current time as timestamp +W (20487) BME688: RTC is uninitialized, using current time as timestamp +W (21112) SEN66: RTC is uninitialized, using current time as timestamp +W (21488) BME688: RTC is uninitialized, using current time as timestamp +E (21798) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (21800) esp-tls: Failed to open new connection +E (21801) transport_base: Failed to open a new connection +E (21812) HTTP_CLIENT: Connection failed, sock < 0 +I (21813) INFLUX: HTTP request performed, status code: 0 +E (21824) INFLUX: Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT +E (21826) INFLUX_BUFFER: Failed to write chunk to Influx: ESP_FAIL +W (22113) SEN66: RTC is uninitialized, using current time as timestamp +W (22489) BME688: RTC is uninitialized, using current time as timestamp +I (22769) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (23114) SEN66: RTC is uninitialized, using current time as timestamp +I (23270) SENSOR_GATHERER: 1/2 tasks still running, waiting... +E BOD: Brownout detector was triggered + + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x40376756 +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (24) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (27) boot: efuse block revision: v1.4 +I (31) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (42) boot: Enabling RNG early entropy source... +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (108) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (494) boot: Loaded app from partition at offset 0x40000 +I (494) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (505) octal_psram: density : 0x03 (64 Mbit) +I (507) octal_psram: good-die : 0x01 (Pass) +I (511) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (539) octal_psram: DriveStrength: 0x00 (1/1) +I (543) esp_psram: Found 8MB PSRAM device +I (547) esp_psram: Speed: 40MHz +I (550) cpu_start: Multicore app +I (1284) esp_psram: SPI SRAM memory test OK +I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1293) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1303) app_init: App version: 1.0 +I (1307) app_init: Compile time: Jun 29 2026 11:03:32 +I (1312) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1321) efuse_init: Min chip rev: v0.0 +I (1325) efuse_init: Max chip rev: v0.99 +I (1329) efuse_init: Chip rev: v0.2 +I (1333) heap_init: Initializing. RAM available for dynamic allocation: +I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1368) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1396) main_task: Started on CPU0 +I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1407) main_task: Calling app_main() +E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1439) MAIN: Matter node created successfully +W (1440) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1470) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1505) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1584) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1595) pp: pp rom version: e7ae62f +I (1596) net80211: net80211 rom version: e7ae62f +I (1602) wifi:wifi driver task: 3fcc524c, prio:23, stack:6656, core=0 +I (1619) wifi:wifi firmware version: 4df78f2 +I (1620) wifi:wifi certification version: v7.0 +I (1620) wifi:config NVS flash: enabled +I (1620) wifi:config nano formatting: disabled +I (1625) wifi:Init data frame dynamic rx buffer num: 32 +I (1630) wifi:Init static rx mgmt buffer num: 5 +I (1634) wifi:Init management short buffer num: 32 +I (1638) wifi:Init dynamic tx buffer num: 32 +I (1642) wifi:Init static tx FG buffer num: 2 +I (1646) wifi:Init static rx buffer size: 1600 +I (1651) wifi:Init static rx buffer num: 10 +I (1654) wifi:Init dynamic rx buffer num: 32 +I (1659) wifi_init: rx ba win: 6 +I (1661) wifi_init: accept mbox: 6 +I (1664) wifi_init: tcpip mbox: 32 +I (1667) wifi_init: udp mbox: 6 +I (1670) wifi_init: tcp mbox: 6 +I (1673) wifi_init: tcp tx win: 5760 +I (1677) wifi_init: tcp rx win: 5760 +I (1680) wifi_init: tcp mss: 1440 +I (1691) chip[DL]: NVS set: chip-counters/reboot-count = 15839 (0x3DDF) +I (1694) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1697) BLE_INIT: BT controller compile version [1bb2f50] +I (1701) BLE_INIT: Using main XTAL as clock source +I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +E BOD: Brownout detector was triggered + + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x40376756 +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (24) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (27) boot: efuse block revision: v1.4 +I (31) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (42) boot: Enabling RNG early entropy source... +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (108) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (469) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (494) boot: Loaded app from partition at offset 0x40000 +I (495) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (505) octal_psram: density : 0x03 (64 Mbit) +I (507) octal_psram: good-die : 0x01 (Pass) +I (511) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (539) octal_psram: DriveStrength: 0x00 (1/1) +I (544) esp_psram: Found 8MB PSRAM device +I (547) esp_psram: Speed: 40MHz +I (550) cpu_start: Multicore app +I (1284) esp_psram: SPI SRAM memory test OK +I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1293) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1304) app_init: App version: 1.0 +I (1307) app_init: Compile time: Jun 29 2026 11:03:32 +I (1312) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1321) efuse_init: Min chip rev: v0.0 +I (1325) efuse_init: Max chip rev: v0.99 +I (1329) efuse_init: Chip rev: v0.2 +I (1333) heap_init: Initializing. RAM available for dynamic allocation: +I (1340) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1368) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1377) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1396) main_task: Started on CPU0 +I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1407) main_task: Calling app_main() +E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1439) MAIN: Matter node created successfully +W (1440) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1461) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1470) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1505) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1540) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1575) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1584) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1595) pp: pp rom version: e7ae62f +I (1596) net80211: net80211 rom version: e7ae62f +I (1602) wifi:wifi driver task: 3fcc524c, prio:23, stack:6656, core=0 +I (1619) wifi:wifi firmware version: 4df78f2 +I (1620) wifi:wifi certification version: v7.0 +I (1620) wifi:config NVS flash: enabled +I (1620) wifi:config nano formatting: disabled +I (1625) wifi:Init data frame dynamic rx buffer num: 32 +I (1630) wifi:Init static rx mgmt buffer num: 5 +I (1634) wifi:Init management short buffer num: 32 +I (1638) wifi:Init dynamic tx buffer num: 32 +I (1642) wifi:Init static tx FG buffer num: 2 +I (1646) wifi:Init static rx buffer size: 1600 +I (1650) wifi:Init static rx buffer num: 10 +I (1654) wifi:Init dynamic rx buffer num: 32 +I (1659) wifi_init: rx ba win: 6 +I (1661) wifi_init: accept mbox: 6 +I (1664) wifi_init: tcpip mbox: 32 +I (1667) wifi_init: udp mbox: 6 +I (1670) wifi_init: tcp mbox: 6 +I (1673) wifi_init: tcp tx win: 5760 +I (1676) wifi_init: tcp rx win: 5760 +I (1680) wifi_init: tcp mss: 1440 +I (1691) chip[DL]: NVS set: chip-counters/reboot-count = 15840 (0x3DE0) +I (1694) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1696) BLE_INIT: BT controller compile version [1bb2f50] +I (1701) BLE_INIT: Using main XTAL as clock source +I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1714) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +I (1767) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0) +I (1804) NimBLE: GAP procedure initiated: stop advertising. + +I (1806) CHIP[DL]: BLE host-controller synced +I (2306) chip[DL]: Starting ESP WiFi layer +I (2308) wifi:mode : sta (a4:cb:8f:c9:46:f8) +I (2309) wifi:enable tsf +W (2309) wifi:Haven't to connect to a suitable AP now! +I (2310) chip[DL]: +I (2310) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43 +Done driving station state, nothing else to do... +I (2319) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 +W (2323) wifi:Haven't to connect to a suitable AP now! +I (2334) chip[DL]: Done driving station state, nothing else to do... +I (2340) chip[SVR]: Initializing subscription resumption storage... +I (2347) chip[SVR]: Server initializing... +I (2350) chip[TS]: Last Known Good Time: 2026-08-04T05:56:06 +I (2356) chip[DMG]: AccessControl: initializing +I (2359) chip[DMG]: Examples::AccessControlDelegate::Init +I (2364) chip[DMG]: AccessControl: setting +I (2368) chip[DMG]: DefaultAclStorage: initializing +I (2373) chip[DMG]: DefaultAclStorage: 0 entries loaded +I (2378) chip[SVR]: WARNING: mTestEventTriggerDelegate is null +I (2397) chip[ZCL]: Using ZAP configuration... +I (2397) esp_matter_cluster: Cluster plugin init common callback +I (2398) chip[DMG]: AccessControlCluster: initializing +I (2401) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported +I (2411) chip[DIS]: Updating services using commissioning mode 1 +I (2415) chip[DIS]: CHIP minimal mDNS started advertising. +I (2420) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 +I (2430) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: 67895F766C4DDEDA. +I (2440) chip[DIS]: mDNS service published: _matterc._udp +I (2445) chip[IN]: CASE Server enabling CASE session setups +I (2451) chip[SVR]: Joining Multicast groups +I (2454) chip[SVR]: Server Listening... +I (2459) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** +I (2469) data_model: Dynamic endpoint 0 added +E (2472) data_model: Cluster cannot be NULL. +I (2477) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** +I (2487) data_model: Dynamic endpoint 1 added +E (2491) data_model: Cluster cannot be NULL. +I (2496) chip[DL]: WIFI_EVENT_STA_START +I (2497) MAIN: ESP-Matter started successfully +W (2499) wifi:Haven't to connect to a suitable AP now! +I (2508) chip[DL]: Done driving station state, nothing else to do... +I (2515) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) +I (2525) NimBLE: GAP procedure initiated: advertise; +I (2525) NimBLE: disc_mode=2 + +I (2526) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 +I (2530) NimBLE: + +I (2532) chip[DL]: CHIPoBLE advertising started +I (2533) MAIN: Commissioning window opened +> +I (4079) I2C: Creating I2C mutex +I (14080) SPI: Creating SPI mutex +I (14122) INFLUX_BUFFER: SD card mounted successfully +I (14123) INFLUX_BUFFER: /sdcard exists, SD card initialized successfully +I (14126) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +W (14140) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +I (14164) BME688: Delaying for 10000 microseconds +I (14782) Flicker: Flicker sensor initialized on pin 14 +I (14784) temperature_sensor: Range [20°C ~ 100°C], error < 2°C +I (14786) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data +W (14791) wifi:Haven't to connect to a suitable AP now! +I (14794) INFLUX_BUFFER: Flushing chunk starting at line 10: +I (14786) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data +W (14799) AS7341: Reading spectral data from AS7341 +I (14787) SEN66: Creating sensor mutex +I (14815) SEN66: Waking up sensor by setting power GPIO to enabled level... +I (14802) INFLUX: Writing to InfluxDB +I (14802) DeviceStatus: Active tasks: 26 +I (14832) DeviceStatus: Task list: +main R 1 876 4 +gather_sensor_t X 1 1352 24 +IDLE1 R 0 700 6 +IDLE0 R 0 692 5 +gather_sensor_t B 1 2544 21 +ds1307_update B 1 800 17 +gather_task B 1 1756 19 +gather_task B 1 14032 20 +alive_diode_tas B 5 356 8 +reset_task B 0 3432 15 +tiT B 18 2424 10 +gather_sensor_t B 1 1724 23 +ota_task B 1 7516 18 +CHIP B 1 3708 12 +Tmr Svc B 1 1364 7 +gather_sensor_t B 1 1300 25 +sys_evt B 20 2672 9 +ipc0 S 24 372 1 +ipc1 S 24 524 2 +console B 5 648 16 +esp_timer S 22 3332 3 +gather_sensor_t B 1 2332 22 +gather_sensor_t B 1 2544 26 +wifi B 23 4280 11 +btController B 23 1952 13 +nimble_host B 21 1480 14 + +W (14898) DeviceStatus: RTC is uninitialized, using current time as timestamp +W (15125) AS7341: Reading basic counts data from AS7341 +W (15141) AS7341: Reading flicker detection state from AS7341 +W (15359) as7341: FD Status Register: 0x20 (0b00100000) +I (15365) AS7341: Updating Matter attributes with AS7341 data: 16909 +I (15365) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms +I (15379) Flicker: Starting to collect samples in gather task +I (15375) BME688: Delaying for 199517 microseconds +I (15366) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** +I (15394) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 16909 ********** +E (15406) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +E (15418) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +W (15420) AS7341: Formatting AS7341 data in line protocol +AS7341 line protocol: +as7341_adc,device=A4CB8FC946F8 f1=1,f2=2,f3=3,f4=4,f5=3,f6=4,f7=7,f8=9,clear=22,nir=5 946684801000000000 +as7341_basic_counts,device=A4CB8FC946F8 f1=2.23,f2=4.46,f3=6.69,f4=8.92,f5=6.69,f6=8.92,f7=15.62,f8=20.08,clear=49.08,nir=11.15 946684801000000000 +as7341_flicker,device=A4CB8FC946F8 state="unknown" 946684801000000000 +W (15910) DeviceStatus: RTC is uninitialized, using current time as timestamp +I (16403) Flicker: Finished collecting samples, notifying main task +I (16405) Flicker: Collected 1024 samples for dominant frequency analysis +I (16412) SENSOR_GATHERER: Sensor should now be asleep +I (16424) SENSOR_GATHERER: Sensor should now be asleep +I (16464) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** +I (16466) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** +E (16479) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +E (16491) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +W (16493) BME688: RTC is uninitialized, using current time as timestamp +I (16911) SENSOR_GATHERER: Sensor should now be asleep +W (17504) BME688: RTC is uninitialized, using current time as timestamp +W (18505) BME688: RTC is uninitialized, using current time as timestamp +W (19506) BME688: RTC is uninitialized, using current time as timestamp +I (19826) SEN66: Sent start measurement command, waiting for sensor to be ready... +I (19923) MICROPHONE: Read 300000 bytes in 4936 ms +I (19924) MICROPHONE: Read 300000 bytes of audio data +I (20116) MICROPHONE: Sum SQ: 0.135236, RMS pressure: 0.001163, Normalized: 0.001163, dB peak: -58.69 +W (20118) SEN66: RTC is uninitialized, using current time as timestamp +W (20507) BME688: RTC is uninitialized, using current time as timestamp +W (21130) SEN66: RTC is uninitialized, using current time as timestamp +W (21508) BME688: RTC is uninitialized, using current time as timestamp +E (21833) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (21835) esp-tls: Failed to open new connection +E (21846) transport_base: Failed to open a new connection +E (21847) HTTP_CLIENT: Connection failed, sock < 0 +I (21848) INFLUX: HTTP request performed, status code: 0 +E (21859) INFLUX: Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT +E (21861) INFLUX_BUFFER: Failed to write chunk to Influx: ESP_FAIL +W (22131) SEN66: RTC is uninitialized, using current time as timestamp +W (22509) BME688: RTC is uninitialized, using current time as timestamp +I (22799) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (23132) SEN66: RTC is uninitialized, using current time as timestamp +I (23300) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (23510) BME688: RTC is uninitialized, using current time as timestamp +E BOD: Brownout detector was triggered + + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x40376756 +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (24) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (27) boot: efuse block revision: v1.4 +I (31) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (42) boot: Enabling RNG early entropy source... +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (108) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (494) boot: Loaded app from partition at offset 0x40000 +I (494) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (505) octal_psram: density : 0x03 (64 Mbit) +I (507) octal_psram: good-die : 0x01 (Pass) +I (511) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (539) octal_psram: DriveStrength: 0x00 (1/1) +I (543) esp_psram: Found 8MB PSRAM device +I (547) esp_psram: Speed: 40MHz +I (550) cpu_start: Multicore app +I (1284) esp_psram: SPI SRAM memory test OK +I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1293) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1303) app_init: App version: 1.0 +I (1307) app_init: Compile time: Jun 29 2026 11:03:32 +I (1312) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1321) efuse_init: Min chip rev: v0.0 +I (1325) efuse_init: Max chip rev: v0.99 +I (1329) efuse_init: Chip rev: v0.2 +I (1333) heap_init: Initializing. RAM available for dynamic allocation: +I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1368) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1396) main_task: Started on CPU0 +I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1407) main_task: Calling app_main() +E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1439) MAIN: Matter node created successfully +W (1440) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1470) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1505) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1540) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1575) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1584) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1595) pp: pp rom version: e7ae62f +I (1596) net80211: net80211 rom version: e7ae62f +I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 +I (1619) wifi:wifi firmware version: 4df78f2 +I (1620) wifi:wifi certification version: v7.0 +I (1620) wifi:config NVS flash: enabled +I (1620) wifi:config nano formatting: disabled +I (1625) wifi:Init data frame dynamic rx buffer num: 32 +I (1630) wifi:Init static rx mgmt buffer num: 5 +I (1634) wifi:Init management short buffer num: 32 +I (1638) wifi:Init dynamic tx buffer num: 32 +I (1642) wifi:Init static tx FG buffer num: 2 +I (1646) wifi:Init static rx buffer size: 1600 +I (1650) wifi:Init static rx buffer num: 10 +I (1654) wifi:Init dynamic rx buffer num: 32 +I (1659) wifi_init: rx ba win: 6 +I (1661) wifi_init: accept mbox: 6 +I (1664) wifi_init: tcpip mbox: 32 +I (1667) wifi_init: udp mbox: 6 +I (1670) wifi_init: tcp mbox: 6 +I (1673) wifi_init: tcp tx win: 5760 +I (1676) wifi_init: tcp rx win: 5760 +I (1680) wifi_init: tcp mss: 1440 +I (1691) chip[DL]: NVS set: chip-counters/reboot-count = 15841 (0x3DE1) +I (1694) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1696) BLE_INIT: BT controller compile version [1bb2f50] +I (1701) BLE_INIT: Using main XTAL as clock source +I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1714) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +I (1766) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0) +I (1806) NimBLE: GAP procedure initiated: stop advertising. + +I (1807) CHIP[DL]: BLE host-controller synced +I (2308) chip[DL]: Starting ESP WiFi layer +I (2310) wifi:mode : sta (a4:cb:8f:c9:46:f8) +I (2311) wifi:enable tsf +W (2311) wifi:Haven't to connect to a suitable AP now! +I (2312) chip[DL]: +I (2312) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43 +Done driving station state, nothing else to do... +I (2321) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 +W (2325) wifi:Haven't to connect to a suitable AP now! +I (2336) chip[DL]: Done driving station state, nothing else to do... +I (2342) chip[SVR]: Initializing subscription resumption storage... +I (2349) chip[SVR]: Server initializing... +I (2352) chip[TS]: Last Known Good Time: 2026-08-04T05:56:06 +I (2358) chip[DMG]: AccessControl: initializing +I (2361) chip[DMG]: Examples::AccessControlDelegate::Init +I (2366) chip[DMG]: AccessControl: setting +I (2370) chip[DMG]: DefaultAclStorage: initializing +I (2375) chip[DMG]: DefaultAclStorage: 0 entries loaded +I (2380) chip[SVR]: WARNING: mTestEventTriggerDelegate is null +I (2399) chip[ZCL]: Using ZAP configuration... +I (2399) esp_matter_cluster: Cluster plugin init common callback +I (2400) chip[DMG]: AccessControlCluster: initializing +I (2403) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported +I (2413) chip[DIS]: Updating services using commissioning mode 1 +I (2417) chip[DIS]: CHIP minimal mDNS started advertising. +I (2422) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 +I (2432) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: 3B5442BDAD98C80C. +I (2442) chip[DIS]: mDNS service published: _matterc._udp +I (2447) chip[IN]: CASE Server enabling CASE session setups +I (2453) chip[SVR]: Joining Multicast groups +I (2456) chip[SVR]: Server Listening... +I (2461) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** +I (2471) data_model: Dynamic endpoint 0 added +E (2474) data_model: Cluster cannot be NULL. +I (2479) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** +I (2489) data_model: Dynamic endpoint 1 added +E (2493) data_model: Cluster cannot be NULL. +I (2498) chip[DL]: WIFI_EVENT_STA_START +I (2499) MAIN: ESP-Matter started successfully +W (2501) wifi:Haven't to connect to a suitable AP now! +I (2510) chip[DL]: Done driving station state, nothing else to do... +I (2517) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) +I (2527) NimBLE: GAP procedure initiated: advertise; +I (2527) NimBLE: disc_mode=2 + +I (2528) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 +I (2532) NimBLE: + +I (2534) chip[DL]: CHIPoBLE advertising started +I (2535) MAIN: Commissioning window opened +> +I (4082) I2C: Creating I2C mutex +I (14083) SPI: Creating SPI mutex +I (14125) INFLUX_BUFFER: SD card mounted successfully +I (14126) INFLUX_BUFFER: /sdcard exists, SD card initialized successfully +I (14129) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +W (14143) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +I (14167) BME688: Delaying for 10000 microseconds +I (14785) Flicker: Flicker sensor initialized on pin 14 +I (14787) temperature_sensor: Range [20°C ~ 100°C], error < 2°C +I (14789) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data +I (14789) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data +W (14794) wifi: +I (14790) SEN66: Creating sensor mutex +Haven't to connect to a suitable AP now! +I (14805) SEN66: Waking up sensor by setting power GPIO to enabled level... + +I (14803) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms +I (14830) Flicker: Starting to collect samples in gather task +I (14818) DeviceStatus: Active tasks: 26 +I (14797) INFLUX_BUFFER: Flushing chunk starting at line 10: +I (14847) INFLUX: Writing to InfluxDB +I (14846) DeviceStatus: Task list: +gather_sensor_t X 1 1140 22 +main R 1 876 4 +IDLE1 R 0 700 6 +IDLE0 R 0 692 5 +ds1307_update B 1 808 17 +gather_task B 1 1860 19 +gather_task B 1 14040 20 +alive_diode_tas B 5 352 8 +reset_task B 0 3424 15 +tiT B 18 2420 10 +gather_sensor_t B 23 1720 24 +ota_task B 1 7508 18 +CHIP B 1 3636 12 +Tmr Svc B 1 1364 7 +gather_sensor_t B 1 2544 21 +sys_evt B 20 2684 9 +ipc0 S 24 372 1 +ipc1 S 24 524 2 +console B 5 640 16 +esp_timer S 22 3332 3 +gather_sensor_t B 1 2284 23 +gather_sensor_t B 1 2324 25 +wifi B 23 4324 11 +btController B 23 1960 13 +gather_sensor_t S 1 1344 26 +flicker_gather_ S 24 2060 27 +nimble_host B 21 1472 14 + +W (14924) DeviceStatus: RTC is uninitialized, using current time as timestamp +I (15857) Flicker: Finished collecting samples, notifying main task +I (15859) Flicker: Collected 1024 samples for dominant frequency analysis +W (15866) AS7341: Reading spectral data from AS7341 +I (15867) SENSOR_GATHERER: Sensor should now be asleep +I (15876) BME688: Delaying for 199517 microseconds +W (15926) DeviceStatus: RTC is uninitialized, using current time as timestamp +W (16176) AS7341: Reading basic counts data from AS7341 +W (16192) AS7341: Reading flicker detection state from AS7341 +W (16410) as7341: FD Status Register: 0x20 (0b00100000) +I (16416) AS7341: Updating Matter attributes with AS7341 data: 16707 +I (16417) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** +I (16430) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 16707 ********** +E (16443) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +E (16445) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +W (16457) AS7341: RTC is uninitialized, using current time as timestamp +I (16458) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** +I (16471) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** +E (16484) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +E (16496) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +W (16497) BME688: RTC is uninitialized, using current time as timestamp +I (16927) SENSOR_GATHERER: Sensor should now be asleep +W (17458) AS7341: RTC is uninitialized, using current time as timestamp +W (17509) BME688: RTC is uninitialized, using current time as timestamp +W (18459) AS7341: RTC is uninitialized, using current time as timestamp +W (18510) BME688: RTC is uninitialized, using current time as timestamp +W (19460) AS7341: RTC is uninitialized, using current time as timestamp +W (19511) BME688: RTC is uninitialized, using current time as timestamp +I (19817) SEN66: Sent start measurement command, waiting for sensor to be ready... +I (19927) MICROPHONE: Read 300000 bytes in 4936 ms +I (19928) MICROPHONE: Read 300000 bytes of audio data +I (20119) MICROPHONE: Sum SQ: 0.185352, RMS pressure: 0.001361, Normalized: 0.001361, dB peak: -57.32 +W (20121) SEN66: RTC is uninitialized, using current time as timestamp +W (20461) AS7341: RTC is uninitialized, using current time as timestamp +W (20512) BME688: RTC is uninitialized, using current time as timestamp +W (21133) SEN66: RTC is uninitialized, using current time as timestamp +W (21462) AS7341: RTC is uninitialized, using current time as timestamp +W (21513) BME688: RTC is uninitialized, using current time as timestamp +E (21849) esp-tls: couldn't get hostname for :influx.kota-app.com: getaddrinfo() returns 202, addrinfo=0x0 +E (21851) esp-tls: Failed to open new connection +E (21852) transport_base: Failed to open a new connection +E (21863) HTTP_CLIENT: Connection failed, sock < 0 +I (21864) INFLUX: HTTP request performed, status code: 0 +E (21875) INFLUX: Failed to perform HTTP request: 0, ESP_ERR_HTTP_CONNECT +E (21877) INFLUX_BUFFER: Failed to write chunk to Influx: ESP_FAIL +W (22134) SEN66: RTC is uninitialized, using current time as timestamp +W (22463) AS7341: RTC is uninitialized, using current time as timestamp +W (22514) BME688: RTC is uninitialized, using current time as timestamp +I (22791) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (23135) SEN66: RTC is uninitialized, using current time as timestamp +I (23292) SENSOR_GATHERER: 1/2 tasks still running, waiting... +W (23464) AS7341: RTC is uninitialized, using current time as timestamp +W (23515) BME688: RTC is uninitialized, using current time as timestamp +I (23793) SENSOR_GATHERER: 1/2 tasks still running, waiting... +I (23888) INFLUX: Writing to InfluxDB +E BOD: Brownout detector was triggered + +Guru Meditation Error: Core 0 panic'ed (Interrupt wdt timeout on CPU0). + +Core 0 register dump: +PC : 0x4037f211 PS : 0x00060d34 A0 : 0x80382ff0 A1 : 0x3fc9ec40 +A2 : 0x3fc9c7c8 A3 : 0xb33fffff A4 : 0x0000cdcd A5 : 0x00060923 +A6 : 0x3fceff68 A7 : 0x0000cdcd A8 : 0xfffffff1 A9 : 0x3fc9c7c8 +A10 : 0x01ffffff A11 : 0x0000cdcd A12 : 0x00001eae A13 : 0x40383206 +A14 : 0x3fcef74c A15 : 0x3fc9e9a0 SAR : 0x0000000c EXCCAUSE: 0x00000005 +EXCVADDR: 0x00000000 LBEG : 0x00000000 LEND : 0x00000000 LCOUNT : 0x00000000 +Core 0 was running in ISR context: +EPC1 : 0x42156637 EPC2 : 0x4037f34a EPC3 : 0x40041a76 EPC4 : 0x4037f211 + + +Backtrace: 0x4037f20e:0x3fc9ec40 0x40382fed:0x3fc9ec60 0x40384c1c:0x3fc9ec90 0x40382a9d:0x3fc9ecb0 0x40377011:0x3fc9ecd0 0x4037b2f9:0x3fc9ecf0 0x4037b32b:0x3fc9ed20 0x4037768d:0x3fc9ed40 0x4037b965:0x3fc9ed60 0x40041a73:0x3fc9ee20 0x40049059:0x3fc9ee40 0x40048818:0x3fc9ee60 0x40048c4b:0x3fc9ee80 0x40043cf3:0x3fc9eea0 0x40043cd5:0x3fc9eec0 0x40044183:0x3fc9eee0 0x40044281:0x3fc9ef60 0x40376715:0x3fc9efb0 0x4037664b:0x3fc9efd0 0x4037b819:0x3fc9eff0 0x4037f347:0x3fcb80e0 0x4216eb41:0x3fcb8100 0x4038429b:0x3fcb8120 0x40382e6d:0x3fcb8140 + + + + +ELF file SHA256: 45fa9d5b9 + +Rebooting... +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0xc (RTC_SW_CPU_RST),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x4038236c +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (24) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (27) boot: efuse block revision: v1.4 +I (31) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (42) boot: Enabling RNG early entropy source... +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (108) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (468) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (494) boot: Loaded app from partition at offset 0x40000 +I (494) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (505) octal_psram: density : 0x03 (64 Mbit) +I (507) octal_psram: good-die : 0x01 (Pass) +I (511) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (539) octal_psram: DriveStrength: 0x00 (1/1) +I (543) esp_psram: Found 8MB PSRAM device +I (547) esp_psram: Speed: 40MHz +I (550) cpu_start: Multicore app +I (1284) esp_psram: SPI SRAM memory test OK +I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1293) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1303) app_init: App version: 1.0 +I (1307) app_init: Compile time: Jun 29 2026 11:03:32 +I (1312) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1321) efuse_init: Min chip rev: v0.0 +I (1325) efuse_init: Max chip rev: v0.99 +I (1329) efuse_init: Chip rev: v0.2 +I (1333) heap_init: Initializing. RAM available for dynamic allocation: +I (1339) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1368) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1376) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1396) main_task: Started on CPU0 +I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1407) main_task: Calling app_main() +E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1440) MAIN: Matter node created successfully +W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1550) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1595) pp: pp rom version: e7ae62f +I (1597) net80211: net80211 rom version: e7ae62f +I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 +I (1620) wifi:wifi firmware version: 4df78f2 +I (1620) wifi:wifi certification version: v7.0 +I (1620) wifi:config NVS flash: enabled +I (1621) wifi:config nano formatting: disabled +I (1625) wifi:Init data frame dynamic rx buffer num: 32 +I (1630) wifi:Init static rx mgmt buffer num: 5 +I (1634) wifi:Init management short buffer num: 32 +I (1638) wifi:Init dynamic tx buffer num: 32 +I (1643) wifi:Init static tx FG buffer num: 2 +I (1647) wifi:Init static rx buffer size: 1600 +I (1651) wifi:Init static rx buffer num: 10 +I (1655) wifi:Init dynamic rx buffer num: 32 +I (1659) wifi_init: rx ba win: 6 +I (1662) wifi_init: accept mbox: 6 +I (1665) wifi_init: tcpip mbox: 32 +I (1668) wifi_init: udp mbox: 6 +I (1671) wifi_init: tcp mbox: 6 +I (1674) wifi_init: tcp tx win: 5760 +I (1677) wifi_init: tcp rx win: 5760 +I (1680) wifi_init: tcp mss: 1440 +I (1691) chip[DL]: NVS set: chip-counters/reboot-count = 15842 (0x3DE2) +I (1694) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1697) BLE_INIT: BT controller compile version [1bb2f50] +I (1702) BLE_INIT: Using main XTAL as clock source +I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +E BOD: Brownout detector was triggered + + +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x3 (RTC_SW_SYS_RST),boot:0xb (SPI_FAST_FLASH_BOOT) +Saved PC:0x40376756 +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce2820,len:0x14f0 +load:0x403c8700,len:0xd24 +load:0x403cb700,len:0x2f48 +entry 0x403c8924 +I (24) boot: ESP-IDF v5.5.3-dirty 2nd stage bootloader +I (24) boot: compile time Jun 29 2026 11:03:24 +I (24) boot: Multicore bootloader +I (25) boot: chip revision: v0.2 +I (27) boot: efuse block revision: v1.4 +I (31) boot.esp32s3: Boot SPI Speed : 80MHz +I (35) boot.esp32s3: SPI Mode : DIO +I (39) boot.esp32s3: SPI Flash Size : 8MB +I (42) boot: Enabling RNG early entropy source... +I (47) boot: Partition Table: +I (50) boot: ## Label Usage Type ST Offset Length +I (56) boot: 0 esp_secure_cert unknown 3f 06 0000d000 00002000 +I (62) boot: 1 nvs WiFi data 01 02 00010000 00020000 +I (69) boot: 2 nvs_keys NVS keys 01 04 00030000 00001000 +I (75) boot: 3 otadata OTA data 01 00 00031000 00002000 +I (82) boot: 4 phy_init RF data 01 01 00033000 00001000 +I (88) boot: 5 ota_0 OTA app 00 10 00040000 00300000 +I (95) boot: 6 ota_1 OTA app 00 11 00340000 00300000 +I (101) boot: 7 fctry WiFi data 01 02 00640000 00006000 +I (108) boot: End of partition table +I (112) esp_image: segment 0: paddr=00040020 vaddr=3c170020 size=63194h (405908) map +I (190) esp_image: segment 1: paddr=000a31bc vaddr=3fc9c600 size=05cc0h ( 23744) load +I (196) esp_image: segment 2: paddr=000a8e84 vaddr=40374000 size=07194h ( 29076) load +I (202) esp_image: segment 3: paddr=000b0020 vaddr=42000020 size=16ff80h (1507200) map +I (469) esp_image: segment 4: paddr=0021ffa8 vaddr=4037b194 size=113a0h ( 70560) load +I (484) esp_image: segment 5: paddr=00231350 vaddr=50000000 size=00020h ( 32) load +I (494) boot: Loaded app from partition at offset 0x40000 +I (495) boot: Disabling RNG early entropy source... +I (505) octal_psram: vendor id : 0x0d (AP) +I (505) octal_psram: dev id : 0x02 (generation 3) +I (505) octal_psram: density : 0x03 (64 Mbit) +I (507) octal_psram: good-die : 0x01 (Pass) +I (511) octal_psram: Latency : 0x01 (Fixed) +I (516) octal_psram: VCC : 0x01 (3V) +I (520) octal_psram: SRF : 0x01 (Fast Refresh) +I (525) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (530) octal_psram: BurstLen : 0x01 (32 Byte) +I (534) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (539) octal_psram: DriveStrength: 0x00 (1/1) +I (544) esp_psram: Found 8MB PSRAM device +I (547) esp_psram: Speed: 40MHz +I (550) cpu_start: Multicore app +I (1284) esp_psram: SPI SRAM memory test OK +I (1292) cpu_start: GPIO 44 and 43 are used as console UART I/O pins +I (1293) cpu_start: Pro cpu start user code +I (1293) cpu_start: cpu freq: 160000000 Hz +I (1295) app_init: Application information: +I (1299) app_init: Project name: kota-sensorboard +I (1304) app_init: App version: 1.0 +I (1307) app_init: Compile time: Jun 29 2026 11:03:32 +I (1312) app_init: ELF file SHA256: 45fa9d5b9... +I (1317) app_init: ESP-IDF: v5.5.3-dirty +I (1321) efuse_init: Min chip rev: v0.0 +I (1325) efuse_init: Max chip rev: v0.99 +I (1329) efuse_init: Chip rev: v0.2 +I (1333) heap_init: Initializing. RAM available for dynamic allocation: +I (1340) heap_init: At 3FCB3FD0 len 00035740 (213 KiB): RAM +I (1345) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM +I (1350) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM +I (1355) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM +I (1361) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocator +I (1368) spi_flash: detected chip: generic +I (1371) spi_flash: flash io: dio +I (1377) sleep_gpio: Configure to isolate all GPIO pins in sleep state +I (1381) sleep_gpio: Enable automatic switching of GPIO sleep configuration +I (1396) main_task: Started on CPU0 +I (1399) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations +I (1407) main_task: Calling app_main() +E (1410) ID: Device MAC address: A4:CB:8F:C9:46:F8 +I (1435) OTA_CLIENT: No OTA token found in NVS, need to register device +I (1439) MAIN: Matter node created successfully +W (1441) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1444) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1453) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1462) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1471) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1479) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1488) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1497) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1506) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1514) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1523) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1532) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1541) data_model: Server Cluster 0x00000003 on endpoint 0x0001 already exists. Not creating again. +W (1549) data_model: Attribute 0x0000FFFC on cluster 0x00000003 already exists. Not creating again. +W (1558) data_model: Attribute 0x0000FFFD on cluster 0x00000003 already exists. Not creating again. +W (1567) data_model: Attribute 0x00000000 on cluster 0x00000003 already exists. Not creating again. +W (1576) data_model: Attribute 0x00000001 on cluster 0x00000003 already exists. Not creating again. +W (1585) data_model: Command 0x00000000 on cluster 0x00000003 already exists. Not creating again. +I (1595) pp: pp rom version: e7ae62f +I (1597) net80211: net80211 rom version: e7ae62f +I (1602) wifi:wifi driver task: 3fcc52d0, prio:23, stack:6656, core=0 +I (1619) wifi:wifi firmware version: 4df78f2 +I (1620) wifi:wifi certification version: v7.0 +I (1620) wifi:config NVS flash: enabled +I (1621) wifi:config nano formatting: disabled +I (1625) wifi:Init data frame dynamic rx buffer num: 32 +I (1630) wifi:Init static rx mgmt buffer num: 5 +I (1634) wifi:Init management short buffer num: 32 +I (1638) wifi:Init dynamic tx buffer num: 32 +I (1642) wifi:Init static tx FG buffer num: 2 +I (1647) wifi:Init static rx buffer size: 1600 +I (1651) wifi:Init static rx buffer num: 10 +I (1655) wifi:Init dynamic rx buffer num: 32 +I (1659) wifi_init: rx ba win: 6 +I (1661) wifi_init: accept mbox: 6 +I (1665) wifi_init: tcpip mbox: 32 +I (1668) wifi_init: udp mbox: 6 +I (1671) wifi_init: tcp mbox: 6 +I (1673) wifi_init: tcp tx win: 5760 +I (1677) wifi_init: tcp rx win: 5760 +I (1680) wifi_init: tcp mss: 1440 +I (1692) chip[DL]: NVS set: chip-counters/reboot-count = 15843 (0x3DE3) +I (1695) chip[DL]: Real time clock set to 946684800 (2000-01-01 00:00:00 UTC) +I (1697) BLE_INIT: BT controller compile version [1bb2f50] +I (1702) BLE_INIT: Using main XTAL as clock source +I (1706) BLE_INIT: Feature Config, ADV:1, BLE_50:1, DTM:1, SCAN:1, CCA:0, SMP:1, CONNECT:1 +I (1715) BLE_INIT: Bluetooth MAC: a4:cb:8f:c9:46:fa +I (1720) phy_init: phy_version 711,97bcf0a2,Aug 25 2025,19:04:10 +I (1767) phy_init: Saving new calibration data due to checksum failure or outdated calibration data, mode(0) +I (1784) NimBLE: GAP procedure initiated: stop advertising. + +I (1786) CHIP[DL]: BLE host-controller synced +I (2286) chip[DL]: Starting ESP WiFi layer +I (2288) wifi:mode : sta (a4:cb:8f:c9:46:f8) +I (2289) wifi:enable tsf +W (2289) wifi:Haven't to connect to a suitable AP now! +I (2290) chip[DL]: +I (2290) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 43 +Done driving station state, nothing else to do... +I (2299) chip[DL]: Posting ESPSystemEvent: Wifi Event with eventId : 2 +W (2303) wifi:Haven't to connect to a suitable AP now! +I (2314) chip[DL]: Done driving station state, nothing else to do... +I (2320) chip[SVR]: Initializing subscription resumption storage... +I (2327) chip[SVR]: Server initializing... +I (2330) chip[TS]: Last Known Good Time: 2026-08-04T05:56:06 +I (2336) chip[DMG]: AccessControl: initializing +I (2339) chip[DMG]: Examples::AccessControlDelegate::Init +I (2344) chip[DMG]: AccessControl: setting +I (2348) chip[DMG]: DefaultAclStorage: initializing +I (2353) chip[DMG]: DefaultAclStorage: 0 entries loaded +I (2358) chip[SVR]: WARNING: mTestEventTriggerDelegate is null +I (2377) chip[ZCL]: Using ZAP configuration... +I (2378) esp_matter_cluster: Cluster plugin init common callback +I (2378) chip[DMG]: AccessControlCluster: initializing +I (2381) chip[ZCL]: WRITE ERR: ep 0 clus 0x0000_0030 attr 0x0000_0000 not supported +I (2393) chip[DIS]: Updating services using commissioning mode 1 +I (2395) chip[DIS]: CHIP minimal mDNS started advertising. +I (2400) chip[DIS]: Advertise commission parameter vendorID=65521 productID=32768 discriminator=3840/15 cm=1 cp=0 +I (2410) chip[DIS]: CHIP minimal mDNS configured as 'Commissionable node device'; instance name: 73BA585012FF9068. +I (2420) chip[DIS]: mDNS service published: _matterc._udp +I (2425) chip[IN]: CASE Server enabling CASE session setups +I (2431) chip[SVR]: Joining Multicast groups +I (2434) chip[SVR]: Server Listening... +I (2439) esp_matter_attribute: ********** R : Endpoint 0x0000's Cluster 0x0000003C's Attribute 0x0000FFFC is 0 ********** +I (2449) data_model: Dynamic endpoint 0 added +E (2452) data_model: Cluster cannot be NULL. +I (2457) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000003's Attribute 0x00000001 is 2 ********** +I (2467) data_model: Dynamic endpoint 1 added +E (2471) data_model: Cluster cannot be NULL. +I (2476) chip[DL]: WIFI_EVENT_STA_START +I (2477) MAIN: ESP-Matter started successfully +W (2479) wifi:Haven't to connect to a suitable AP now! +I (2488) chip[DL]: Done driving station state, nothing else to do... +I (2495) chip[DL]: Configuring CHIPoBLE advertising (interval 25 ms, connectable) +I (2505) NimBLE: GAP procedure initiated: advertise; +I (2506) NimBLE: disc_mode=2 + +I (2506) NimBLE: adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=40 adv_itvl_max=40 +I (2511) NimBLE: + +I (2512) chip[DL]: CHIPoBLE advertising started +I (2513) MAIN: Commissioning window opened +> +I (4060) I2C: Creating I2C mutex +I (14061) SPI: Creating SPI mutex +I (14103) INFLUX_BUFFER: SD card mounted successfully +I (14104) INFLUX_BUFFER: /sdcard exists, SD card initialized successfully +I (14107) INFLUX_BUFFER: Read INFLUX_URL from .env file: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +W (14121) INFLUX: Initializing Influx client with URL: https://influx.kota-app.com/api/v2/write?bucket=sensorbox&org=kota +I (14145) BME688: Delaying for 10000 microseconds +I (14763) Flicker: Flicker sensor initialized on pin 14 +I (14765) temperature_sensor: Range [20°C ~ 100°C], error < 2°C +I (14767) SENSOR_GATHERER: Resumed 2 tasks to gather sensor data +I (14767) SENSOR_GATHERER: Resumed 4 tasks to gather sensor data +W (14772) wifi:Haven't to connect to a suitable AP now! +I (14768) SEN66: Creating sensor mutex +W (14782) AS7341: Reading spectral data from AS7341 +I (14775) INFLUX_BUFFER: Flushing chunk starting at line 10: +I (14796) INFLUX: Writing to InfluxDB +I (14784) SEN66: Waking up sensor by setting power GPIO to enabled level... +I (14783) DeviceStatus: Active tasks: 26 +I (14813) DeviceStatus: Task list: +gather_sensor_t X 1 1344 22 +IDLE1 R 0 812 6 +IDLE0 R 0 692 5 +gather_sensor_t B 1 2552 21 +ds1307_update B 1 800 17 +gather_task B 1 1964 19 +gather_task B 1 14032 20 +alive_diode_tas B 5 352 8 +reset_task B 0 3432 15 +tiT B 18 2420 10 +gather_sensor_t B 1 1720 24 +ota_task B 1 7516 18 +CHIP B 1 3700 12 +Tmr Svc B 1 1364 7 +gather_sensor_t B 1 1092 25 +sys_evt B 20 2684 9 +ipc0 S 24 372 1 +ipc1 S 24 524 2 +console B 5 648 16 +esp_timer S 22 3332 3 +gather_sensor_t B 1 2332 23 +gather_sensor_t B 1 2544 26 +wifi B 23 4324 11 +btController B 23 1960 13 +main B 1 876 4 +nimble_host B 21 1480 14 + +W (14878) DeviceStatus: RTC is uninitialized, using current time as timestamp +W (15105) AS7341: Reading basic counts data from AS7341 +W (15121) AS7341: Reading flicker detection state from AS7341 +W (15339) as7341: FD Status Register: 0x20 (0b00100000) +I (15345) AS7341: Updating Matter attributes with AS7341 data: 16707 +I (15345) Flicker: Collecting 1024 samples at 1000 Hz for dominant frequency analysis for total measurement window of 1000 ms +I (15359) Flicker: Starting to collect samples in gather task +I (15355) BME688: Delaying for 199517 microseconds +I (15347) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 0 ********** +I (15374) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000400's Attribute 0x00000000 is 16707 ********** +E (15386) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +E (15398) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x400, attribute_id: 0x0 +W (15400) AS7341: Formatting AS7341 data in line protocol +AS7341 line protocol: +as7341_adc,device=A4CB8FC946F8 f1=1,f2=2,f3=2,f4=3,f5=3,f6=4,f7=7,f8=9,clear=21,nir=5 946684801000000000 +as7341_basic_counts,device=A4CB8FC946F8 f1=2.23,f2=4.46,f3=4.46,f4=6.69,f5=6.69,f6=8.92,f7=15.62,f8=20.08,clear=46.85,nir=11.15 946684801000000000 +as7341_flicker,device=A4CB8FC946F8 state="unknown" 946684801000000000 +W (15890) DeviceStatus: RTC is uninitialized, using current time as timestamp +I (16383) Flicker: Finished collecting samples, notifying main task +I (16385) Flicker: Collected 1024 samples for dominant frequency analysis +I (16392) SENSOR_GATHERER: Sensor should now be asleep +I (16404) SENSOR_GATHERER: Sensor should now be asleep +I (16444) esp_matter_attribute: ********** R : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is null ********** +I (16446) esp_matter_attribute: ********** W : Endpoint 0x0001's Cluster 0x00000403's Attribute 0x00000000 is 1018 ********** +E (16459) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +E (16471) MAIN: Attribute update callback: endpoint_id: 1, cluster_id: 0x403, attribute_id: 0x0 +W (16473) BME688: RTC is uninitialized, using current time as timestamp +I (16891) SENSOR_GATHERER: Sensor should now be asleep +W (17484) BME688: RTC is uninitialized, using current time as timestamp --- disconnected --- \ No newline at end of file