Sommaire
Dans un précédent tutoriel, nous avons découvert comment créer un capteur de luminosité avec un ESP8266 connecté par Wifi. Ici je vous propose de créer un capteur extérieur piloté par un Raspberry Pi.
Pour être exact, le Raspberry Pi sera installé en intérieur, connecté au réseau filaire Ethernet et relié par un câble à un boitier étanche installé sur le toit qui contiendra une photorésistance et/ou un capteur TSL2561.
Nous allons utiliser un Raspberry B+ version 1 sur lequel nous installerons une sentinelle pour pouvoir déployer notre package depuis Visual Studio. Le package d’acquisition des données sera écrit en Python.
Prérequis
- Un serveur Constellation
- Un Raspberry Pi sur lequel nous installerons une sentinelle
- Une photorésistance avec un condensateur 1uF et/ou un capteur TSL2561
- Visual Studio avec le SDK Constellation
Mesurer la luminosité ambiante avec une photorésistance
Le montage
Tout d’abord, il nous faut un boitier étanche avec un couvercle transparent (pour mesurer la luminosité c’est mieux !) :
Sur une plaque epoxy, soudons la photorésistance avec un condensateur 1uF.
Une des deux pattes de la photorésistance sera reliée sur la pin « 3v3 » du Raspberry et la deuxième sera reliée à une entrée du Raspberry, disons la pin n°12 (soit la GPIO #18). Toujours sur cette 2ème patte, on soudera le pole « + » du condensateur. L’autre pole du condensateur, celle marquée « -« , devra être reliée à la masse, sur l’une des pins « GND » du Raspberry.
On a donc trois fils connectés entre le Raspberry et notre capteur : le 3V3 et la masse (Gnd) pour l’alimentation et un pour le signal (GPIO #18). L’idée est donc de « charger » le condensateur et de chronométrer le temps qu’il mets à se décharger.
Plus il y a de lumière et moins la résistance est forte, autrement dit le condensateur se déchargera très rapidement. A l’inverse, plus il fait sombre et plus la résistance sera forte. Autrement dit, moins il y a de lumière et moins vite se déchargera le condensateur.
On voit sur les photos ci-dessous, le fil Orange pour le 3V3, le fil Bleu pour la GPIO et le fil Blanc pour la masse (Gnd) :
Il fois les connexions réalisées, on peut refermer proprement notre boitier en vérifiant soigneusement son étanchéité avant de l’installer sur le toit.
Côté intérieur, on relie les 3 fils de notre boitier au Raspberry (3v3, Gnd et GPIO18), le câble Ethernet et l’alimentation MicroUSB.
Pour l’installation du système sur le Raspberry suivez ce guide puis installez-y une sentinelle. Votre Raspberry est prêt et connecté à Constellation, reste à développer notre package Python.
Programmation
Depuis Visual Studio, créons un nouveau projet de type « Constellation Python Package » que nous nommerons “LightSensor”.
Renommez le fichier « Demo.py » en « Light.py » et remplacez l’intégralité du contenu par le code suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import Constellation import RPi.GPIO as GPIO, time, os MAX_READING = 30000 LIGHT_SENSOR_GPIO = 18 MEASURE_INTERVAL = 10 def OnExit(): GPIO.cleanup() def RCtime (RCpin): reading = 0 GPIO.setup(RCpin, GPIO.OUT) GPIO.output(RCpin, GPIO.LOW) time.sleep(0.1) GPIO.setup(RCpin, GPIO.IN) # This takes about 1 millisecond per loop cycle while (GPIO.input(RCpin) == GPIO.LOW): if (reading > MAX_READING): break reading += 1 return reading def Start(): global INTERVAL GPIO.setmode(GPIO.BCM) Constellation.OnExitCallback = OnExit lastSend = 0 currentValue = 0 count = 0 Constellation.WriteInfo("LightSensor is ready !") while Constellation.IsRunning: currentValue = currentValue + RCtime(LIGHT_SENSOR_GPIO) count = count + 1 ts = int(round(time.time())) if ts - lastSend >= int(Constellation.GetSetting("Interval")): avg = int(round(currentValue / count)) Constellation.PushStateObject("Light", avg, "int") currentValue = 0 count = 0 lastSend = ts time.sleep(MEASURE_INTERVAL) Constellation.Start(Start) |
N’hésitez pas à relire le guide sur la création de package Python pour bien comprendre les bases d’un package Python.
Ici on démarre notre package par la méthode « Start » qui démarre une boucle pour appeler la méthode RCtime toutes les 10 secondes (variable MEASURE_INTERVAL).
La méthode RCtime charge le condensateur (mode output) puis passe l’I/O en « input » et incrémente la variable « reading » en continue tant que le condensateur n’est pas déchargé (input == « low »). Plus la valeur « reading » est élevée, plus le condensateur a mis du temps à se décharger et donc moins il y a de lumière. On a un garde-fou à « 30.000 » pour ne pas attendre indéfiniment dans le cas où il fait trop sombre.
Bien entendu on ne mesure pas une unité précise. Si vous déployez votre code sur un RPi V1 et V2 (plus puissant), vous n’aurez pas la même valeur pour les mêmes conditions.
Chaque mesure incrémente la variable « currentValue », puis dès qu’on atteint l’intervalle configuré dans les Settings Constellation, on fait la moyenne des mesures on publie un StateObject nommé « Light » de type « int ».
Dans le manifeste du package (PackageInfo.xml), n’oubliez pas de déclarer le setting « Interval » en lui spécifiant une valeur par défaut, ici de 10 seconde :
1 2 3 |
<Settings> <Setting name="Interval" isRequired="false" type="Int32" defaultValue="10" description="Interval to read sensors in second" /> </Settings> |
De même vous pouvez également modifier les informations de compatibilité des plateformes pour exclure la plateforme Windows dans la mesure où le package exploite les GPIO du Raspberry.
1 |
<Platform id="Win32NT" isCompliant="false" /> |
Et voilà le package est prêt, vous pouvez depuis Visual Studio le publier dans votre Constellation :
… puis le déployer sur votre sentinelle Raspberry depuis la Console Constellation, en cliquant sur le bouton “Deploy new package” :
Dans l’assistant de déploiement vous pourrez alors personnaliser le setting “Interval” ou bien laisser la valeur par défaut que nous avons fixé à 10 (secondes) :
Sur la Console Log, le package va être téléchargé et déployé par votre sentinelle Raspberry avant de démarrer :
Et en vous rendant dans le StateObjects Explorer, vous verrez votre package “LightSensor” publier un StateObject “Light” avec un entier comme valeur :
Votre package Python est opérationnel, on peut dés à présent exploiter cette valeur dans une page Web, un script, un programme .NET ou Python, un Arduino, etc… comme nous le verrons dans la suite de cet article.
Mesurer des lux avec un capteur TSL2561
Pour obtenir une mesure plus fiable et surtout exploitant une échelle de mesure universelle, nous allons utiliser un capteur de luminosité TSL2561 capable de mesurer des Lux.
Le montage
Dans cette deuxième version, j’ai conservé la photorésistance et simplement ajouté le capteur TSL2561. Celui-ci est également alimenté en 3V3, j’ai donc réutilisé les deux fils (Orange et Blanc) de la photorésistance pour alimenter le capteur également.
Le TSL2561 utilise l’I²C, j’ai donc ajouté deux fils entre le boitier étanche et le Raspberry pour connecter le capteur en i²C (SDA et SCL).
Sur le Raspberry, le SDA est la GPIO#2 et le SCL la GPIO #3, c’est à dire juste en dessus de la pin 3v3.
La programmation
Le plus simple et le plus fiable pour récupérer les données du capteur TSL2561 depuis un Raspberry est d’utiliser un programme natif. Pour cela nous allons utiliser le code publié à cette adresse : http://dino.ciuffetti.info/2014/03/tsl2561-light-sensor-on-raspberry-pi-in-c/.
Il suffit récupérer les 3 fichiers C et de les compiler avec GCC pour générer le binaire. Pour vous simplifier la tache, vous pouvez directement récupérer le binaire GetTSL2561 ici.
Ajoutez ensuite un deuxième script nommé « Lux.py » dans le répertoire « Scripts » de votre projet et n’oubliez pas d’inclure votre script dans le package en définissant la Build Action à « Content » et en activant la copie du fichier dans le répertoire de sortie :
Copiez également le binaire GetTSL2561 dans le répertoire « Scripts » de votre package en l’incluant également dans le package (Build Action à Content et Copy if newer).
Le contenu du script « Lux.py » sera :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import Constellation import os, re, subprocess, time, stat # Const EXECUTABLE_FILENAME = "GetTSL2561" def DoMeasure(): # Start process process = subprocess.Popen("./" + EXECUTABLE_FILENAME, stdout=subprocess.PIPE) # Reading output for line in iter(process.stdout.readline, ''): # Parse line matchObj = re.match('RC: (\d*)\((.*)\), broadband: (\d*), ir: (\d*), lux: (\d*)', line) if matchObj: # Reading value returnCode = int(matchObj.group(1)) broadband = int(matchObj.group(3)) ir = int(matchObj.group(4)) lux = int(matchObj.group(5)) # Push StateObject if returnCode != 0: Constellation.WriteWarn("Unknow return code %s : %s" % (returnCode, line)) else: Constellation.PushStateObject("Lux", { "Broadband": broadband, "IR" : ir, "Lux" : lux }, "LightSensor.Lux") else: Constellation.WriteError("Unable to parse the output: %s" % line) def Start(): # Make the "GetTSL2561" file as executable st = os.stat(EXECUTABLE_FILENAME) os.chmod(EXECUTABLE_FILENAME, st.st_mode | stat.S_IEXEC) Constellation.WriteInfo("LuxSensor is ready !") while Constellation.IsRunning: DoMeasure() time.sleep(int(Constellation.GetSetting("Interval"))) Constellation.Start(Start) |
Comme pour le script « Light.py », on déclare la méthode « Start » comme méthode de démarrage. Celle-ci boucle tant que le package est démarré à l’intervalle défini par le setting « Interval » qu’on a déclaré à 10 secondes par défaut dans le manifeste.
A chaque itération, on invoque la méthode « DoMeasure » qui démarre le programme « GetTSL2561 » et on parse avec une regex le résultat de la sortie du programme (process.stdout) pour extraire le code de retour, le broadband (spectre visible), l’infrarouge et le nombre de lux courant mesuré par le capteur.
Si le code de retour est égal à 0 c’est que la mesure est correcte, on publie alors un StateObject nommé « Lux » contenant les trois propriétés mesurées : Broadband, IR et Lux.
1 |
Constellation.PushStateObject("Lux", { "Broadband": broadband, "IR" : ir, "Lux" : lux }, "LightSensor.Lux") |
Attention avant de déployer cette nouvelle version il faut activer l’I²C sur le Raspberry. Pour cela lancer la commande « sudo raspi-config » puis dans le menu « Advanced Options » activez l’I²C et chargez le module par défaut au démarrage.
On peut maintenant publier cette nouvelle version depuis Visual Studio et lancer un « Reload » sur notre package pour déployer cette nouvelle version sur notre Rapsberry.
Désormais nous avons deux StateObjects publiés par ce package : « Light » via la photorésistance et « Lux » via le TSL2561 :
Notre nouveau StateObject « Lux » est un objet complexe contenant trois propriétés :
Exploitez les données
Notre package Python vit sa vie sur notre Raspberry et publie à intervalle régulier deux StateObjects sur base des mesures réalisées par la photorésistance et/ou le capteur de lux TSL2561.
On peut maintenant écrire des pages Web, des scripts, des packages .NET ou Python, Arduino, etc… qui exploiteront ces mesures en temps réel.
Un dashboard Web
Depuis une page Web, il suffit d’ajouter un StateObjectLink. Vous pouvez vous inspirer de ce tutoriel par exemple.
1 2 3 4 5 |
constellation.registerStateObjectLink("*", "LightSensor", "Lux", "*", function (so) { $scope.$apply(function () { console.log("Lux = ", so.Value.Lux); }); }); |
Par exemple sur le projet S-Panel avec quelques composants Bootstrap :
Un programme .NET
Vous pouvez là aussi vous inspirer de ce tutoriel. En clair, il suffit dans votre code C# d’ajouter des StateObjectLinks avec l’API.NET :
1 2 |
[StateObjectLink("LightSensor", "Lux")] public StateObjectNotifier Lux { get; set; } |
Ainsi la propriété .NET nommée « Lux » ci-dessus sera liée en temps réel au StateObject « Lux » du package « LigthSensor ».
Vous pouvez ajouter des événements dès que la valeur change :
1 2 3 4 |
this.Lux.ValueChanged += (s, e) => { PackageHost.WriteInfo($"Nouvelle mesure à {this.Lux.Value.LastUpdate} = {this.Lux.DynamicValue.Lux} lux"); }; |
Vous pouvez ainsi faire ce que bon vous semble avec cette information. Par exemple allumer automatiquement les lumières si la luminosité est trop faible, fermer les volets, enregistrer la valeur des Lux dans un fichier Excel ou une base de données, etc.. etc..
Historisation avec ElasticSearch / Kibana
En utilisant le package Graylog du catalogue de package, on peut historiser les mesures de notre capteur dans une base ElasticSearch.
Dans la configuration du package Graylog, on aura quelque chose comme :
1 2 3 4 5 6 7 8 9 |
<graylogConfiguration xmlns="urn:GraylogConnector" sendPackageLogs="false" sendPackageStates="true" sendSentinelUpdates="true"> <subscriptions> <subscription package="LightSensor" name="Lux" /> <!-- ..... --> </subscriptions> <outputs> <gelfOutput name="Graylog server UDP" host="graylog.ajsinfo.loc" port="12201" protocol="Udp" /> </outputs> </graylogConfiguration> |
La documentation du package Graylog est disponible ici. En résumé on crée ici un abonnement pour la StateObject « Lux » du package « LightSensor » et dans les « outputs » on a déclaré notre serveur Graylog.
De ce fait dès que le StateObject « Lux » est mis à jour par notre package Python, le package Graylog enregistre la nouvelle version du StateObject sur le serveur Graylog qui lui-même utilise ElasticSearch comme base de stockage.
Ainsi on pourra utiliser un outil comme Kibana pour visualiser l’évolution de notre StateObject :
Un article complet sur le sujet a été publié ici.
Stats avec Cacti
On peut également écrire un script pour récupérer notre StateObject « Lux » depuis l’interface REST qu’on ajoutera en tant que « Data Input Methods » dans Cacti pour pouvoir créer des graphiques facilement :
Démarrez la discussion sur le forum Constellation