Compare commits
1
Commits
v0.7
...
bfa7edce6f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfa7edce6f |
No files matched your search
@@ -0,0 +1,41 @@
|
|||||||
|
# KOTA Sensorbox Technician Tool v0.8
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Uruchomienie
|
||||||
|
|
||||||
|
**Windows:** dwuklik `run_windows.bat`.
|
||||||
|
|
||||||
|
**macOS:** dwuklik `run_macos.command`.
|
||||||
|
Jeśli Finder odpowie, że pliku nie można uruchomić (Google Drive gubi uprawnienie „wykonywalny”),
|
||||||
|
otwórz Terminal i jednorazowo wpisz `bash ` (ze spacją), przeciągnij plik `run_macos.command` do okna i naciśnij Enter.
|
||||||
|
Na stałe naprawia to: `chmod +x run_macos.command`.
|
||||||
|
|
||||||
|
Przy pierwszym uruchomieniu skrypt proponuje instalację `uv` (menedżer Pythona, instaluje się tylko dla bieżącego
|
||||||
|
użytkownika) i pobiera biblioteki. Potrzebny jest internet, trwa to 1–2 minuty. Kolejne starty są natychmiastowe.
|
||||||
|
Środowisko jest poza folderem Google Drive:
|
||||||
|
`%LOCALAPPDATA%\KotaTechnicianTool\venv-0.8` (Windows), `~/Library/Caches/KotaTechnicianTool/venv-0.8` (macOS).
|
||||||
|
|
||||||
|
Sesje, kopie zapasowe flash i ustawienia są w `~/KotaTechnicianTool` (Windows: `C:\Users\<użytkownik>\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**.
|
||||||
|
|
||||||
|
## Testy (dla zespołu)
|
||||||
|
|
||||||
|
```
|
||||||
|
uv run --frozen pytest -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Zmiany: `docs/CHANGES-v0.7.md`, `docs/CHANGES-v0.8.md`.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Technician tool v0.8 - changes
|
||||||
|
|
||||||
|
## Windows + macOS
|
||||||
|
- Dependencies verified to install on macOS arm64 and x86_64 (uv.lock: PySide6 6.11 universal2 wheels,
|
||||||
|
requires macOS 13+); Windows unchanged.
|
||||||
|
- SD card check: macOS `diskutil` reports FAT32 partitions as `DOS_FAT_32` / `Windows_FAT_32`; these are now
|
||||||
|
accepted (previously a correct card was reported as "not FAT32" on a Mac).
|
||||||
|
- Unit tests for the macOS serial-port filter (`/dev/cu.*` only) and FAT32 names.
|
||||||
|
- README.md with launch steps for both systems (incl. the Google Drive executable-bit workaround on macOS);
|
||||||
|
docs/mac_agent_command.txt updated for a v0.8 test run on a Mac.
|
||||||
|
|
||||||
|
## Diagnosis
|
||||||
|
- Harmless Wi-Fi start-up errors are ignored (rule `benign.wifi_start`): `wifi:sta is connecting, return error`,
|
||||||
|
`esp_wifi_connect() failed: ESP_ERR_WIFI_CONN`, `Failed to get configured network when updating network
|
||||||
|
status` (unpaired box).
|
||||||
|
- Brown-out resets are detected (`E BOD: Brownout detector was triggered`, ROM reports RTC_SW_SYS_RST).
|
||||||
|
- Service report note = the Diagnosis panel as text (LED, firmware, box self-report, start-up progress with
|
||||||
|
times, findings with "Seen Nx, first at ... s, last at ... s" and log line), shortened to fit the service
|
||||||
|
form's 5000 characters.
|
||||||
|
|
||||||
|
Not yet run on a real Mac: see docs/mac_agent_command.txt.
|
||||||
+21
-16
@@ -1,9 +1,10 @@
|
|||||||
You are testing the KOTA sensorbox technician tool v0.5 on macOS. The current folder is
|
You are testing the KOTA sensorbox technician tool v0.8 on macOS. The current folder is
|
||||||
004-technician_tool/v0.5 (a Python/PySide6 desktop app; it has only been tested on Windows so far).
|
004-technician_tool/v0.8 (a Python/PySide6 desktop app; tested on Windows 11, macOS paths are covered by
|
||||||
|
unit tests but not yet run on a real Mac).
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
- Python only via uv or conda/mamba. Never use bare/system python or pip.
|
- Python only via uv or conda/mamba. Never use bare/system python or pip.
|
||||||
- Do not build, modify or flash firmware other than the bundled firmware/new_system.bin.
|
- Do not build or modify firmware. Flash only files from the tool's firmware/ folder.
|
||||||
- Do not push anything to Git or GitLab. This is a local demo.
|
- Do not push anything to Git or GitLab. This is a local demo.
|
||||||
- Ask me before any destructive action: formatting an SD card, Full-mode flashing, or deleting files.
|
- Ask me before any destructive action: formatting an SD card, Full-mode flashing, or deleting files.
|
||||||
Before formatting, show me the exact disk (identifier, name, size, current files).
|
Before formatting, show me the exact disk (identifier, name, size, current files).
|
||||||
@@ -11,20 +12,24 @@ Rules:
|
|||||||
|
|
||||||
Tests, in order. Report pass/fail with evidence for each:
|
Tests, in order. Report pass/fail with evidence for each:
|
||||||
1. Launch: `bash run_macos.command` (it offers to install uv). The app window opens; note any warnings.
|
1. Launch: `bash run_macos.command` (it offers to install uv). The app window opens; note any warnings.
|
||||||
2. Tests: `UV_PROJECT_ENVIRONMENT="$HOME/Library/Caches/KotaTechnicianTool/venv-0.5" uv run --frozen pytest -q`
|
Also check whether a double-click in Finder works (Google Drive may drop the executable bit).
|
||||||
|
2. Tests: `UV_PROJECT_ENVIRONMENT="$HOME/Library/Caches/KotaTechnicianTool/venv-0.8" uv run --frozen pytest -q`
|
||||||
|
(expect 172 passed).
|
||||||
3. Serial: plug in the sensorbox (native USB). The port list shows a /dev/cu.usbmodem* "Sensorbox (native USB)"
|
3. Serial: plug in the sensorbox (native USB). The port list shows a /dev/cu.usbmodem* "Sensorbox (native USB)"
|
||||||
entry with a MAC. Connect, press "Reset & capture": the boot log starts at "ESP-ROM:esp32s3" and the
|
entry with a MAC (no /dev/tty.* duplicate). Connect, press "Reset & capture": the log starts at
|
||||||
Diagnosis panel identifies firmware v1.0 (build 45fa9d5b9). Check whether the port disappears after the
|
"ESP-ROM:esp32s3" and the Diagnosis panel shows firmware v1.0.16 (build 4f10989e), the "Box self-report"
|
||||||
reset and whether the tool reconnects.
|
section and the LED with its pattern. Check whether the port disappears after the reset and whether the
|
||||||
4. Guided troubleshooting: run "Quick check" and answer truthfully with what I tell you the LED shows.
|
tool reconnects. Wait 2 minutes: a "KOTA: health" line appears every 60 s.
|
||||||
5. Flash (App only, factory image; the box already runs it): the tool must enter flashing mode by itself,
|
4. Show filter "KOTA diagnostics + errors" shows only KOTA lines and errors.
|
||||||
write, restart the box and report "Success ... build 45fa9d5b9". Capture the esptool output.
|
5. Guided troubleshooting: run "Quick check" and answer truthfully with what I tell you the LED shows.
|
||||||
6. SD card tab: the settings load from ../../002-SD_card_format/ENV.txt. The card reader and card are listed
|
6. Flash (App only, the newest file in firmware/, chosen by its compile date): the tool must enter flashing mode
|
||||||
(internal disks must NOT be listed). Run "Check card", then, after my OK, "Format card and write ENV.TXT".
|
by itself, write, restart the box and report "Success ... build 4f10989e". Capture the esptool output.
|
||||||
It must use `diskutil eraseDisk FAT32 KOTA MBRFormat` on the correct disk, write ENV.TXT byte-identical
|
7. SD card tab: the card reader and card are listed (internal disks must NOT be listed). "Check card" must NOT
|
||||||
to the template, and eject the card.
|
report a wrong file system for a FAT32 card. After my OK, "Format card and write ENV.TXT" must use
|
||||||
7. "Service report..." opens the browser at https://service.kota-app.com/<MAC>#kota-report=... (it is fine
|
`diskutil eraseDisk FAT32 KOTA MBRFormat` on the correct disk, write ENV.TXT and eject the card.
|
||||||
if the page itself cannot prefill yet).
|
8. "Service report..." opens the browser at https://service.kota-app.com/<MAC>#kota-report=...; the note in
|
||||||
|
the form must match the Diagnosis panel (self-report, start-up progress with times, findings with
|
||||||
|
"Seen Nx, first at ... s").
|
||||||
|
|
||||||
At the end, write a short report: macOS version, Mac model/architecture, each test with result, errors, and
|
At the end, write a short report: macOS version, Mac model/architecture, each test with result, errors, and
|
||||||
suggested fixes (file:line).
|
suggested fixes (file:line).
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
"""KOTA sensorbox technician tool."""
|
"""KOTA sensorbox technician tool."""
|
||||||
|
|
||||||
__version__ = "0.7.0"
|
__version__ = "0.8.0"
|
||||||
@@ -137,6 +137,14 @@ id = "benign.zcl"
|
|||||||
pattern = 'chip\[ZCL\]: WRITE ERR|data_model: Cluster cannot be NULL'
|
pattern = 'chip\[ZCL\]: WRITE ERR|data_model: Cluster cannot be NULL'
|
||||||
severity = "ignore"
|
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]]
|
[[rule]]
|
||||||
id = "benign.connect_chain"
|
id = "benign.connect_chain"
|
||||||
# Transport-level echo of a failure that a more specific rule reports.
|
# Transport-level echo of a failure that a more specific rule reports.
|
||||||
|
|||||||
@@ -243,8 +243,9 @@ def check_card(mount: str, filesystem: str = "") -> CardReport:
|
|||||||
"""What the box would find on this card (values masked in the UI)."""
|
"""What the box would find on this card (values masked in the UI)."""
|
||||||
root = Path(mount)
|
root = Path(mount)
|
||||||
problems: list[str] = []
|
problems: list[str] = []
|
||||||
fs = filesystem.upper()
|
# Windows says "FAT32"; macOS diskutil says "DOS_FAT_32" / "Windows_FAT_32" / "MS-DOS FAT32".
|
||||||
if fs and "FAT32" not in fs and fs not in ("FAT", "MS-DOS FAT32", "MSDOS"):
|
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.")
|
problems.append(f"File system is {filesystem}; the box needs FAT32 - re-format the card.")
|
||||||
env_file = find_case_insensitive(root, ENV_NAME)
|
env_file = find_case_insensitive(root, ENV_NAME)
|
||||||
env = parse_env(env_file.read_bytes()) if env_file else None
|
env = parse_env(env_file.read_bytes()) if env_file else None
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "kota-technician-tool"
|
name = "kota-technician-tool"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
description = "KOTA sensorbox technician diagnostic tool (demo)"
|
description = "KOTA sensorbox technician diagnostic tool (demo)"
|
||||||
requires-python = ">=3.11,<3.14"
|
requires-python = ">=3.11,<3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
+3
-1
@@ -2,8 +2,10 @@
|
|||||||
# Double-click in Finder, or run: bash run_macos.command
|
# Double-click in Finder, or run: bash run_macos.command
|
||||||
cd "$(dirname "$0")" || exit 1
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
|
# If Finder refuses to open this file (Google Drive drops the executable bit), run it once as:
|
||||||
|
# bash run_macos.command (or: chmod +x run_macos.command)
|
||||||
# Keep the virtual environment out of the (Google Drive) tool folder.
|
# Keep the virtual environment out of the (Google Drive) tool folder.
|
||||||
export UV_PROJECT_ENVIRONMENT="$HOME/Library/Caches/KotaTechnicianTool/venv-0.7"
|
export UV_PROJECT_ENVIRONMENT="$HOME/Library/Caches/KotaTechnicianTool/venv-0.8"
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
if ! command -v uv >/dev/null 2>&1; then
|
if ! command -v uv >/dev/null 2>&1; then
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ setlocal
|
|||||||
cd /d "%~dp0"
|
cd /d "%~dp0"
|
||||||
|
|
||||||
rem Keep the virtual environment out of the (Google Drive) tool folder.
|
rem Keep the virtual environment out of the (Google Drive) tool folder.
|
||||||
set "UV_PROJECT_ENVIRONMENT=%LOCALAPPDATA%\KotaTechnicianTool\venv-0.7"
|
set "UV_PROJECT_ENVIRONMENT=%LOCALAPPDATA%\KotaTechnicianTool\venv-0.8"
|
||||||
set "PATH=%USERPROFILE%\.local\bin;%PATH%"
|
set "PATH=%USERPROFILE%\.local\bin;%PATH%"
|
||||||
|
|
||||||
where uv >nul 2>nul
|
where uv >nul 2>nul
|
||||||
|
|||||||
@@ -247,3 +247,13 @@ def test_live_brownout_loop_is_a_power_problem():
|
|||||||
assert top.finding.rule_id == "reboot.brownout" and top.finding.count >= 5
|
assert top.finding.rule_id == "reboot.brownout" and top.finding.count >= 5
|
||||||
assert "reboot.loop" in {c.rule_id for c in top.consequences}
|
assert "reboot.loop" in {c.rule_id for c in top.consequences}
|
||||||
assert r.verdict.startswith("Problem: Power supply")
|
assert r.verdict.startswith("Problem: Power supply")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("line", [
|
||||||
|
"E (2893) wifi:sta is connecting, return error",
|
||||||
|
"E (2892) chip[DL]: esp_wifi_connect() failed: ESP_ERR_WIFI_CONN",
|
||||||
|
"E (5120) chip[DL]: Failed to get configured network when updating network status: Error ESP32:0x0500300F",
|
||||||
|
])
|
||||||
|
def test_harmless_wifi_start_errors_are_not_reported(line):
|
||||||
|
_, r = run(BOOT + APP + [line])
|
||||||
|
assert r.roots == [] and "Unrecognised" not in r.to_text()
|
||||||
@@ -12,3 +12,15 @@ def test_other_ports_not_sensorbox():
|
|||||||
bt = PortInfo("COM3", "Standard Serial over Bluetooth link", None, None, None)
|
bt = PortInfo("COM3", "Standard Serial over Bluetooth link", None, None, None)
|
||||||
assert not bt.is_sensorbox and bt.mac is None
|
assert not bt.is_sensorbox and bt.mac is None
|
||||||
assert bt.label == "COM3 - Standard Serial over Bluetooth link"
|
assert bt.label == "COM3 - Standard Serial over Bluetooth link"
|
||||||
|
|
||||||
|
|
||||||
|
def test_macos_lists_only_callout_devices(monkeypatch):
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from kota_tech.serialio import ports
|
||||||
|
fake = [SimpleNamespace(device=d, description="USB JTAG/serial debug unit", vid=0x303A, pid=0x1001,
|
||||||
|
serial_number="A4:CB:8F:C9:46:F8")
|
||||||
|
for d in ("/dev/tty.usbmodem1101", "/dev/cu.usbmodem1101")]
|
||||||
|
monkeypatch.setattr(ports.sys, "platform", "darwin")
|
||||||
|
monkeypatch.setattr(ports.list_ports, "comports", lambda: fake)
|
||||||
|
assert [p.device for p in ports.list_serial_ports()] == ["/dev/cu.usbmodem1101"]
|
||||||
@@ -170,3 +170,14 @@ def test_elevated_result_folder_is_not_owner_only(tmp_path, monkeypatch):
|
|||||||
assert work.parent == tmp_path / "tmp" and work.is_dir()
|
assert work.parent == tmp_path / "tmp" and work.is_dir()
|
||||||
(work / "result.json").write_text('{"ok": true}', encoding="utf-8")
|
(work / "result.json").write_text('{"ok": true}', encoding="utf-8")
|
||||||
assert (work / "result.json").read_text(encoding="utf-8")
|
assert (work / "result.json").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fs", ["FAT32", "DOS_FAT_32", "Windows_FAT_32", "MS-DOS FAT32"])
|
||||||
|
def test_fat32_names_from_windows_and_macos_are_accepted(tmp_path, fs):
|
||||||
|
(tmp_path / "ENV.TXT").write_bytes(b"INFLUX_URL=https://x\nINFLUX_TOKEN=abc\n")
|
||||||
|
assert not any("File system" in p for p in check_card(str(tmp_path), fs).problems)
|
||||||
|
|
||||||
|
|
||||||
|
def test_exfat_is_flagged(tmp_path):
|
||||||
|
(tmp_path / "ENV.TXT").write_bytes(b"INFLUX_URL=https://x\nINFLUX_TOKEN=abc\n")
|
||||||
|
assert any("File system" in p for p in check_card(str(tmp_path), "ExFAT").problems)
|
||||||
@@ -250,7 +250,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kota-technician-tool"
|
name = "kota-technician-tool"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "esptool" },
|
{ name = "esptool" },
|
||||||
|
|||||||
Reference in new issue
Block a user