# Plan de test — dwarfctl sur télescope réel

## Conditions de test
- **Télescope** : DWARF II, firmware 2.1.6, mode AP (hotspot WiFi)
- **IP** : `192.168.88.1`
- **Réseau** : machine connectée au hotspot du télescope
- **Environnement** : intérieur, journée (pas d'accès au ciel/étoiles)

## Découvertes protocolaires importantes

### Modèle de réponse du télescope

Le télescope utilise **type=3 (REPLY)** pour les réponses directes, PAS type=1 (RESPONSE). De plus, certaines commandes ne reçoivent AUCUNE réponse directe — seulement des notifications (type=2).

| Pattern | Commandes | Comportement |
|---------|-----------|--------------|
| **Reply type=3 (même cmd)** | `photo` (10002), `focus auto` (15000), `focus step` (15001), `state` (16405) | Réponse directe avec le même cmd → request-response |
| **Notification uniquement** | `rgb-on/off` (13500/13501), `camera open/close` (10000/10001), `camera params` (10036) | Pas de reply → fire-and-forget + vérifier via `state` |
| **Fire-and-forget natif** | `motor slew` (14006), `motor stop` (14002) | Pas d'ack attendu |

### Implémentation dans dwarfctl
- `send()` : attend un type=3 reply (10s timeout) — pour photo, focus, state
- `sendNotify()` : fire-and-forget — pour RGB, camera open/close, motor slew
- Flag `--debug` / `-d` : loggue tout le trafic WebSocket (cmd, module, type, taille data)

## Tests par groupe

### ✅ 1. Connexion (PASS)

| Test | Commande | Résultat |
|------|----------|---------|
| Ping | `ping 192.168.88.1` | ✅ 36ms |
| Port 9900 (WebSocket) | `/dev/tcp/192.168.88.1/9900` | ✅ OPEN |
| Port 80 (RTSP) | `/dev/tcp/192.168.88.1/80` | ✅ OPEN |
| WebSocket handshake | `dwarfctl --ip 192.168.88.1 state` | ✅ connexion établie |

### ✅ 2. État du device (PASS)

```bash
dwarfctl --ip 192.168.88.1 state
```
Réponse complète : shooting_mode, cameras (résolution, FoV), focus position (594), batterie (100%), stockage (38/52 GB), température CMOS, modes disponibles.

### ✅ 3. Caméra Tele (PASS)

| Test | Commande | Debug | Statut |
|------|----------|-------|--------|
| Open camera | `camera open --cam tele` | fire-and-forget | ✅ |
| Photo | `camera photo --cam tele` | reply cmd=10002 type=3 data=11B | ✅ photo capturée |
| Focus step out | `focus step 1` | reply cmd=15001 type=3 data=0B | ✅ |

### ✅ 4. Moteurs / Slew (PASS)

Tests systématiques effectués — tous les mouvements confirmés visuellement sur le télescope.

#### 4.1 Directions cardinales (joystick cmd 14006)

| Direction | Angle | Length | Durée | Résultat |
|-----------|-------|--------|-------|----------|
| HAUT | 0° | 0.3 | 2s | ✅ mouvement observé |
| DROITE | 90° | 0.3 | 2s | ✅ mouvement observé |
| BAS | 180° | 0.3 | 2s | ✅ mouvement observé |
| GAUCHE | 270° | 0.3 | 2s | ✅ mouvement observé |

**Système de coordonnées joystick** : angle en degrés (polaire), 0°=haut, 90°=droite, 180°=bas, 270°=gauche. Length = vitesse normalisée (0.0 à 1.0).

#### 4.2 Vitesses variables

| Vitesse | Length | Résultat |
|---------|--------|----------|
| Lente | 0.1 | ✅ mouvement visible et lent |
| Moyenne | 0.5 | ✅ mouvement net |
| Rapide | 0.9 | ✅ mouvement rapide |

#### 4.3 Diagonales

| Direction | Angle | Résultat |
|-----------|-------|----------|
| HAUT-DROITE | 45° | ✅ mouvement diagonal |
| BAS-GAUCHE | 225° | ✅ mouvement diagonal |

Le joystick est bien polaire — 360° de liberté, pas seulement 4 directions.

#### 4.4 Arrêt d'urgence

| Test | Commande | Résultat |
|------|----------|----------|
| Stop pendant slew | `motor stop 0` (cmd 14002) | ✅ arrêt immédiat |
| Stop motor 1 | `motor stop 1` | ✅ |

Le `motor stop` interrompt un slew en cours instantanément.

#### ⚠️ 4.4b DANGER : Butée matérielle

**Test réel** : le slew à 90° (descente) continu a fait atteindre la butée basse mécanique. Le firmware a **coupé immédiatement le réseau** (perte totale : ping KO, port 9900 fermé). Redémarrage physique nécessaire.

**Leçon** : le slew joystick n'a PAS de soft-limit côté firmware. Il faut impérativement limiter la durée des slew altitude (90°/270°) à de courts intervalles (< 2s) et observer visuellement.

**Comportement après butée** : l'axe altitude s'est verrouillé — le firmware refuse tout mouvement de slew sur cet axe après `LIMIT_POSITION_HIT`. L'axe azimut continue de fonctionner. Le déverrouillage nécessite un home/reset (calibration nocturne uniquement). Le `motor reset` (cmd 14003) provoque un reboot et ne déverrouille pas l'axe.

**Découverte clé — Home par butée** : slewing en 270° (descente) à vitesse modérée jusqu'à ce que le limit switch se déclenche provoque un **home automatique du firmware**. Le télescope revient à la position 0 physique (optique vers le sol). C'est la méthode d'initialisation manuelle des moteurs — pas besoin de `motor reset` (qui ne fait que rebooter).

Procédure d'initialisation manuelle :
1. `motor slew 90 0.4` pendant 5s — monter ~45° pour décoller de la butée basse
2. `motor init down 40` — slew 270° jusqu'à la butée basse → home automatique
3. Position 0 = optique vers le sol
4. L'axe altitude reste **pleinement opérationnel** après le home
5. L'API reste en `NEED_RESET` tant que la calibration astro n'est pas faite (le home mécanique ne donne pas la position absolue, seulement la position physique)

**IMPORTANT** : si le télescope démarre en butée basse, il faut d'abord monter (`motor slew 90`) avant de lancer `motor init down`, sinon le slew est ignoré car déjà en butée.

**Commande `motor init`** :
- `motor init up [timeout]` — home via butée haute (90°)
- `motor init down [timeout]` — home via butée basse (270°, recommandé)
- Le firmware coupe la connexion WS quand le home se déclenche (normal)
- Après reconnexion, l'axe est opérationnel

#### 4.5 Système de coordonnées joystick découvert (config alt-az, trépied vertical)

Tests en condition réelle, télescope posé verticalement sur trépied (pas en mode équatorial) :

| Angle | Axe | Direction observée |
|-------|-----|-------------------|
| 0° | Azimut (horizontal) | Rotation horaire vue de dessus |
| 90° | Altitude | **Montée** (vers le ciel) |
| 180° | Azimut | Rotation anti-horaire (opposé de 0°) |
| 270° | Altitude | **Descente** (vers le sol) |

**IMPORTANT** : ces directions sont validées pour une config alt-az simple (télescope vertical sur trépied). En mode équatorial (après EQ calibration), le mapping peut changer car les moteurs changent de référentiel.

#### 4.5b Butées mécaniques — test complet des 4 butées

Les 4 butées mécaniques ont été testées et sont **toutes gérées safely** par le firmware (arrêt au limit switch, pas de crash, pas de verrouillage d'axe) :

| Butée | Angle slew | Comportement | Reboot ? | Axe verrouillé ? |
|-------|-----------|-------------|----------|-----------------|
| Altitude basse (sol) | 270° continu | ✅ Arrêt + home auto à 0 | Non | Non |
| Altitude haute (ciel) | 90° continu | ✅ Arrêt au contact | Non | Non |
| Azimut horaire (↻) | 0° continu | ✅ Arrêt au contact | Non | Non |
| Azimut anti-horaire (↺) | 180° continu | ✅ Arrêt au contact | Non | Non |

**Conclusion** : les butées mécaniques sont sûres. Le firmware arrête le moteur au contact du limit switch sans crash ni verrouillage. L'incident précédent de verrouillage d'axe était dû au `motor reset` (cmd 14003), pas aux butées.

#### 4.5 Motor run/goto (cmd 14000)

| Test | Commande | Résultat |
|------|----------|----------|
| Run to position | `motor goto 0 10 5` | ✅ fire-and-forget envoyé |

`ReqMotorRunTo` : moteur ID, position cible, vitesse. Fire-and-forget (pas de ack type=3).

#### 4.6 Motor get-position (cmd 14001 — inféré, confirmé fonctionnel)

| Test | Commande | Résultat |
|------|----------|----------|
| Position moteur 0 | `motor position 0` | ✅ reply type=3, `code=-14520` (NEED_RESET) |
| Position moteur 1 | `motor position 1` | ✅ reply type=3, `code=-14520` (NEED_RESET) |

L'ID 14001 n'est pas dans le WsCmd enum de l'APK mais fonctionne — le firmware répond avec `ResMotorPosition{id, code, position}`. Le `code=-14520` correspond à `CODE_STEP_MOTOR_NEED_RESET` : les moteurs n'ont pas été homed.

#### 4.7 Motor reset (cmd 14003 — ⚠️ DANGEREUX, DÉSACTIVÉ)

| Test | Commande | Résultat |
|------|----------|----------|
| Reset moteur 0 | `motor reset 0 0` | ⚠️ **A causé le reboot du télescope** |

L'ID 14003 est inféré (gap entre STOP=14002 et JOYSTICK=14006). L'envoi de `ReqMotorReset{id, direction}` a provoqué un **reboot immédiat du télescope**. La commande est désormais **désactivée** dans le code et retourne une erreur explicite.

**Leçon** : ne jamais envoyer de commandes inférées (IDs non confirmés dans le WsCmd enum de l'APK) sans validation préalable. L'app Android n'utilise que les commandes joystick (14006-14009), run (14000) et stop (14002). Le reset/home des moteurs est probablement géré côté firmware uniquement, déclenché par la calibration astro ou le panorama.

#### 4.6 Observations sur la lecture de position

- **`focus_position`** (moteur de mise au point) : visible dans `state` → `focus_position:{pos:593}`, reste stable pendant les mouvements de pointage.
- **Position moteurs de pointage (RA/DEC)** : `motion_motor_state_info:{exclusive_state:{}}` reste vide — la position des moteurs alt-az n'est PAS exposée dans `GetDeviceState`.
- Les messages proto `ReqMotorGetPosition` et `ReqMotorReset` existent dans le proto mais ne sont **pas utilisés par l'app Android** (aucune classe de requête correspondante). La lecture de position se fait probablement via les notifications d'attitude (cmd 15295 `CMD_NOTIFY_DEVICE_ATTITUDE`).

### ✅ 5. RGB / Power (PASS après fix fire-and-forget)

| Test | Commande | Avant fix | Après fix |
|------|----------|-----------|-----------|
| RGB off | `power rgb-off` | ❌ timeout (cmd 13501) | ✅ fire-and-forget |
| RGB on | `power rgb-on` | ❌ timeout (cmd 13500) | ✅ fire-and-forget |

Vérification : `rgb_state:{}` (éteint) vs `rgb_state:{state:1}` (allumé) dans `state`.

### ✅ 6. Focus (PASS)

| Test | Commande | Debug | Statut |
|------|----------|-------|--------|
| Autofocus | `focus auto` | notifications + reply cmd=15000 type=3 | ✅ |
| Step out | `focus step 1` | reply cmd=15001 type=3 | ✅ |

### ✅ 7. System (NON TESTÉ — nécessite validation)

| Test | Commande | Notes |
|------|----------|-------|
| Sync time | `system sync-time` | À tester |
| Set location | `system set-location 48.86 2.35 35` | À tester |

### ❌ 8. Astrophotographie (NON TESTABLE — jour/intérieur)

Ces commandes nécessitent un ciel étoilé nocturne :

| Test | Raison |
|------|--------|
| `astro calibrate start` | Nécessite des étoiles visibles |
| `astro goto-dso` | Nécessite le ciel |
| `astro stack start` | Nécessite des étoiles |
| `astro eq-solve start` | Nécessite des étoiles |
| `track start` | Nécessite une cible mobile |

### ❌ 9. Camera params (KNOWN ISSUE)

`camera params --cam tele` (cmd 10036) : le télescope ne renvoie pas de reply.
Les paramètres sont toutefois visibles dans la réponse `state` (résolution, FoV).
Les paramètres ajustables (exposure, gain) nécessiteraient une commande GET qui ne répond pas en type=3.

## Bugs corrigés pendant les tests

1. **`readLoop` ne dispatchait pas type=3 correctement** → corrigé : tous les types sont dispatchés vers `dispatchResponse` + `dispatchNotification`
2. **Commandes RGB/camera open attendaient un reply inexistant** → converties en fire-and-forget (`sendNotify`)
3. **Motor stop/goto attendaient un reply** → convertis en fire-and-forget
4. **Pas de logging** → ajout du flag `--debug` / `-d`

## Tests unitaires

```
ok  github.com/antitbone/dwarfctl/internal/transport  0.003s
ok  github.com/antitbone/dwarfctl/internal/api        0.003s
```
57 tests, tous PASS.
