Vérif terrain sur pro100 : à 100 kHz le CCS811 renvoie des valeurs corrompues
0x8000 (32768) par clock-stretching, et le modèle oneshot-reset-toutes-les-10s
ne donne que le 1er échantillon post-init (garbage). Refonte :
- CCS811/daemon.py: service long-running (Type=simple, Restart=always). Init 1x,
boucle lecture/écriture 10s, filtre eCO2 dans [400,8192], re-init auto sur
erreurs I2C répétées. Remplace write_data.py (supprimé).
- CCS811/get_data.py: lit la dernière ligne data_CCS811 au lieu du capteur
(évite la collision I2C avec le daemon -> corruption observée).
- setup_services.sh: service daemon + self-heal suppression de l'ancien .timer;
activation hors boucle timers.
- launcher.php: .timer -> .service (map statut + allowedServices x2).
- update_firmware.sh: redémarre le daemon à l'OTA.
- doc: README (archi daemon + I2C 10kHz confirmé requis), CLAUDE.md, changelog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Découvert en vérif SSH sur nebuleair-pro100 : le timer CCS811 échouait en
ModuleNotFoundError car l'OTA fait git pull mais ne réinstallait jamais les
dépendances pip (installation_part1.sh ne tourne qu'à l'install neuve).
- requirements.txt: source unique de vérité des deps Python
- installation_part1.sh: install via requirements.txt (chemin relatif au script,
le repo n'est pas encore cloné dans /var/www à cette étape)
- update_firmware.sh: nouvelle étape 2a, pip install -r requirements.txt
(idempotent) -> les capteurs déjà déployés récupèrent les libs manquantes à l'OTA
- CCS811/write_data.py + get_data.py: skip des lectures eCO2 < 400 ppm
(échantillon 0/0 parasite juste après init du driver, plancher physique = 400)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouveau capteur de qualité d'air CCS811 sur le bus I2C, calqué sur le
pattern S88 (local-only, pas encore dans le payload de transmission).
- CCS811/get_data.py (lecture live) + write_data.py (timer 10s, self-heal table)
- table data_CCS811 (timestamp, eCO2, TVOC) dans create_db.py
- config CCS811 (bool) + CCS811_address (0x5A/0x5B, défaut 0x5A) dans set_config.py
- service+timer systemd nebuleair-ccs811-data (10s) + ajout boucle d'activation
- admin.html: case d'activation + dropdown adresse I2C
- sensors.html: carte Get Data (TVOC + eCO2)
- database.html + launcher.php: consultation/export/stats data_CCS811
- lib adafruit-circuitpython-ccs811 dans installation_part1.sh
- CCS811/README.md: câblage, adresses, warning clock-stretching I2C sur Pi
- CLAUDE.md + changelog mis à jour
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Protège contre le cas où l'OTA n'appelle pas create_db.py (problème
bash classique: le script d'OTA est chargé en mémoire au lancement,
le git pull du step 1 met à jour le fichier sur disque mais bash
continue d'exécuter l'ancienne version).
Le script crée la table data_S88 lui-même au démarrage. Schéma dupliqué
de create_db.py — à garder synchro. Pattern à appliquer aux futurs
capteurs pour éviter cette classe de bug.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Les MAJ OTA ne lancent pas create_db.py, donc toute nouvelle table
ajoutée par une release (ex: data_S88) reste inexistante en base.
Les timers tournent, le script s'exécute, mais l'INSERT échoue
silencieusement avec 'no such table' — capturé par le try/except,
exit 0, systemd voit success.
Symptôme observé sur les capteurs avec S88 activé: 'Get Data' marche
(live read), mais 'Mesures CO2 (Senseair S88)' montre 'Aucune donnée
disponible dans cette table'.
Fix: create_db.py est appelé en step 2 juste avant set_config.py
dans les deux scripts d'update (git pull et upload fichier).
Idempotent (CREATE IF NOT EXISTS + ALTER ADD COLUMN in try/except).
Self-bootstrap: après cette OTA, tous les capteurs auront toutes
les tables, y compris celles introduites dans des releases passées.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refonte des boutons 'Consulter la base de donnée': ils ouvrent
désormais un grand modal Bootstrap (modal-xl scrollable) avec
pagination 20 lignes/page (Précédent/Suivant + indicateur de plage).
Le dropdown 'Nombre de mesures' est supprimé.
Ajout des boutons Senseair S88 dans les 3 cartes pointant sur
data_S88, et renommage du bouton MH-Z19 pour le distinguer.
Backend: sqlite/read.py accepte un OFFSET optionnel (3e argument,
défaut 0) et launcher.php endpoint table_mesure transmet ?offset=N.
Rétrocompatible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le sélecteur affiche 'port NPM1 (/dev/ttyAMA5)' au lieu de juste
'/dev/ttyAMA5', pour matcher le silkscreen de la PCB et éviter
les erreurs de branchement. ttyAMA0 et ttyAMA2 (SARA) sont retirés.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ajoute un dropdown ttyAMA0/2/3/4/5 sous la checkbox 'Send CO2
sensor data (Senseair S88)' qui modifie S88_port en base.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
read_co2() lit IR1..IR4 en une trame (status + CO2) à 9600 8N1,
adresse 0xFE 'any address', avec vérification CRC16-Modbus et rejet
de la mesure si status non-nul (warm-up ou erreur).
CRC requête/réponse validés contre les exemples du datasheet TDE14367.
Doc protocole consolidée dans S88/README.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Table data_S88, flag config S88 + port configurable S88_port
(default /dev/ttyAMA5), service/timer systemd 10s, carte
sensors.html, endpoint launcher.php, toggle admin.html.
read_co2() est un stub NotImplementedError en attente du datasheet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Quand AT+CSQ ne repond pas (hoquet serie temporaire), retente le
AT+CSQ jusqu'a 3x (0.5s d'intervalle) avant d'escalader. Si le modem
repond, le flux normal reprend sans reboot -> evite les coupures
d'alim GPIO et l'usure du modem pour une absence de reponse ponctuelle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ajoute un bouton à côté de 'Run Self Test' (4 pages) qui lance
uniquement le check sous-tension dans un petit modal dédié, sans
dérouler tout le Self Test. Réutilise l'endpoint type=throttled.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ajoute un test 'Power Supply' au Self Test pour détecter une
sous-tension du Pi (cause fréquente de capteurs USB instables,
corruptions SD, reboots). Endpoint launcher.php?type=throttled
+ script power/get_throttled.py (lancé via sudo python3, déjà
whitelisté — pas de modif sudoers). Affiché en tête des résultats
et dans le rapport copiable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le NSRT MK4 (USB CDC-ACM) se ré-énumère ~1s par cycle sur le pro150
(dmesg: USB disconnect/reconnect en boucle), ce qui faisait échouer
l'ouverture de /dev/ttyACM0 et enregistrait des points isolés 0.0
"Déconnecté". On retente jusqu'à 3 fois (1s d'intervalle) avant de
conclure à une déconnexion. Cause racine matérielle (alim/câble USB)
à traiter en parallèle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fix Envea Gas Sensors: scan physique via detect_envea_device (read_ref.py)
sur ttyAMA3/4/5 au lieu de juste vérifier envea_sondes_table.connected=1.
L'ancien check disait Passed même sans sonde branchée car read_value_v2.py -d
imprime un en-tête de debug non vide et utilise "Failed" pas "error".
- Ajout Firmware Version dans les logs et le rapport (via firmware_version
déjà retourné par get_config_sqlite, pas d'AJAX supplémentaire).
- Renommage titre modal "Modem Self Test" -> "Self Test" (couvre aussi
capteurs et RTC, pas uniquement le modem).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Au lieu d'afficher l'erreur Apache "404 Not Found" qui faisait croire
à un bug, le modal indique maintenant que le log n'existe que lorsqu'une
tentative de connexion WiFi a déjà eu lieu. Inclut aussi un échappement
HTML basique du contenu et limite l'affichage aux 1000 dernières lignes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ajoute un bouton 'WiFi connect logs' dans logs.html qui ouvre un modal
avec le contenu de logs/wifi_connect.log (créé en v1.9.3). Permet de
diagnostiquer une tentative de connexion WiFi sans passer en SSH.
Le contenu est chargé à l'ouverture du modal (pas au chargement de la
page) pour éviter de saturer la limite de 6 connexions du navigateur
sur la page Journal.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bugs corrigés:
- launcher.php passait SSID/PASS au shell sans escapeshellarg(): un
mot de passe avec $/&/;/espace cassait silencieusement la commande
avant que nmcli ne soit appelé. Cause probable des retours clients
"ça bloque au cliquer sur Se connecter".
- wifi.html n'encodait pas SSID/PASS dans l'URL: caractères &/+/=
corrompaient la requête.
Observabilité:
- Nouveau fichier logs/wifi_connect.log avec timestamps stricts
- launcher.php log la requête entrante (IP, longueurs SSID/PASS)
- connexion.sh: fonction log_wc(), snapshots NM avant/après,
capture stdout+stderr nmcli, code retour explicite, fallback SSID
dérivé du serial si deviceName indisponible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- boot_hotspot.sh: busy timeout 5s sur les requêtes SQLite
- boot_hotspot.sh: SSID fallback nebuleair-pro-<serial> si deviceName vide
- Corrige le cas où le hotspot ne démarrait pas quand la DB était
lockée par les timers systemd au boot (SSID vide → nmcli refuse)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- admin.html: nouveau card 'Reseau Tailscale' avec statut connecte/deconnecte,
IP tailnet, hostname, serveur Headscale et bouton Actualiser
- admin.html: bloc deroulant pour consulter les 50 dernieres lignes du log
bootstrap (logs/tailscale_bootstrap.log)
- launcher.php: nouvelles actions get_tailscale_info (status + IP + hostname
via sudo tailscale ip/status) et get_tailscale_log (tail -n 50)
Complete la v1.9.0 avec la visibilite UI necessaire pour valider/diagnostiquer
la connexion Tailscale sur chaque capteur sans avoir a passer en SSH.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- installation_part1.sh: install paquet Tailscale + sudoers /usr/bin/tailscale
- services/tailscale_bootstrap.sh (nouveau): script idempotent d'enrolement au boot
- services/setup_services.sh: service systemd nebuleair-tailscale-bootstrap (one-shot)
- update_firmware.sh: nouvelle etape 3d 'Bootstrap Tailscale' (self-heal install + fetch
authkey depuis data.nebuleair.fr/pro_4G/get_tailscale_key.php + enrolement). Fallback
HTTPS->HTTP en attendant le cert TLS cote serveur.
Permet l'acces SSH distant aux 200 capteurs deployes via le tailnet une fois que leur
client a clique sur 'Update' dans l'admin web. Necessite l'endpoint serveur
get_tailscale_key.php en place sur data.nebuleair.fr (a deployer en parallele cote
AirCarto, auth par deviceID + rate limit + audit log).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ajoute la version firmware sous le nom du capteur dans la sidebar, visible
sur toutes les pages. Permet d'identifier d'un coup d'oeil le chemin de
mise a jour disponible (online git pull vs offline ZIP upload >= v1.4.0).
- launcher.php: get_config_sqlite injecte firmware_version (lu depuis VERSION)
- sidebar.html: ajout d'un <small> sous sideBar_sensorName (statique)
- topbar-logo.js: peuple .sideBar_firmwareVersion via le fetch existant +
MutationObserver (aucun nouveau fetch -> respecte la limite 6 connexions)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sur les anciens capteurs sans regle sudoers NOPASSWD pour
/var/www/nebuleair_pro_4g/*, l'update echouait avec un message
sudo cryptique.
Nouveau:
- preflight_sudo_check() en PHP teste 'sudo -n -l <script>' avant
de lancer l'update (online ou offline)
- Si KO: la route retourne error_type=sudoers_missing avec un
message clair et la sortie technique de sudo
- L'UI affiche une alerte warning structuree avec etapes numerotees,
contenu du fichier /etc/sudoers.d/nebuleair pret a coller, et un
bouton 'Copier le contenu' (presse-papier)
- Echec immediat (<1s) au lieu d'attendre le timeout du script
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L'upload offline avait deux defauts vs l'update online:
- pas de self-heal des services (pas de Step 3c equivalent)
- ancienne UX synchrone (spinner sans feedback pendant 60-90s)
Maintenant:
- update_firmware_from_file.sh: nouveau Step 4c qui appelle
setup_services.sh (alignement avec online)
- launcher.php upload_firmware: lance le script en background et
reutilise le mecanisme log/done de l'update online
- admin.html uploadFirmware: apres l'upload du ZIP, bascule sur
le meme systeme de polling/progress que l'online (avec mapping
d'etapes specifique au script offline)
- Detection de fin par substring 'completed successfully!' (matche
les 2 markers finaux differents)
Fix au passage: le bouton 'Upload & Install' restait bloque sur
'Installing...' apres succes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
L'ancien flow etait un AJAX bloquant qui attendait ~90s sans aucun
retour visuel autre qu'un spinner.
Nouveau flow:
- Backend: launcher.php lance update_firmware.sh en background
(route update_firmware_start) et expose une route de polling
incremental (update_firmware_progress) avec offset.
- Frontend: progress bar Bootstrap animee + label de l'etape en
cours + timer mm:ss / estimation, plus streaming des logs
toutes les 700ms.
- Sous-etape Step 3c (la plus longue): interpolation fine de la
progression en comptant les 'Started X' (services demarres).
- Logs techniques masques par defaut dans <details>, ouverts
automatiquement en cas d'echec pour faciliter le debug.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Les requetes MIN/MAX(timestamp) sur les tables capteur etaient
faussees par les lignes accumulees pendant le bug RTC (v1.7.4):
la chaine 'not connected' > toute date ISO en tri ASCII, donc
MAX() retournait 'not connected' au lieu de la vraie date la
plus recente.
Fix: WHERE timestamp != 'not connected' dans les requetes MIN/MAX.
Les lignes 'not connected' restent en base, elles sont juste
ignorees pour le calcul des bornes temporelles affichees.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ajout dans le tableau admin et dans les listes allowed restart/toggle:
- rtc_save_to_db.service
- nebuleair-wifi-powersave.timer
- nebuleair-cpu-power.service
Aussi: nebuleair-noise-data.timer etait dans get_systemd_services
mais absent des listes restart/toggle (les boutons n'auraient pas
fonctionne). Corrige.
Nouveau: support d'un display_name explicite par service (override
optionnel) pour les noms qui ne suivent pas la convention
'nebuleair-*-data.timer'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le check -x echouait car git config core.fileMode=false (defini dans
installation_part1.sh) strippe le bit executable. Resultat: Step 3c
logguait 'not found or not executable, skipping' au lieu de
reellement appeler setup_services.sh.
Fix: chmod +x avant l'execution, comme le fait deja
installation_part2.sh sur le meme script.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
setup_services.sh devient la source unique pour les services systemd
(le service rtc_save_to_db etait auparavant cree inline dans
installation_part2.sh, en doublon avec un commentaire dans save_to_db.py).
update_firmware.sh appelle maintenant setup_services.sh apres le git
pull. Resultat: les capteurs deja deployes peuvent se reparer tout
seuls au prochain update firmware (services manquants, masques, ou
nouveaux services ajoutes au repo apres l'installation initiale).
Defensif: systemctl unmask sur rtc_save_to_db avant creation du
fichier .service, pour eviter d'ecrire dans /dev/null si le service
avait ete masque (cas observe sur un capteur en production).
Pas de risque sur les capteurs sains: reecriture des .service avec
le meme contenu, comportement inchange.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Le check SIM sur CSQ=99 ajoutait 2 commandes AT a chaque coupure
signal temporaire (frequent en zone marginale) pour un gain
marginal: le check sur la branche USOCR/PDP attrape le cas avec
un delai de 1-2 cycles max (le modem sans SIM alterne CSQ=99
et CSQ>0).
On garde uniquement le check v1.7.1 dans la branche USOCR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sans SIM, le modem retourne CSQ=99 systematique et le script
sortait avant d'atteindre le check SIM ajoute en v1.7.1.
- Branche CSQ=99: appelle aussi check_sim_status()
- Si SIM absente: alerte rouge + notification WiFi
- Si SIM OK: message vert (coupure reseau temporaire)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Meme modele que NPM: ecriture en base avec valeurs a 0 et noise_status=0xFF
si capteur deconnecte, flag ERR_NOISE (bit 5) dans byte 66 UDP, messages
explicites sur page capteurs et self-test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Lecture npm_status depuis derniere mesure en base (rowid DESC, pas de moyenne)
- Independant du RTC (pas de dependance au timestamp)
- Byte 67 du payload UDP = registre status NextPM
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- NPM: mode --dry-run (print JSON sans ecriture en base)
- launcher.php: endpoint npm appelle get_data_modbus_v3.py --dry-run
- sensors.html: affichage PM + temp + humidite + status NPM decode
- Suppression unite ug/m3 sur le champ status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Lecture fichier VERSION et pack major.minor.patch dans bytes 69-71
- README: documentation complete structure 100 bytes + conso data
- Changelog mis a jour
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bytes 66-68 (error_flags, npm_status, device_status) initialises a 0x00
au lieu de 0xFF pour eviter faux positifs cote serveur
- Implementation flag RTC (byte 66) + methodes SensorPayload
- Escalade PDP reset: si echec → notification + hardware reboot + exit
- Changelog et VERSION mis a jour
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Corrige une desynchronisation serie qui causait l'envoi de la commande
AT+USOWR comme donnees UDP au lieu du payload capteurs. Ajout de flush
buffer serie, verification du prompt @, et abort propre a chaque etape.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>