Après avoir installé et mis à jour Raspberry pi OS ( [[Linux:Installation:SSH]], [[Linux:Installation:BashRc]], [[Linux:Installation:PHP]]), on va installer un client pour nextcloud, et un environnement python pour monter un poulailler connecté.
====== Nextcloud CMD ======
Sources :
https://www.aukfood.fr/nextcloud-client-en-ligne-de-commande/
sudo apt install nextcloud-client
ne trouve pas le client.
Il faut ajouter le dépôt https://ppa.launchpadcontent.net/nextcloud-devs
sudo apt install software-properties-common python3-launchpadlib
Ensuite on peut faire l'installation du dépôt et de sa clé GPG :
sudo add-apt-repository ppa:nextcloud-devs/client
Attention la distribution bookworm n'est pas connue sur ce launchpad, il faut éditer le fichier sources-list en donnant l'ancienne distribution jammy :
sudo nano /etc/apt/sources.list.d/nextcloud-devs-ubuntu-client-bookworm.list
Et modifier la ligne en :
deb https://ppa.launchpadcontent.net/nextcloud-devs/client/ubuntu/ jammy main
Et enfin faire l'installation de nextcloud-client :
sudo apt update
sudo apt install nextcloud-client
On peut procéder à une première synchronisation :
nextcloudcmd --user $USER --password "$PASS" /data/nextcloud/GCheramy https://monurlnextcloud
Liste des options sur :
https://docs.nextcloud.com/desktop/latest/advancedusage.html
on peut ajouter --non-interactive pour une utilisation dans un cron par exemple.
nextcloudcmd --path / /home/user/ \ https://:@
====== Python ======
sudo apt install python3 python3-dev python3-rpi.gpio python3-venv
Python fonctionne avec des environnement (par projet ou par user). On crée un utilisateur dédié :
sudo useradd -m -d /home/$USER $USER
sudo passwd $USER
cd /home/$USER
Puis l'environnement :
python3 -m venv .venv
On l'active :
source /home/pi/.venv/bin/activate
Dans l'environnement .venv :
#pip3 install RPi (n'a pas marché ? non nécessaire ?)
pip3 install RPi.GPIO
#pip3 install Adafruit_DHT --install-option="--force-pi" (on va essayer de se passer de cette lib obsolète et buguée)
pip3 install board
Et enfin pour lancer un script (potentiellement en sudo) :
/home/$USER/.venv/bin/python3 /home/$USER/script.py
Si les pages php n'arrivent pas à executer le script python, cela peut provenir d'un problème de droits.
On peut ajouter www-data au sudoers :
sudo nano /etc/sudoers
Ajouter à la fin la ligne :
www-data ALL = NOPASSWD: ALL
====== DHT22 ======
Actuellement avec le code trouvé ici : https://www.reddit.com/r/raspberry_pi/comments/13pscin/comment/jlbinb2/?share_id=wWvKIpI7UVC945M2GZ5BK&utm_medium=android_app&utm_name=androidcss&utm_source=share&utm_term=1
Et dont voici le script :
# MIT License
#
# Original author: Zoltan Szarvas
# https://github.com/szazo/DHT11_Python
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#!/usr/bin/python3
import time
import sys
import RPi.GPIO as GPIO
# DHTxx sensor result returned by DHT.read() method'
class DHTResult:
ERR_NO_ERROR = 0
ERR_MISSING_DATA = 1
ERR_CRC = 2
ERR_NOT_FOUND = 3
error_code = ERR_NO_ERROR
temperature = -1
humidity = -1
def __init__(self, error_code, temperature, humidity):
self.error_code = error_code
self.temperature = temperature
self.humidity = humidity
def is_valid(self):
return self.error_code == DHTResult.ERR_NO_ERROR
# DHTxx sensor reader class for Raspberry'
class DHT:
__pin = 0
__isDht11 = True
def __init__(self, pin, isDht11):
self.__pin = pin
self.__isDht11 = isDht11
def read(self):
GPIO.setup(self.__pin, GPIO.OUT)
# send initial high
self.__send_and_sleep(GPIO.HIGH, 0.05)
# pull down to low
self.__send_and_sleep(GPIO.LOW, 0.02)
# change to input using pull up
GPIO.setup(self.__pin, GPIO.IN, GPIO.PUD_UP)
# collect data into an array
data = self.__collect_input()
# parse lengths of all data pull up periods
pull_up_lengths = self.__parse_data_pull_up_lengths(data)
if len(pull_up_lengths) == 0:
return DHTResult(DHTResult.ERR_NOT_FOUND, 0, 0)
# if bit count mismatch, return error (4 byte data + 1 byte checksum)
if len(pull_up_lengths) != 40:
return DHTResult(DHTResult.ERR_MISSING_DATA, 0, 0)
# calculate bits from lengths of the pull up periods
bits = self.__calculate_bits(pull_up_lengths)
# we have the bits, calculate bytes
the_bytes = self.__bits_to_bytes(bits)
# calculate checksum and check
checksum = self.__calculate_checksum(the_bytes)
if the_bytes[4] != checksum:
return DHTResult(DHTResult.ERR_CRC, 0, 0)
# ok, we have valid data
# The meaning of the return sensor values
# the_bytes[0]: humidity int
# the_bytes[1]: humidity decimal
# the_bytes[2]: temperature int
# the_bytes[3]: temperature decimal
temperature = -1
humidity = -1
if(self.__isDht11):
# DHT11
temperature = the_bytes[2] + float(the_bytes[3]) / 10
humidity = the_bytes[0] + float(the_bytes[1]) / 10
else:
#DHT22
temperature = (the_bytes[2] * 256 + float(the_bytes[3])) / 10
humidity = (the_bytes[0] * 256 + float(the_bytes[1])) / 10
c = (float)(((the_bytes[2]&0x7F)<< 8)+the_bytes[3])/10
if ( c > 125 ):
c = the_bytes[2]
if (the_bytes[2] & 0x80):
c = -c;
temperature = c
humidity = ((the_bytes[0]<<8)+the_bytes[1])/10.00
return DHTResult(DHTResult.ERR_NO_ERROR, temperature, humidity)
def __send_and_sleep(self, output, sleep):
GPIO.output(self.__pin, output)
time.sleep(sleep)
def __collect_input(self):
# collect the data while unchanged found
unchanged_count = 0
# this is used to determine where is the end of the data
max_unchanged_count = 100
last = -1
data = []
while True:
current = GPIO.input(self.__pin)
data.append(current)
if last != current:
unchanged_count = 0
last = current
else:
unchanged_count += 1
if unchanged_count > max_unchanged_count:
break
return data
def __parse_data_pull_up_lengths(self, data):
STATE_INIT_PULL_DOWN = 1
STATE_INIT_PULL_UP = 2
STATE_DATA_FIRST_PULL_DOWN = 3
STATE_DATA_PULL_UP = 4
STATE_DATA_PULL_DOWN = 5
state = STATE_INIT_PULL_DOWN
lengths = [] # will contain the lengths of data pull up periods
current_length = 0 # will contain the length of the previous period
for i in range(len(data)):
current = data[i]
current_length += 1
if state == STATE_INIT_PULL_DOWN:
if current == GPIO.LOW:
# ok, we got the initial pull down
state = STATE_INIT_PULL_UP
continue
else:
continue
if state == STATE_INIT_PULL_UP:
if current == GPIO.HIGH:
# ok, we got the initial pull up
state = STATE_DATA_FIRST_PULL_DOWN
continue
else:
continue
if state == STATE_DATA_FIRST_PULL_DOWN:
if current == GPIO.LOW:
# we have the initial pull down, the next will be the data pull up
state = STATE_DATA_PULL_UP
continue
else:
continue
if state == STATE_DATA_PULL_UP:
if current == GPIO.HIGH:
# data pulled up, the length of this pull up will determine whether it is 0 or 1
current_length = 0
state = STATE_DATA_PULL_DOWN
continue
else:
continue
if state == STATE_DATA_PULL_DOWN:
if current == GPIO.LOW:
# pulled down, we store the length of the previous pull up period
lengths.append(current_length)
state = STATE_DATA_PULL_UP
continue
else:
continue
return lengths
def __calculate_bits(self, pull_up_lengths):
# find shortest and longest period
shortest_pull_up = 1000
longest_pull_up = 0
for i in range(0, len(pull_up_lengths)):
length = pull_up_lengths[i]
if length < shortest_pull_up:
shortest_pull_up = length
if length > longest_pull_up:
longest_pull_up = length
# use the halfway to determine whether the period it is long or short
halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
bits = []
for i in range(0, len(pull_up_lengths)):
bit = False
if pull_up_lengths[i] > halfway:
bit = True
bits.append(bit)
return bits
def __bits_to_bytes(self, bits):
the_bytes = []
byte = 0
for i in range(0, len(bits)):
byte = byte << 1
if (bits[i]):
byte = byte | 1
else:
byte = byte | 0
if ((i + 1) % 8 == 0):
the_bytes.append(byte)
byte = 0
return the_bytes
def __calculate_checksum(self, the_bytes):
return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255
# Parse command line parameters
if len(sys.argv) == 3:
sensor = sys.argv[1]
if sensor not in ("11", "22", "2302"):
print('Sensor "{}" is not valid. Use 11, 22 or 2302'.format(sensor))
exit(1)
isDht11 = sensor == "11"
try:
pin = int(sys.argv[2])
if (pin < 2 or pin > 27):
raise ValueError
except:
print('Gpio {} is not valid'.format(pin))
exit(1)
else:
print('usage: dht.py [11|22|2302] [gpio#]')
exit(1)
# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
# read data
MAX_ATTEMPTS = 15
MAX_NOT_FOUND_ATTEMPTS = 3
dht = DHT(pin, isDht11)
result = dht.read()
not_found_attempts = 0
# make some attempts, because someone may not be successful
for x in range(0, MAX_ATTEMPTS):
if result.is_valid() or not_found_attempts == MAX_NOT_FOUND_ATTEMPTS:
break
else:
time.sleep(2)
result = dht.read()
if result.error_code == DHTResult.ERR_NOT_FOUND:
not_found_attempts += 1
else:
not_found_attempts = 0
# print result
if result.is_valid():
print('Temp: {0:0.1f} C Humidity: {1:0.1f} %'.format(result.temperature, result.humidity))
else:
print('Failed to get reading. Is the sensor connected? Is the pin number correct?')
# clean the gpio
GPIO.cleanup()
Je peux lancer le script en faisant :
source /home/pi/.venv/bin/activate
cd /home/pi
sudo .venv/bin/python3 ./dht.py 22 25
(ici 22 pour DHT22, et 25 pour le GPIO25)
@TODO :
* Modifier dht.py pour formater et renvoyer temp+humid en json
* Créer https://poul.fr.nf/info_dht.php qui récupère en json les infos de dht.py
* Modifier https://poul.fr.nf/info_proc.php pour qu'elle récupère les infos proc uniquement (plus la partie dht)
* Modifier l'interface index.php pour :
* Lancer un "pisto/put_info_proc_in_bdd_post.php" (tant qu'à faire ça donne un enregistrement plus récent) >>> finalement non, ça va ajouter pas mal de données en BDD pour rien (1 data par heure ça donne déjà 88k ligne en 10 ans, et si on reste sur le site ça ajoute des lignes toutes les 15s...)
* Récupérer température/humidité par info_dht.php
* Rapatrier les données via nextcloud-cmd
* Migrer sur le pi "en prod" (attention au path de .venv !)
* Modifier pisto/put_info_proc_in_bdd_post.php pour pointer sur cette nouvelle page info_dht.php
@TODO :
* Motion
* Mise en place caméra
* Mise en place timelapse (on move ?)
* MEP IR
@TODO :
* Récupérer les tutos de pistodocs\Perso\configs\Scripts\RPI-Poulailler
* log2ram (et modifs des fichiers logs pour /tmp)