Monitorer la température et le PH d’un bassin avec une connexion Lora et remonter l’information dans Jeedom
6 janvier 2021Bonjour à tous,
Aujourd’hui, on va apprendre comment monitorer l’état d’un bassin pour monitorer la température de l’eau ainsi que son PH afin de savoir si tout va bien pour ses habitants. Ce tutoriel change un peu de l’ordinaire car le matériel utilisé est plus cher que ce que j’utilise d’habitude.
Partie 0: Le matériel choisi
- Une Raspberry pi + son alimentation + une carte SD
- Un shield Dragino
- Une sonde de température Atlas scientific PT-1000
- Un circuit de mesure de température EZO RTD
- Une sonde de PH Atlas scientific
- Un circuit de mesure de EZO PH
- 2 Cartes de support EZO vers USB
- 2 câbles mini USB -> USB
- Optionnellement, une imprimante 3D
Partie 1: Préparation du matériel
Comme pour chaque projet que je fais avec un raspberry pi, vous devez commencer par créer une carte SD (voir les parties de 1 à 4 de mon ancine poste https://miniprojets.net/index.php/2019/05/13/jeedom-ou-comment-domotiser-sa-maison-en-quelques-clics/)
Maintenant nous pouvons préparer le matériel physique.
Dans un premier temps, nous devons faire une légère modification sur le shield Dragino car certaines versions ont un défaut et il faut souder en elles les pins 22 et 24. (Pour plus d’information, je vous invite à aller vous le wiki https://wiki.dragino.com/index.php?title=Lora_Shield)
Maintenant je vous propose d’imprimer en 3D le boitier pour la raspberry et le shield Dragino mais cela n’est pas nécessaire pour la suite du projet, c’est juste que je vais l’installer dans mon garage et j’aimerai une installation propre.
https://www.thingiverse.com/thing:3352448
Ainsi que le support pour maintenir en place les cartes de support EZO vers USB.
https://www.thingiverse.com/thing:1561906
Une fois cela fait, nous pouvons les monter de la façon suivante:
On commence par mettre la raspberry dans le support bas du boitier
Puis on monte la pièce intermédiaire et le chapeau.
Ensuite on fixe les 2 cartes supports
On assemble le tout sur le boitier et on branche les 2 câbles USB
On peux enfin mettre en place le système.
Avant de plonger la sonde de PH, il faudra faire sa calibration. Pour cela, je modifierai mon article un peu plus tard car je n’ai pas le matériel sous la main à cause de la situation actuelle….
Partie 2: Identification des sondes
Pour cette partie, je me base sur la documentation fournie par AtlasScientific (comme d’habitude je vous mets la copie ci-dessous).
Dans un premier temps, il faut rentrer en SSH sur la raspberry pi puis télécharger depuis le git le code nécessaire avec la commande suivante:
git clone https://github.com/AtlasScientific/Raspberry-Pi-sample-code.git
Il faut maintenant installer et paramétrer les modules nécessaire avec les commandes suivantes:
sudo apt-get install libftdi-dev
sudo pip3 install pylibftdi
sudo nano /etc/udev/rules.d/99-libftdi.rules
Copier en remplaçant le contenu du fichier 99-libftdi.rules par la ligne suivante:
SUBSYSTEMS==”usb”, ATTRS{idVendor}==”0403”, ATTRS{idProduct}==”6015”,GROUP=”dialout”, MODE=”0660”, SYMLINK+=”FTDISerial_Converter_$attr{serial}”
Faites un Ctrl+X, puis y pour sauvegarder.
Redemarrez le service udev avec la commande suivante:
sudo service udev restart
Vérifiez que dans le fichier /usr/local/lib/python3.7/dist-packages/pylibftdi/driver.py pour la ligne qui contient USB_PID_LIST vous avez exactement cela USB_PID_LIST = [0x6001, 0x6010, 0x6011, 0x6014, 0x6015] grâce à la commande suivante:
sudo nano /usr/local/lib/python3.7/dist-packages/pylibftdi/driver.py
puis faite un Ctrl+W et tapez USB_PID_LIST, puis entrée.
Si jamais il manque 0x6015, rajoutez le en faite un Ctrl+X, puis Y.
Maintenant tapez la commande suivante:
sudo python3 -m pylibftdi.examples.list_devices
Normalement vous devriez avec une réponse de ce genre:
FTDI:FT230X Basic UART:DO009J4O
FTDI:FT230X Basic UART:DO009MWF
Ce qui correspond à nos 2 capteurs.
Maintenant tapez les commandes suivantes:
cd ~/Raspberry-Pi-sample-code
Nous allons modifier le fichier ftdi car avec python3 la fonction decode n’est plus nécessaire. Pour cela on tape la commande:
sudo nano ftdi.py
Puis on recherche decode avec le Ctrl+w
On tombe sur la ligne suivante:
dev_info = map(lambda x: x.decode('latin1'), device)
il suffit de supprimer le « .decode(‘latin1’) » ce qui donne
dev_info = map(lambda x: x, device)
Faites un Ctrl+x, puis Y
Maintenant, tapez la commande suivante:
sudo python3 ftdi.py
Vous aurez le choix suivant:
Welcome to the Atlas Scientific Raspberry Pi FTDI Serial example.
Any commands entered are passed to the board via UART except:
Poll,xx.x command continuously polls the board every xx.x seconds
Pressing ctrl-c will stop the polling
Press enter to receive all data in buffer (for continuous mode)
Discovered FTDI serial numbers:
Index: 0 Serial: DO009J4O
Index: 1 Serial: DO009MWF
===================================
Please select a device index:
Tapez par exemple 0 ou 1 au choix (ici je tape 1), vous vous retrouvez avec l’annonce suivante:
Please select a device index: 1
>> Opened device DO009MWF
>> Any commands entered are passed to the board via FTDI:
Enter command:
Tapez la commande R (Read (lecture)) puis taper sur Entrée. Après quelques secondes, vous aurez le résultat suivant:
>> Opened device DO009MWF
>> Any commands entered are passed to the board via FTDI:
Enter command: R
8.294
Comme je sais que l’eau est à environ 23°C (mesure fait via un thermomètre simple) cela me renseigne que le capteur DO009MWF (ayant pour index 1) est mon capteur de PH et donc que le capteur DO009J4O (ayant pour index 0) est mon capteur de température.
Je mets ici les 2 pdfs correspondant aux 2 capteurs afin de permettre de s’amuser avec les différentes commandes possibles (étalonnage, mesure continue,….)
Voila nous avons identifié les 2 capteurs et nous pouvons passer à la suite afin de permettre la lecture des 2 capteurs toutes les 15 minutes, la sauvegarde de la valeur puis leur envoie via Lora.
Partie 3: Lecture et enregistrement des valeurs des sondes
Maintenant, nous allons créer un script qui va permettre de lire les valeurs des capteurs puis de sauvegarder leur valeur dans 3 fichiers distincts (1 pour l’historisation, 1 pour la valeur actuelle de la température mais retravaillé pour la transmission et 1 pour la valeur actuelle du PH).
Pour cela, il faut créer un fichier avec la commande suivante:
sudo nano read_sensor_temp_ph.py
Puis collez le code suivant:
import string
import pylibftdi
from pylibftdi.device import Device
from pylibftdi.driver import FtdiError
from pylibftdi import Driver
import os
import time
from datetime import datetime
class AtlasDevice(Device):
def __init__(self, sn):
Device.__init__(self, mode='t', device_id=sn)
def read_line(self, size=0):
"""
taken from the ftdi library and modified to
use the ezo line separator "\r"
"""
lsl = len('\r')
line_buffer = []
while True:
next_char = self.read(1)
if next_char == '' or (size > 0 and len(line_buffer) > size):
break
line_buffer.append(next_char)
if (len(line_buffer) >= lsl and
line_buffer[-lsl:] == list('\r')):
break
return ''.join(line_buffer)
def read_lines(self):
"""
also taken from ftdi lib to work with modified readline function
"""
lines = []
try:
while True:
line = self.read_line()
if not line:
break
self.flush_input()
lines.append(line)
return lines
except FtdiError:
print("Failed to read from the sensor.")
return ''
def send_cmd(self, cmd):
"""
Send command to the Atlas Sensor.
Before sending, add Carriage Return at the end of the command.
:param cmd:
:return:
"""
buf = cmd + "\r" # add carriage return
try:
self.write(buf)
return True
except FtdiError:
print("Failed to send command to the sensor.")
return False
def get_ftdi_device_list():
"""
return a list of lines, each a colon-separated
vendor:product:serial summary of detected devices
"""
dev_list = []
for device in Driver().list_devices():
# list_devices returns bytes rather than strings
# dev_info = map(lambda x: x.decode('latin1'), device)
dev_info = map(lambda x: x, device)
# device must always be this triple
vendor, product, serial = dev_info
dev_list.append(serial)
return dev_list
##########################################################################
if __name__ == '__main__':
real_raw_input = vars(__builtins__).get('raw_input', input) # used to find the correct function for python2/3
print("\nWelcome to the Atlas Scientific Raspberry Pi FTDI Serial.\n")
print("Discovered FTDI serial numbers:")
devices = get_ftdi_device_list()
cnt_all = len(devices)
#print "\nIndex:\tSerial: "
for i in range(cnt_all):
print( "\nIndex: ", i, " Serial: ", devices[i])
print( "===================================")
############### Capteurs ###################
index_temp = 0
index_ph = 1
while True:
dev_temp = AtlasDevice(devices[int(index_temp)])
dev_ph = AtlasDevice(devices[int(index_ph)])
time.sleep(1)
dev_temp.flush()
dev_ph.flush()
input_val = "R"
# pass commands straight to board
if len(input_val) == 0:
lines = dev.read_lines()
for i in range(len(lines)):
print("temperature" + lines[i])
else:
fichier = open("/home/pi/Raspberry-Pi-sample-code/datalog_Sensors_Temp_Ph.csv", "a")
fichier_lastest_temp = open("/home/pi/Raspberry-Pi-sample-code/datalog_sensors_lastest_temp.csv", "w")
fichier_lastest_ph = open("/home/pi/Raspberry-Pi-sample-code/datalog_sensors_lastest_ph.csv", "w")
dev_temp.send_cmd(input_val)
time.sleep(1.3)
lines = dev_temp.read_lines()
now = datetime.now()
timestamp = datetime.timestamp(now)
for i in range(len(lines)):
print("Temperature: " + lines[i])
fichier.write(str(timestamp) + ",")
temperature = lines[i]
temperature = temperature.replace(temperature[len(temperature)-1],',')
fichier.write(temperature)
temperature = temperature.replace(temperature[len(temperature)-1],'')
temperature = str(float(temperature)+40)
fichier_lastest_temp.write(temperature)
fichier_lastest_temp.close()
dev_ph.send_cmd(input_val)
time.sleep(1.3)
lines = dev_ph.read_lines()
for i in range(len(lines)):
print("PH: " + lines[i])
ph = lines[i]
ph = ph.replace(ph[len(ph)-1],'')
fichier.write(ph + "\n")
fichier_lastest_ph.write(ph)
fichier_lastest_ph.close()
fichier.close()
time.sleep(60)
Vérifiez que les index sont corrects pour vos capteurs (ici)
index_temp = 0
index_ph = 1
et modifiez en cas où
Ce code un fois lancé va lire la valeur du capteur de température ainsi que celui du capteur de PH puis les enregistrer dans les fichiers et attendre 60 secondes avant de recommencer.
Faites un Ctrl+X, puis Y.
Maintenant, on va créer un service qui se lancera ce script au démarrage de la raspberry pi.
Pour cela, il faut faire les commandes suivantes:
cd /lib/systemd/system/
sudo nano atlassensors.service
Maintenant, il faut coller le code suivant:
[Unit] Description=Read Atlas sensors After=multi-user.target [Service] Type=simple ExecStart=/usr/bin/python3 /home/pi/Raspberry-Pi-sample-code/read_sensor_temp_ph.py Restart=on-abort [Install] WantedBy=multi-user.target
Faites un Ctrl+X, puis Y.
Maintenant on met en route le service via les commandes suivantes:
sudo chmod 644 /lib/systemd/system/atlassensors.service
sudo chmod +x /home/pi/Raspberry-Pi-sample-code/read_sensor_temp_ph.py
sudo systemctl daemon-reload
sudo systemctl enable atlassensors.service
sudo systemctl start atlassensors.service
Maintenant, nous allons vérifier que l’enregistrement fonctionne bien avec la commande suivante:
sudo nano /home/pi/Raspberry-Pi-sample-code/datalog_Sensors_Temp_Ph.csv
On se retrouve avec l’affichage suivant:
1591267278.838152,21.467,8.039
1591267342.582241,21.467,8.048
1591267406.338943,21.465,8.043
1591267470.095639,21.463,8.031
1591267533.852958,21.464,8.035
1591267597.594173,21.462,8.035
1591267661.351104,21.463,8.041
1591267725.10559,21.462,8.042
1591267788.860872,21.463,8.048
Ce qui correspond pour la première colonne au timestamp, la seconde à la température et la troisième au PH.
on vérifie aussi pour les valeurs actuelles avec les commandes:
sudo nano /home/pi/Raspberry-Pi-sample-code/datalog_sensors_lastest_temp.csv
Qui nous donne 61.463 (car on a rajouté 40° afin de n’avoir que des chiffres positifs pour l’envoi)
et la commande
sudo nano /home/pi/Raspberry-Pi-sample-code/datalog_sensors_lastest_ph.csv
qui nous donne 8.048
Voila maintenant que les données sont formatées et enregistrées, nous pouvons passer à leur envoi via le réseau Lora
Partie 4: Préparation de l’environnement LORA sur thethingsnetwork
Dans un premier temps, il faut vous inscrire sur TTN
https://www.thethingsnetwork.org/
Quand vous êtes inscrit et loggé, cliquez sur Console puis Application
Cliquez maintenant sur add application
Remplissez les cases vides comme ci-dessous (c’est un exemple que j’ai fait avec ma raspberry qui se situe dans le garage ^^)
La prochaine étape consiste a ajouter un appareil (device) donc cliquez sur register device
Remplissez le Device ID avec un nom du genre sensors_from_dragino. (car c’est un shield dragino que j’utilise ^^)
Cliquez sur les deux flèches sous le champ Device EUI ce qui le généra de façon automatique
Et finalement cliquez sur Register (Surtout pas sur Cancel car si vous cliquez dessus, vous perdez votre configuration en cours et cela est peu stupide…. Non non je n’ai jamais fait cela… Bon Ok peut-être une fois quand j’étais jeune…)
Normalement vous devriez avoir une nouvelle page avec les identifiants pour connecter votre Lopy sur le réseau Lora
Maintenant que nous avons les bonnes informations, nous pouvons retourner sur la raspberry pi pour coder la partie Lora
Partie 5: Code LORA avec un dragino
Dans un premier temps, il faut télécharger le programme avec la commande suivante:
git clone https://github.com/wklenk/lmic-rpi-lora-gps-hat
Une fois cela fait, allez dans le dossier lmic-rpi-lora-gps-hat/examples/periodic/
cd lmic-rpi-lora-gps-hat/examples/periodic/
Maintenant, il faut modifier le fichier sensor.c
sudo nano lmic-rpi-lora-gps-hat/examples/periodic/sensor.c
On modifie la partie Read data de la façon suivante:
// Read data u2_t readsensor (char* file_name) { debug_str(file_name);FILE* file = NULL; int tempo[2]={0}; file = fopen(file_name, "r");
if (file != NULL) { fscanf(file, "%d.%d", &tempo[0], &tempo[1] ); printf("Les meilleurs scores sont : %d,%d", tempo[0], tempo[1]); fclose(file); }int t = 21;
return tempo[0]*1000+tempo[1];
}
On sauvegarde avec ctrl+x
Puis, nous allons modifier le fichier main.c
sudo nano lmic-rpi-lora-gps-hat/examples/periodic/main.c
Ajoutez les lignes suivantes en dessous de #include « lmic.h » #include « debug.h »
// sensor functions
extern void initsensor(void);
extern u2_t readsensor(char*);
Dans la partie CONFIGURATION renseignez les clefs APPEUI, DEVEUI et DEV KEY avec les informations TTN respectives Application EUI, Device EUI et App Key
ATTENTION!!!! Pour les APPEUI et DEVEUI, il faut inverser l’ordre des hexas, c’est à dire que par exemple sur TTN, vous avez la clef Device EUI: 0127356F2894472B, il faudra écrire pour DEVEUI: 0x2B, 0x47, 0x94, 0x28, 0x6F, 0x35, 0x27, 0x01.
Seul App Key garde le même sens de lecture!
Voila ce que cela donne par exemple:
////////////////////////////////////////////////// // CONFIGURATION (FOR APPLICATION CALLBACKS BELOW) ////////////////////////////////////////////////// // application router ID (LSBF) static const u1_t APPEUI[8] = { 0xXX, 0xXX, 0xXX, 0xXX, 0x7E, 0xD5, 0xB3, 0x70 }; // unique device ID (LSBF) static const u1_t DEVEUI[8] = { 0xXX, 0x37, 0x94, 0x28, 0xXX, 0xXX, 0xXX, 0x00 }; // device-specific AES key (derived from device EUI) static const u1_t DEVKEY[16] = { 0xXX, 0x95, 0xEA, 0x92, 0xXX, 0xXX, 0xDD, 0x9C, 0xXX, 0x27, 0xXX, 0xXX, 0xF7, 0xXX, 0xFB, 0xF4 };
Maintenant, il faut aussi modifier la partie UTILIY JOB de la façon suivante:
////////////////////////////////////////////////// // UTILITY JOB ////////////////////////////////////////////////// static osjob_t reportjob; // report sensor value every minute static void reportfunc (osjob_t* j) { char sensors[2][100] = { "/home/pi/Raspberry-Pi-sample-code/datalog_sensors_lastest_temp.csv", "/home/pi/Raspberry-Pi-sample-code/datalog_sensors_lastest_ph.csv"}; int i; for(i=0; i<2; i++){ u2_t val = readsensor(sensors[i]); debug_val("val = ", val); // prepare and schedule data for transmission LMIC.frame[2i] = val >> 8; LMIC.frame[2i+1] = val; } LMIC_setTxData2(1, LMIC.frame, i*2, 0); // (port 1, 2 bytes, unconfirmed) // reschedule job in 900 seconds os_setTimedCallback(j, os_getTime()+sec2osticks(900), reportfunc); }
On fait une sauvegarde avec ctrl+x
Maintenant, on compile le code
sudo make clean && sudo make && sudo ./build/periodic.out
on test le programme avec la commande suivante:
sudo ./examples/periodic/build/periodic.out
on devrait avoir le retour suivant:
000000000 HAL: Initializing … 000000002 HAL: Detected SX1276 radio module. 000000002 HAL: Set radio RST pin to 0x00 000000002 HAL: Wait until 000000003 ms 000000003 HAL: Set radio RST pin to 0x02 000000003 HAL: Wait until 000000008 ms 000000011 HAL: Receiving … 000000029 Debug: Initializing 000000029 Debug: JOINING 000004213 Debug: EV_TXSTART 000004214 HAL: Sending … 000009279 HAL: Receiving … 000009279 HAL: Wait until 000009278 ms 000009349 Debug: JOINED 000009350 Debug: /home/pi/Raspberry-Pi-sample-code/datalog_sensors_lastest_temp.csv Les meilleurs scores sont : 23,589000009350 Debug: Label 'val = ' value 0x5c25 000009350 Debug: /home/pi/Raspberry-Pi-sample-code/datalog_sensors_lastest_ph.csv Les meilleurs scores sont : 5,190000009350 Debug: Label 'val = ' value 0x1446 000009350 Debug: EV_TXSTART 000009351 HAL: Sending … 000010406 HAL: Receiving … 000010406 HAL: Wait until 000010405 ms 000011451 HAL: Receiving … 000011451 HAL: Wait until 000011452 ms 000015017 Debug: TXCOMPLETE
On vérifie sur TTN que tout est ok en allant sur l’onglet DATA
Si tout est bon, repérez bien le numéro de Port. Cela servira pour la suite.
on peux créer le service lora-node pour rendre automatique celui-ci
Pour cela on fait les commande suivantes:
cd /lib/systemd/system/ sudo nano lora-node.service
On remplile fichier de la façon suivante:
[Unit] Description=LoRaWAN node After=user.target [Service] Type=idle ExecStart=/home/pi/lmic-rpi-lora-gps-hat/examples/periodic/build/periodic.out StandardOutput=journal StandardError=journal Restart=on-failure [Install] WantedBy=multi-user.target
On sauvegarde avec ctrl+x et on execute les commandes suivantes:
sudo chmod 644 /lib/systemd/system/lora-node.service sudo systemctl daemon-reload sudo systemctl enable lora-node.service sudo systemctl start lora-node.service sudo systemctl status lora-node.service
Nous devons avoir le retour suivant:
● lora-node.service - LoRaWAN node Loaded: loaded (/lib/systemd/system/lora-node.service; enabled; vendor preset: enabled) Active: active (running) since Wed 2021-01-06 10:35:23 CET; 7min ago Main PID: 26155 (periodic.out) Tasks: 1 (limit: 2063) CGroup: /system.slice/lora-node.service └─26155 /home/pi/lmic-rpi-lora-gps-hat/examples/periodic/build/periodic.out janv. 06 10:35:23 raspberrypi systemd[1]: Started LoRaWAN node.
C’est ok? Parfait! maintenant on va passer à la partie décodage
Partie 6: Décodage de la Trame Lora
Il faut aller dans l’onglet Applications > capteurs_garage > Payload Formats
On renseigne le code suivant dans la partie decoder
function Decoder(bytes, port) { var decoded = {}; var counter = 1; decoded.temperature_eau = 0; for(i=0; i<2; i++){ decoded.temperature_eau += Math.pow(256,i)*parseInt(bytes[2*counter-1-i]); } decoded.temperature_eau = decoded.temperature_eau/1000-20; counter +=1; decoded.ph_eau = 0; for(i=0; i<2; i++){ decoded.ph_eau += Math.pow(256,i)*parseInt(bytes[2*counter-1-i]); } decoded.ph_eau = decoded.ph_eau/1000; return decoded; }
On va tester pour voir si cela fonctionne pour cela dans Payload, mettez 5C 24 14 46 et cliquez sur Test
Si tout est ok, on sauvegarde avec le bouton situé en bas. Voilà nous avons réussi à décoder la trame et elle sera ainsi tout le temps décodée.
Partie 7: Récupérer les données sur Jeedom
Pour cette partie, on va utiliser le plugin LoRa Node for TTN (Attention le plugin est payant, j’écrirai un article sur une solution gratuite utilisant MQTT un peu plus tard)
Pour cela dans le market de Jeedom téléchargé le plugin et activé le, il n’y a pas de configuration spécifique.
Maintenant allez dans le plugin en cliquant sur Plugins > Protocole domotique > Lora Node for TTN
Cliquez sur Ajouter un équipement et donnez lui un nom
Remplissez les informations suivantes: Application ID, Device ID et Port
Pensez à mettre Visible et sauvegardez
Copiez l’adresse avec l’apikey en bas de la page et renseignez Europe pour le Sélection du serveur TTN (Région)
Maintenant allez sur TTN et cliquez sur l’onglet Applications >capteurs_garage > Integrations
Cliquez sur + add integration
Choisissez HTPP Integration
Maintenant, remplissez les données suivantes Process ID, choisissez default key et collez l’URL prise dans Jeedom (ATTENTION, Il faut avoir ouvert et redirigé les ports correctement de sa box afin d’avoir accès à Jeedom depuis l’extérieur, je ferai un article prochainement dessus)
Sauvegardez et maintenatnt retournez sur Jeedom.
Si tout est paramétré correctement, on devrait voir apparaitre à la prochaine remontée les données issues de TTN. Notre script Lora remontant les données toutes les 15 minutes, nous avons le temps de boire un café ^^
Après cette pause café, on vérifie….
Nous pouvons même historiser…
Voilà, maintenant vous êtes capable de faire le monitoring d’un bassin via une connexion Lora et remonter les données dans Jeedom.
J’espère que cela vous aidera, n’hésitez pas à poser des questions ou des updates.
Jérôme Ferrari
Bonjour
Je me permet de venir vers vous, car je suis en recherche d’une personne ayant des compétences afin de rendre une serre horticole ( production de fruits et légumes) un maximum autonome, en l’occurrence gestion de l’ouverture des évents (déjà motorisés) automatiquement en fonction de l’hygrométrie, du vent, et de la température, éventuellement un suivi sur un smartphone,
Je suis sur la région d’Aix en Provence, je suis en particulier.
Merci par avance,
Bien cordialement
Mr SOUCHE Fabrice
Bonjour,
je vous envoi un mail demain pour discuter de ce projet.
Bonne journée,
Jérôme Ferrari