Files
elmer/doc/session-2026-05-26.md
T

308 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Сессия 2026-05-26 — отладка Android-клиента и Mock ELM327
## Хронология
### 08:00 — Старт: пулл изменений с ночи
Ночью на другой машине было сделано:
- `android/` вынесен в отдельный репо `github.com/Repinoid/elmer-android`
- Добавлен `tools/mock_elm327.py` — TCP-эмулятор ELM327 на порту 35000
- `web/raw_endpoint.py` — стейт-машина, парсинг ответов, LLM-интеграция
- Исправлены 3 бага в Android-клиенте (NetworkOnMainThread, URL сервера, фильтр \n)
### 08:20 — Попытка 1: телефон → mock → сервер
Запущены mock (:35000) и Flask (:5005). Настроен проброс портов через Windows:
```powershell
netsh interface portproxy add v4tov4 listenport=5005 listenaddress=0.0.0.0 connectport=5005 connectaddress=192.168.18.62
netsh advfirewall firewall add rule name="Elmer Flask" dir=in action=allow protocol=TCP localport=5005
```
WiFi IP ноутбука: `10.47.183.102`.
Телефон подключился к mock (TCP: OK), но команды не шли.
### 08:30 — Ошибка 1: Server URL захардкожен на obdai.ru
Глянув код клиента в `ElmForwardService.kt`, обнаружено:
```kotlin
// MainActivity.kt строка 66
putExtra(EXTRA_SERVER_URL, "https://obdai.ru/api/v1/raw-obd")
```
Телефон слал данные в интернет, а не на локальный Flask.
**Исправление:** в `MainActivity.kt` — авто-вывод URL сервера из IP устройства:
```kotlin
val deviceHost = debugHost.split(":")[0]
val localServerUrl = "http://$deviceHost:5005/api/v1/raw-obd"
```
### 08:35 — Ошибка 2: Gradle 9.5 слишком новый
CI упал с `org.gradle.api.artifacts.SelfResolvingDependency`. Причина: AGP 8.2.0 несовместим с Gradle 9.x.
**Исправление:** в `build-apk.yml`:
```yaml
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: "8.5"
```
И создан `gradle/wrapper/gradle-wrapper.properties` с `gradle-8.5-bin.zip`.
---
## ЧАСТЬ 2: Тест с реальным ELM327 (Фаэтон)
### Зелёная кнопка (TestService) — 100% работает
- BT-подключение к ELM327 ✅
- ATZ → ATEx → ATH1 ✅
- VIN: получен (16-ричные данные) ✅
- DTC stored/pending: получены ✅
- PID'ы: RPM, ОЖ, скорость, дроссель и др. ✅
- **Тайминги:** `Thread.sleep(250)` между командами — критически важно
### Оранжевая кнопка (ElmForwardService + сервер) — НЕ работает
Проблема: стейт-машина сервера шлёт команды мгновенно, без пауз. ELM327 не успевает.
- ATZ → OK
- ATEx → OK
- 0902 → SEARCHING...UNABLE TO CONNECT (ELM не может выполнить режим 09)
- Сервер переходит к DTC → шлёт 03 → ELM отвечает `?` → бесконечный цикл `?`
**Корень проблемы:** тонкий клиент требует server-driven архитектуру (сервер даёт команду → клиент пишет в ELM → ELM отвечает → клиент шлёт ответ серверу → сервер даёт следующую). Но сервер не делает пауз, а ELM327 требует ~200мс между командами.
### Почему Deadlock при первом запуске оранжевой кнопки
1. Клиент: BT-коннект ✅, входит в loop()
2. ELM327: ждёт команду (не шлёт приветствие без запроса)
3. Клиент: `read()` блокируется — нет данных
4. Сервер: не получает "READY" — не шлёт ATZ
5. **DEADLOCK**
**Исправлено:** `fwd("READY")` сразу после коннекта — кикстарт сервера.
---
## Вывод: архитектура телефон↔сервер в реальных условиях
### Проблема
В движении связи с сервером нет. Архитектура «сервер рулит каждой командой» нежизнеспособна.
### Решение (обсуждено)
**Две фазы работы:**
**Фаза 1 — ОФЛАЙН (в машине):**
- Клиент получает со старта **скрипт** от сервера
- Скрипт: последовательность команд + промпты водителю
- Клиент сам гоняет протокол (как зелёная кнопка)
- Данные пишутся локально (SQLite) с таймстемпами
- Водитель видит промпты: «Разгон 0-100», «Кикдаун», «Холостой ход 30с»
- Кнопка Старт / Стоп
**Фаза 2 — ОНЛАЙН (дома):**
- Клиент заливает всю сессию одним POST на сервер
- Сервер парсит, анализирует, LLM → диагноз
- Возможно — выдаёт следующий скрипт для нового теста
### Формат скрипта (пример):
```json
{
"name": "Тест турбины",
"steps": [
{"type": "obd", "cmd": "ATZ"},
{"type": "obd", "cmd": "010C", "label": "RPM"},
{"type": "prompt", "text": "Разгон 0-100, кикдаун"},
{"type": "loop", "pid": "010C", "duration": 30, "rate_ms": 200},
{"type": "obd", "cmd": "03"},
{"type": "upload"}
]
}
```
### Кто что делает
| Компонент | Файл | Статус | Что добавить |
|-----------|------|--------|-------------|
| Тестовый клиент | `TestService.kt` | ✅ гоняет протокол | Сохранение в БД, скрипты, промпты |
| Транспортный клиент | `ElmForwardService.kt` | ⚠️ требует стабильной связи | Возможно удалить |
| Стейт-машина | `raw_endpoint.py` | ⚠️ нет пауз | Переделать под батчевую обработку |
| Mock ELM327 | `mock_elm327.py` | ✅ эмулятор | Добавить задержки для реализма |
| Сервер приёма | `web/app.py` | ✅ | POST-эндпоинт для заливки сессии |
### Договорённости по процессу
- **НИКОГДА не кодить без прямой команды**
- Сначала обсуждать → потом делать
- Коммитить часто, с понятными сообщениями
- Документировать все ошибки и решения
### 08:40 — Ошибка 3: Коммиты не в ту ветку
Изначально все изменения ушли в `master`, но рабочая ветка — `relay-only`.
**Исправление:** переключился на `relay-only`, применил изменения туда, master откатил через `git reset --hard && git push --force`.
### 08:45 — Ветка relay-only: добавлена отладка
В `ElmForwardService.kt` добавлено:
- `say("🌐 Server: $serverUrl")` — показ URL при подключении
- `say("← $raw")` — каждое сырое сообщение от устройства
- `say("→ $cmd")` — каждая команда сервера
- Обработка ошибок: `⚠️ Server unreachable`, `⚠️ Bad JSON`
### 09:00 — Ошибка 4: Cleartext HTTP заблокирован
Телефон показал `⚠️ Server down: cleartext...`. Android 9+ блокирует HTTP (не-HTTPS) по умолчанию.
**Исправление:** в `AndroidManifest.xml`:
```xml
android:usesCleartextTraffic="true"
```
### 09:05 — Ошибка 5: APK не скачивается с сервера
Телефон открыл `http://10.47.183.102:5005`, страница загрузилась, но APK — 404.
Flask отдаёт статику из `/static/`, а ссылка была `/app-debug.apk`.
**Исправление:** ссылка изменена на `/static/app-debug.apk`.
### 09:10 — Ошибка 6: Приложение не устанавливается поверх
Google Play Protect проверил APK, но «приложение не установлено». Причина: каждый CI-билд генерирует новый debug-keystore → сигнатуры не совпадают → Android блокирует установку поверх.
**Исправление:** сгенерирован фиксированный `debug.keystore` (пароль `android`, alias `androiddebugkey`) и закоммичен в репо. В CI добавлен шаг:
```yaml
- name: Setup debug keystore
run: cp debug.keystore ~/.android/debug.keystore
```
### 09:15 — Тестовая версия: вместо сервера — локальный протокол
Пользователь потребовал тестовую версию без сервера. Создан `TestService.kt`:
- TCP-подключение к mock
- Самостоятельная отправка AT-команд (ATZ, ATE0, ATL0, ATSP0, ATH1)
- Чтение VIN (0902)
- Чтение DTC stored (03), pending (07)
- Чтение 10 PID'ов (0105..0107)
- Весь вывод на экран в реальном времени
Интерфейс: зелёная кнопка «🧪 ТЕСТ», версия `v0.2.0-test`, URL по умолчанию `10.47.183.102:35000`.
### 09:25 — Ошибка 7: Мусор в командах
Mock получил `01070 ATZ` вместо `ATZ`. Причина: клиент слал `\r`, а mock использовал `readline()` (ждёт `\n`). В буфере накопился мусор.
**Исправление в клиенте:** `cmd + "\r\n"` вместо `cmd + "\r"`.
**Исправление в mock:** полная переделка чтения — побайтовый разбор:
```python
# Было: self.rfile.readline()
# Стало: читаем по 1 байту, \r и \n — разделители
ch = self.rfile.read(1)
if ch in (b'\n', b'\r'):
# обработать накопленный буфер
else:
buf += ch
```
### 09:30 — 100% успешный тест
Mock лог (чистый!):
```
🔌 Подключение
📥 ATZ → ELM327 v1.5
📥 ATE0 → OK
📥 ATL0 → OK
📥 ATSP0 → OK
📥 ATH1 → OK
📥 0902 → VIN: WVWZZZ1KZAW123456
📥 03 → DTC: P0301, P0303
📥 07 → DTC: none
📥 0105..07 → 10 PID'ов
🔌 Отключение
```
18 команд — 18 ответов. Ноль мусора.
---
## Архитектура (текущая)
```
┌─────────────────────────────────────────────────────┐
│ ТЕСТОВЫЙ РЕЖИМ (работает на 100%) │
│ │
│ Телефон (Android) Ноутбук (WSL) │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ TestService │──TCP──→ │ mock_elm327.py │ │
│ │ │←──TCP── │ :35000 │ │
│ │ ATZ→ATEx→ │ │ │ │
│ │ 0902→03/07 │ │ Фейковые данные: │ │
│ │ PID'ы │ │ VIN, DTC, PID │ │
│ └──────────────┘ └──────────────────┘ │
│ │
├─────────────────────────────────────────────────────┤
│ РАБОЧИЙ РЕЖИМ (клиент готов, сервер частично) │
│ │
│ Телефон Ноутбук │
│ ┌──────────────┐ ┌──────────────────┐ │
│ │ElmForwardSvc │─HTTP→ │ Flask :5005 │ │
│ │ (транспорт) │←─JSON─ │ raw_endpoint.py │ │
│ │ │ │ ↓ стейт-машина │ │
│ │ BT/TCP → │ │ ↓ парсер │ │
│ │ ELM327/mock │ │ ↓ LLM (DeepSeek)│ │
│ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
```
---
## Все ошибки и решения
| # | Ошибка | Причина | Решение |
|---|--------|---------|---------|
| 1 | Телефон не слал команды | server_url = obdai.ru (интернет) | Авто-вывод `http://host:5005` |
| 2 | CI build fail | Gradle 9.5 ≠ AGP 8.2 | Закрепить Gradle 8.5 |
| 3 | Коммиты в master | Не переключил ветку | Перенос в relay-only, откат master |
| 4 | Server down: cleartext | Android блокирует HTTP | `usesCleartextTraffic="true"` |
| 5 | APK 404 на сервере | Flask static path | `/static/app-debug.apk` |
| 6 | Не устанавливается поверх | Разные debug-ключи | Фиксированный keystore в репо |
| 7 | Мусор `01070 ATZ` | `\r` vs `\r\n` + readline() | Побайтовое чтение в mock + `\r\n` |
---
## Ключевые файлы
### Android (ветка relay-only)
| Файл | Назначение |
|------|-----------|
| `TestService.kt` | **ТЕСТОВЫЙ** — сам гонит протокол, без сервера |
| `ElmForwardService.kt` | **РАБОЧИЙ** — транспорт BT/TCP ↔ HTTP |
| `MainActivity.kt` | UI: кнопки ТЕСТ и Диагностировать |
| `debug.keystore` | Фиксированный ключ подписи (пароль `android`) |
| `build-apk.yml` | CI: Gradle 8.5, сборка debug APK |
### Сервер (ветка master)
| Файл | Назначение |
|------|-----------|
| `tools/mock_elm327.py` | Эмулятор ELM327 на TCP :35000 |
| `web/app.py` | Flask сервер :5005 |
| `web/raw_endpoint.py` | Стейт-машина: парсинг, сессии, LLM |
| `web/templates/index.html` | Страница загрузки APK |
---
## Команды для запуска
```bash
# Ноутбук — терминал 1: mock
python tools/mock_elm327.py
# Ноутбук — терминал 2: сервер
python web/app.py
# Телефон: открыть http://10.47.183.102:5005 → скачать APK → кнопка ТЕСТ
```
## Версии APK
| Версия | Статус | Ключ |
|--------|--------|------|
| 0.1.0 | Устарела | Случайный |
| 0.2.0-test | На телефоне | Случайный |
| 0.5.0-test | В CI | Фиксированный |
---
## Что дальше (рабочая версия)
1. Дописать `raw_endpoint.py` — надёжный парсинг + сохранение в БД
2. Настроить `DEEPSEEK_API_KEY` для LLM
3. Протестировать цепочку: телефон → сервер → LLM → диагноз
4. Подключить к реальному ELM327 в машине (Bluetooth вместо mock)