# Dashboard Observatoire Allsky GEVEX — Documentation complète
**Auteur :** Frédéric Tapissier — Gîte AVEX / GEVEX, Vimoutiers, Normandie
**Site :** https://avex-asso.org/ftapissier/
**Dashboard live :** http://86.241.95.110:8888/
**Version :** Mai 2026
---
## Table des matières
1. [Présentation du projet](#1-présentation-du-projet)
2. [Matériel et logiciels utilisés](#2-matériel-et-logiciels-utilisés)
3. [Architecture générale](#3-architecture-générale)
4. [Le dashboard HTML](#4-le-dashboard-html)
5. [L'analyseur Python allsky_analyzer.py](#5-lanalyseur-python-allsky_analyzerpy)
6. [Analyse de la couverture nuageuse](#6-analyse-de-la-couverture-nuageuse)
7. [Comptage des étoiles et calibration](#7-comptage-des-étoiles-et-calibration)
8. [Intégration OpenWeatherMap](#8-intégration-openweathermap)
9. [Correction de la Lune](#9-correction-de-la-lune)
10. [Intégration NINA via ninaAPI](#10-intégration-nina-via-ninaapi)
11. [Publication sur serveur web Infomaniak](#11-publication-sur-serveur-web-infomaniak)
12. [Historique multi-nuits](#12-historique-multi-nuits)
13. [Démarrage automatique Windows](#13-démarrage-automatique-windows)
14. [Structure des fichiers](#14-structure-des-fichiers)
15. [Dépannage](#15-dépannage)
16. [Évolutions possibles](#16-évolutions-possibles)
---
## 1. Présentation du projet
### Objectif
Le GEVEX est un gîte astronomique situé à Vimoutiers en Normandie, équipé d'une caméra allsky ASI178 pilotée par le logiciel AllSkEye. L'objectif de ce projet est de construire un **dashboard d'observatoire complet**, accessible depuis n'importe quel appareil (PC, téléphone, tablette), affichant en temps réel :
- L'image allsky en direct
- L'analyse automatique du ciel (couverture nuageuse, comptage d'étoiles)
- L'état du séquenceur astronomique NINA
- Les données météo (OWM + Météo-France + carte interactive)
- Les flux des caméras de surveillance
- L'historique des nuits d'observation
### Point de départ
AllSkEye génère une page HTML de statut (`allskeyestatus.html`) servie sur le réseau local (port 8888). Cette page existait mais était basique. Le projet consiste à l'enrichir considérablement tout en restant compatible avec le mécanisme de mise à jour d'AllSkEye.
> 📸 *[Capture : page AllSkEye originale]*
### Résultat final
> 📸 *[Capture : dashboard complet GEVEX]*
---
## 2. Matériel et logiciels utilisés
### Matériel
| Élément | Détail |
|---|---|
| Caméra allsky | ZWO ASI178 couleur, objectif fisheye |
| PC observatoire | Windows 11, héberge AllSkEye |
| PC télescope | Windows, héberge NINA (réseau local séparé) |
| Réseau | IP publique fixe `86.241.95.110` |
| Hébergement web | Infomaniak (`avex-asso.org`) |
### Logiciels
| Logiciel | Rôle |
|---|---|
| **AllSkEye** | Pilotage caméra allsky, génération JPG, serveur web port 8888 |
| **NINA** | Séquenceur d'acquisition astronomique |
| **ninaAPI** (plugin) | API REST pour NINA (v2) |
| **Python 3.14** | Analyseur d'images, proxy, serveur HTTP |
| **FileZilla** | Client FTP pour uploads Infomaniak |
### Bibliothèques Python
```bash
pip install opencv-python numpy matplotlib watchdog ephem pillow
```
| Bibliothèque | Usage |
|---|---|
| `opencv-python` | Analyse d'images allsky |
| `numpy` | Calculs matriciels sur les pixels |
| `matplotlib` | Génération des graphiques PNG |
| `watchdog` | Surveillance du dossier images en temps réel |
| `ephem` | Calcul position Soleil, Lune, crépuscules |
| `pillow` | Lecture du masque PNG |
---
## 3. Architecture générale
```
┌─────────────────────────────────────────────────────┐
│ PC OBSERVATOIRE (86.241.95.110) │
│ │
│ AllSkEye ──► D:\AllSkEye\ImageConversions\*.jpg │
│ │ │
│ ▼ │
│ allsky_analyzer.py (Python, tâche planifiée admin) │
│ ├── Analyse image (OpenCV) │
│ ├── Appel OWM (météo référence) │
│ ├── Calcul Soleil/Lune (ephem) │
│ ├── Génère sky_status.json + PNG graphiques │
│ ├── Poll NINA API ──► nina_status.json │
│ ├── Upload FTP ──► Infomaniak │
│ └── Serveur HTTP :8890 (proxy NINA + données) │
│ │
│ AllSkEye web server :8888 │
│ └── allskeyestatus.html (dashboard) │
└─────────────────────────────────────────────────────┘
│ FTP TLS │ navigateur
▼ ▼
┌──────────────────┐ ┌─────────────────────────────┐
│ Infomaniak │ │ Navigateur (local/internet) │
│ avex-asso.org │ │ │
│ /ftapissier/ │ │ http://86.241.95.110:8888/ │
│ gevex/ │ │ ├── allskeyestatus.html │
│ ├── sky_status │◄───│ └── iframes Infomaniak : │
│ ├── nina_status │ │ ├── sky_status_iframe │
│ ├── history │ │ ├── nina_iframe │
│ └── graphiques │ │ └── history_iframe │
└──────────────────┘ └─────────────────────────────┘
```
### Pourquoi cette architecture ?
La contrainte principale est que le navigateur impose des restrictions **CORS** (Cross-Origin Resource Sharing) : une page chargée depuis `86.241.95.110:8888` ne peut pas faire de requêtes `fetch()` vers d'autres domaines/ports. Les solutions retenues :
- Les **données textuelles** (JSON) sont lues depuis des **iframes** hébergées sur Infomaniak — même domaine = pas de CORS
- Les **images PNG** (graphiques) passent directement car les balises `
` ne sont pas soumises au CORS
- **NINA** (sur un autre PC du réseau local) est proxyfié par le programme Python via un serveur HTTP sur le port 8890
---
## 4. Le dashboard HTML
### Fichier : `allskeyestatus.html`
Ce fichier est le fichier de statut standard d'AllSkEye, enrichi. Il doit rester dans `C:\Program Files\AllSkEye_0.9.29.2\` et conserve les **placeholders AllSkEye** :
- `{ImagesPlaceholderDoNotChange}` — image allsky injectée par AllSkEye
- `{StatusPlaceholderDoNotChange}` — statut système AllSkEye
- `{RefreshTimePlaceholderDoNotChange}` — timer de refresh AllSkEye
### Layout du dashboard (grille 3 colonnes)
```
┌─────────────┬─────────────┬─────────────┐ Ligne 1
│ Caméra │ Analyse │ Vue │
│ Allsky │ Ciel GEVEX │ observatoire│
│ + Statut │ (iframe + │ gevex2.jpg │
│ │ graphiques) │ │
├─────────────┴─────────────┼─────────────┤ Ligne 2
│ NINA — Séquenceur │ Caméra │
│ (iframe Infomaniak) │ télescope │
├─────────────┬─────────────┴─────────────┤ Ligne 3
│ Satellite │ Carte météo │ Caméra │
│ animé │ interactive │ observ. 2 │
├─────────────┴──────────────┴────────────┤ Ligne 4
│ Historique des nuits (pleine largeur) │
└─────────────────────────────────────────┘
```
### Design
- Thème **dark** scientifique/industriel
- Police `Rajdhani` (titres) + `Share Tech Mono` (données)
- Effet scanlines subtil
- Accents colorés : bleu cyan, rose, vert, orange selon les panneaux
- Widget horloge externe (zeitverschiebung.net) — heure locale + date
- Widget phase lunaire (moonphase.guide)
### Rafraîchissement
- **Toutes les 30 secondes** : cache-bust sur les images statiques (allsky, satellite, gevex2.jpg, graphiques PNG) via `?t=timestamp`
- **Toutes les 2 minutes** : rechargement complet de la page via `location.href` avec paramètre `?nocache=timestamp`
- **Iframes Infomaniak** : se rechargent indépendamment via ``
> 📸 *[Capture : dashboard complet annoté]*
---
## 5. L'analyseur Python allsky_analyzer.py
### Rôle
Programme Python tournant en permanence sur le PC observatoire. Il surveille le dossier images d'AllSkEye et traite chaque nouvelle image dès qu'elle apparaît.
### Configuration (début du fichier)
```python
CONFIG = {
'images_dir': r'D:\AllSkEye\ImageConversions',
'mask_path': r'D:\AllSkEye\masque_etoile_pour_Claude.png',
'output_dir': r'D:\AllSkEye',
'lat': '48.873',
'lon': '0.219',
'elevation': 100,
'owm_key': 'VOTRE_CLE_API_OWM',
'owm_lat': 48.7167, # Fresnay-les-Samon
'owm_lon': 0.3833,
'ftp_host': 'dzcd.ftp.infomaniak.com',
'ftp_user': 'dzcd_XXXXX',
'ftp_pass': 'VOTRE_MOT_DE_PASSE',
'ftp_dir': '/web/ftapissier/gevex',
'ftp_tls': True,
'ftp_enabled': True,
}
```
### Pipeline de traitement par image
```
Nouvelle image JPG détectée (watchdog)
│
├── Attente 1.5s (écriture complète du fichier)
├── Lecture image OpenCV
├── Extraction horodatage depuis nom de fichier
│ └── Format AllSkEye : -YYYY-MM-DDTHH-MM-SS-mmm.jpg
│ (heure LOCALE France → conversion UTC pour ephem)
├── Calcul contexte astronomique (ephem)
│ ├── Altitude Soleil → mode JOUR/CRÉPUSCULE/NUIT
│ ├── Position Lune → masque dynamique
│ └── Phase lunaire → facteur de correction étoiles
├── Application masque paysage + masque Lune dynamique
├── Analyse couverture nuageuse (selon mode)
├── Si NUIT : comptage étoiles + correction OWM/Lune
├── Enregistrement point historique nuit
├── Génération sky_status.json
├── Génération graphiques PNG (2h glissantes)
└── Upload FTP vers Infomaniak (thread séparé)
```
### Nommage des fichiers AllSkEye
AllSkEye nomme ses JPG en heure **locale France** :
```
-2026-05-04T22-30-15-123.jpg
└── 4 mai 2026, 22h30m15s heure locale
```
Le programme convertit automatiquement en UTC pour les calculs astronomiques (ephem travaille toujours en UTC). La conversion tient compte du changement d'heure été/hiver (CEST UTC+2 / CET UTC+1).
---
## 6. Analyse de la couverture nuageuse
### Problématique
La détection de nuages sur une caméra allsky couleur est plus complexe qu'il n'y paraît, pour deux raisons :
1. La méthode varie selon que c'est le jour ou la nuit
2. La présence de la Lune (surtout pleine) peut faire briller les nuages exactement comme un ciel dégagé
### Mode jour : saturation HSV
De jour, les nuages sont **blancs ou gris** (faible saturation), tandis que le ciel dégagé est **bleu** (forte saturation et teinte dans la plage 85°-135° en HSV).
```python
# Ciel bleu dégagé : teinte bleue + saturation > 50
blue_frac = pixels avec H∈[85-135] et S>50
# Nuages : saturation faible (blanc/gris)
cloud_frac = pixels avec S < 60
# Résultat combiné
cloud_pct = 0.5 * cloud_frac * 100 + 0.5 * (1 - blue_frac) * 100
```
**Fusion avec OWM :** La caméra ASI178 produit des images avec une saturation naturellement faible, ce qui peut sous-estimer les nuages. On fusionne donc la détection image (40%) avec la donnée OWM (60%) pour le mode jour :
```python
cloud_pct = 0.4 * cloud_image + 0.6 * owm_clouds
```
### Mode nuit : variance locale + comptage étoiles
La nuit, le ratio B/R et la saturation HSV ne fonctionnent plus (tout est sombre). On utilise deux indicateurs :
**Variance locale par blocs 32×32 pixels :**
- Un ciel dégagé avec des étoiles → variance résiduelle **élevée** (sources ponctuelles brillantes)
- Un ciel couvert homogène → variance résiduelle **faible**
**Ratio étoiles observées / étoiles attendues (méthode principale) :**
```python
cloud_pct = 0.75 * (1 - étoiles_observées/étoiles_attendues) * 100
+ 0.25 * cloud_variance
```
> 📸 *[Capture : graphique couverture nuageuse 2h]*
---
## 7. Comptage des étoiles et calibration
### Méthode de détection
```
Image nuit → Niveaux de gris
│
├── Soustraction du fond lent (GaussianBlur 51×51)
│ └── Élimine le gradient de luminosité (Lune, pollution lumineuse)
├── Seuillage strict (moyenne + 3.5 σ, minimum 12)
├── Détection de contours
└── Filtres de validation :
├── Aire entre 3 et 120 pixels² (élimine bruit et satellites)
└── Circularité > 0.4 (élimine artefacts allongés)
```
### Auto-calibration de star_max_ref
Le programme maintient une référence `star_max_ref` (nombre d'étoiles pour un ciel parfait). Cette valeur s'auto-calibre automatiquement :
- **Conditions requises** : OWM < 10% nuages, visibilité > 8 km, Lune < 20% ou couchée
- **Mécanisme** : si le nombre d'étoiles observées dépasse la référence actuelle (corrigé de la gêne lunaire), la référence est mise à jour et sauvegardée dans `star_max_ref.json`
```python
# Sauvegardé dans D:\AllSkEye\star_max_ref.json
{"star_max_ref": 450, "updated": "2026-05-10T03:15:00"}
```
> 📸 *[Capture : graphique étoiles détectées 2h]*
---
## 8. Intégration OpenWeatherMap
### Pourquoi OWM ?
OWM fournit des données météo en temps réel (toutes les 10 minutes dans le programme) qui servent à **deux usages distincts** :
1. **Calibration de jour** : référence de vérité terrain pour la couverture nuageuse
2. **Calcul d'étoiles attendues la nuit** : transparence atmosphérique théorique
### Clé API
Inscription gratuite sur [openweathermap.org](https://openweathermap.org). Le plan gratuit suffit (60 appels/minute, largement suffisant).
### Calcul de transparence atmosphérique
```python
transp = (1 - nuages_OWM/100) # couverture nuageuse
× (visibilité/10000) # brume/brouillard
× (1 - 0.25 × humidité/100) # vapeur d'eau
× facteur_code_météo # 0.0 si pluie, 0.3 si brume, 1.0 si clair
```
### Localisation
La station météo OWM est choisie proche de l'observatoire mais pas exactement au même endroit (Fresnay-les-Samon est plus représentatif météorologiquement que Vimoutiers pour ce site).
---
## 9. Correction de la Lune
### Problème
La Lune, surtout en phase gibbeuse ou pleine, éclaire les nuages et fausse tous les algorithmes de détection. Une pleine Lune à 45° de hauteur rend un ciel couvert indiscernable d'un ciel dégagé par simple analyse de luminosité.
### Masque dynamique
La position de la Lune est calculée à chaque image avec `ephem`. Un masque circulaire est appliqué autour de sa position projetée sur l'image fisheye :
```python
# Projection fisheye équidistante
r = r_max × (1 - altitude_lune / 90°)
moon_x = cx + r × sin(azimut)
moon_y = cy - r × cos(azimut)
# Rayon du masque proportionnel à la phase et l'altitude
rayon = 80 × (0.5 + phase) × (altitude/90°)
```
### Étoiles attendues corrigées
```python
# Facteur lunaire : réduit les étoiles visibles selon phase et altitude
moon_factor = 1 - (phase × altitude/90° × 0.75)
étoiles_attendues = star_max_ref × transparence_OWM × moon_factor
```
---
## 10. Intégration NINA via ninaAPI
### Prérequis
- Plugin **ninaAPI** installé dans NINA (disponible sur le gestionnaire de plugins NINA)
- API activée dans les paramètres du plugin (port 1888 par défaut)
- Les deux PC (observatoire et télescope) sur le même réseau local
### Endpoints utilisés (API v2)
| Endpoint | Données récupérées |
|---|---|
| `GET /v2/api/equipment/camera/info` | Température capteur, gain, setpoint |
| `GET /v2/api/equipment/mount/info` | RA, DEC, Alt, Az, statut parking |
| `GET /v2/api/equipment/guider/info` | RMS guidage RA et DEC |
| `GET /v2/api/equipment/focuser/info` | Position, température |
| `GET /v2/api/sequence/json` | Nom séquence, statut, progression |
### Contournement CORS
NINA tourne sur `192.168.1.21:1888`, le dashboard sur `86.241.95.110:8888`. Le navigateur bloque les requêtes cross-origin entre ces deux origines. Solution : le programme Python **proxyfie** les appels NINA via son serveur HTTP interne (port 8890) qui ajoute les headers CORS appropriés.
```
Navigateur → GET http://86.241.95.110:8890/nina/camera
│
Python :8890 → GET http://192.168.1.21:1888/v2/api/equipment/camera/info
│
└── Réponse + header Access-Control-Allow-Origin: *
```
**Depuis internet** : le port 8890 n'est pas exposé sur internet (sécurité). Les données NINA sont uploadées toutes les 30 secondes sur Infomaniak via FTP, et affichées via une iframe `nina_iframe.html` hébergée sur le même domaine.
### Affichage
> 📸 *[Capture : panneau NINA avec équipements connectés]*
---
## 11. Publication sur serveur web Infomaniak
### Pourquoi Infomaniak ?
Le serveur web d'AllSkEye (port 8888) ne sert pas les fichiers statiques librement — il génère dynamiquement sa page HTML. Les fichiers JSON et PNG générés par le programme ne peuvent donc pas être servis directement. Infomaniak (hébergeur du site `avex-asso.org`) sert de relais public accessible depuis internet.
### Structure des fichiers sur Infomaniak
```
/web/ftapissier/gevex/
├── .htaccess ← Headers CORS (uploadé au démarrage)
├── sky_status.json ← Données ciel temps réel
├── sky_cloud_chart.png ← Graphique nuages 2h
├── sky_star_chart.png ← Graphique étoiles 2h
├── sky_status_iframe.html ← Page HTML affichant sky_status.json
├── nina_status.json ← État NINA temps réel
├── nina_iframe.html ← Page HTML affichant nina_status.json
├── history_status.json ← Historique 30 dernières nuits
├── sky_history_chart.png ← Graphique historique
└── history_iframe.html ← Page HTML affichant l'historique
```
### Upload automatique FTP TLS
Le programme uploade automatiquement après chaque analyse (~1 min) :
- Upload principal : 6 fichiers (status JSON + PNG)
- Upload NINA : `nina_status.json` toutes les 30 secondes
- Upload historique : à chaque clôture de nuit (aube)
- Upload `.htaccess` : une seule fois au démarrage
### .htaccess CORS
```apache
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "GET, OPTIONS"
Header always set Cache-Control "no-cache, no-store, must-revalidate"
```
### Principe des iframes
Les pages `*_iframe.html` sont servies depuis `avex-asso.org` et font leurs `fetch()` vers des fichiers JSON **sur le même domaine** — pas de CORS. Ces pages sont ensuite intégrées en `