Nouvelle page
# 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 `