Dimensionner et faire un tracker solaire photovolatïque low tech : Différence entre versions

 
(14 révisions intermédiaires par le même utilisateur non affichées)
Ligne 35 : Ligne 35 :
  
 
4€
 
4€
 
Moteur "Moteur électrique à engrenage à vis sans fin à couple élevé, engrenage en métal réversible, basse vitesse, angle droit, aimant en continu, CC, 12V, 24V" (Torque 6Nm)
 
 
53€
 
 
ou
 
  
 
Moteur "Moteur à engrenages CC à vis sans fin autobloquant, couple de bain, moteur de boîte de vitesses turbo en métal, inversé, basse vitesse, DC 12V, 24V, 200kg.cm" 62€
 
Moteur "Moteur à engrenages CC à vis sans fin autobloquant, couple de bain, moteur de boîte de vitesses turbo en métal, inversé, basse vitesse, DC 12V, 24V, 200kg.cm" 62€
Ligne 83 : Ligne 77 :
  
  
La théorie de wikipedia nous dit que le moment d'inertie selon l'axe Oz est donc :
+
La théorie de wikipedia nous dit que le moment d'inertie selon l'axe Oz est donc :
  
 
Jdelta=1/12*m*L (avec m masse du module et L longueur du module)
 
Jdelta=1/12*m*L (avec m masse du module et L longueur du module)
Ligne 92 : Ligne 86 :
  
  
On verifie maintenant experiementalement :
+
On verifie maintenant experiementalement :
  
 
Centrer le module sur le leve plaque et le mettre horizontalement.
 
Centrer le module sur le leve plaque et le mettre horizontalement.
Ligne 121 : Ligne 115 :
  
  
On mesure la rotation : dans notre cas, la distance de rotation à l'extrémité du module varie entre 3 et 10 cm. Les variations importantes
+
On mesure la rotation : dans notre cas, la distance de rotation à l'extrémité du module varie entre 3 et 10 cm. Les variations importantes
  
 
sont dus aux frotements de l'axe qui peut etre en force ou coulisser plus librement.
 
sont dus aux frotements de l'axe qui peut etre en force ou coulisser plus librement.
Ligne 169 : Ligne 163 :
  
  
On a donc :
+
On a donc :
  
 
Fp=1/2*1,2*1,8*2*v²=2,16*v²
 
Fp=1/2*1,2*1,8*2*v²=2,16*v²
Ligne 176 : Ligne 170 :
 
Chatgpt nous donne les abaques des vitesses de vent en km/h et leurs conversions en m/s et le nom generique en météorologie:
 
Chatgpt nous donne les abaques des vitesses de vent en km/h et leurs conversions en m/s et le nom generique en météorologie:
  
Calme : Moins de 1 km/h (Moins de 0.3 m/s)
+
Calme : Moins de 1 km/h (Moins de 0.3 m/s)
  
Très légère brise : 1-5 km/h (0.3-1.5 m/s)
+
Très légère brise : 1-5 km/h (0.3-1.5 m/s)
  
Légère brise : 6-11 km/h (1.6-3.0 m/s)
+
Légère brise : 6-11 km/h (1.6-3.0 m/s)
  
Petite brise : 12-19 km/h (3.4-5.4 m/s)
+
Petite brise : 12-19 km/h (3.4-5.4 m/s)
  
Jolie brise : 20-28 km/h (5.5-7.9 m/s)
+
Jolie brise : 20-28 km/h (5.5-7.9 m/s)
  
Bonne brise : 29-38 km/h (8.0-10.7 m/s)
+
Bonne brise : 29-38 km/h (8.0-10.7 m/s)
  
Vent frais : 39-49 km/h (10.8-13.8 m/s)
+
Vent frais : 39-49 km/h (10.8-13.8 m/s)
  
Vent modéré : 50-61 km/h (13.9-16.9 m/s)
+
Vent modéré : 50-61 km/h (13.9-16.9 m/s)
  
Vent assez fort : 62-74 km/h (17.2-20.6 m/s)
+
Vent assez fort : 62-74 km/h (17.2-20.6 m/s)
  
Fort vent : 75-88 km/h (20.8-24.4 m/s)
+
Fort vent : 75-88 km/h (20.8-24.4 m/s)
  
Tempête : 89-102 km/h (24.7-28.3 m/s)
+
Tempête : 89-102 km/h (24.7-28.3 m/s)
  
Violente tempête : 103-117 km/h (28.6-32.5 m/s)
+
Violente tempête : 103-117 km/h (28.6-32.5 m/s)
  
Ouragan : Au moins 118 km/h (Au moins 32.8 m/s)
+
Ouragan : Au moins 118 km/h (Au moins 32.8 m/s)
  
  
Ligne 217 : Ligne 211 :
 
Si vous souhaitez construire un tracker qui resiste donc à des conditions de tempete, il est conseillé de dimensionner le tracker en conséquence
 
Si vous souhaitez construire un tracker qui resiste donc à des conditions de tempete, il est conseillé de dimensionner le tracker en conséquence
  
d'une part avec des attaches au sol suffisante, d'autres part avec une armature au dos des modules pour les attacher adaptée, et désactivant le tracking
+
d'une part avec des attaches au sol suffisante, d'autres part avec une armature adaptée -voir astuce caravane a la fin de cette etape-, et désactiver
  
lors des vents de tempetes ou plus important.
+
lors des vents de tempetes ou plus important.)
  
  
Ligne 271 : Ligne 265 :
 
On va donc à utiliser un moteur pas à pas ou un moteur à engrenage avec vis sans fin (test de résistance non alimenté à réception des pièces) commandé par un raspberry pi (mais le porte plaque etant sur roulette, on prendra la précaution de ranger le tracker en cas de tempete ;))  
 
On va donc à utiliser un moteur pas à pas ou un moteur à engrenage avec vis sans fin (test de résistance non alimenté à réception des pièces) commandé par un raspberry pi (mais le porte plaque etant sur roulette, on prendra la précaution de ranger le tracker en cas de tempete ;))  
  
 +
 +
Astuce caravane: pour dimensionner l'épaisseur de l'armature en métal, les normes modernes semblent être moins faites sur des bases scientifiques que sur des bases commerciales pour faite vendre. On pourra donc utilement mesurer l'épaisseur de vieux matériel (années 70). Par exemple l'armature de cette vieille caravane homologuée carte grise 750kg a une armature métal de 5mm d'épaisseur. On pourra ensuite faire une règle de 3 pour vérifier que l'épaisseur de notre armature est convenable.
 +
 +
C'est rustique (les calculs de resistance des matériaux rigoureux sont complexes), mais ca permet d'avoir une première approximation "a visto de nas" comme on dit en gascon<br />
 
<br /></translate>
 
<br /></translate>
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie1.jpg
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie1.jpg
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie2.jpg
+
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie3.jpg
|Step_Picture_02=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie3.jpg
+
|Step_Picture_02=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie4.jpg
|Step_Picture_03=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie4.jpg
+
|Step_Picture_03=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie5.jpg
|Step_Picture_04=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie5.jpg
+
|Step_Picture_04=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie6.jpg
|Step_Picture_05=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_momentinertie6.jpg
+
|Step_Picture_05=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_IMG_20240530_093819.jpg
 +
}}
 +
{{Tuto Step
 +
|Step_Title=<translate>Test la resistance des moteurs</translate>
 +
|Step_Content=<translate>Voir vidéos
 +
 
 +
 
 +
Le moteur rotatif ("Moteur à engrenages CC à vis sans fin autobloquant, couple de bain, moteur de boîte de vitesses turbo en métal, inversé, basse vitesse, DC 12V, 24V, 200kg.cm" sur aliexpress) tient bien à 20Nmà vide hors tension et en rotation sous tension.
 +
 
 +
 
 +
Rappel : torque ou couple = moment d'inertie engendré par le moteur
 +
 
 +
=force*distance à l'axe du moteur
 +
 
 +
 
 +
ici 4kg à 0,5m=9,8*4*0,5=20Nm (en ordre de grandeur)</translate>
 +
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_resistance_a_vide_VID_20240529_185214.mp4
 +
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_resistance_sous_tension_VID_20240529_185517.mp4
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
Ligne 384 : Ligne 399 :
 
|Step_Content=<translate>Après observation des éléments bloquant, on va modifier la potence pour éviter la butée lorsque le soleil a un degré d'altitude inférieur à 27°.
 
|Step_Content=<translate>Après observation des éléments bloquant, on va modifier la potence pour éviter la butée lorsque le soleil a un degré d'altitude inférieur à 27°.
  
Pour cela on va :  
+
Pour cela on va :
  
 
-laisser passer le ressort en évidant la potence (pour éviter que le ressort fasse butée)
 
-laisser passer le ressort en évidant la potence (pour éviter que le ressort fasse butée)
  
-augmenter l'angle en abaissant la fixation du ressort en perçant la potence  
+
-augmenter l'angle en abaissant la fixation du ressort en perçant la potence
  
-augmenter l'angle en coupant les bords de la potence  
+
-augmenter l'angle en coupant les bords de la potence
  
  
Voir photos :  
+
Voir photos :
  
1/2: observation dessus/dessous  
+
1/2: observation dessus/dessous
  
 
3/4: demontage potence
 
3/4: demontage potence
Ligne 411 : Ligne 426 :
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=<translate>Etaloner la commande du verin et ajuster sa fixation</translate>
+
|Step_Title=<translate>Tester les commandes du verin et du moteur rotatif</translate>
 
|Step_Content=<translate>Pour controller le verin, on va utiliser un raspberry pi, l'ordinateur monocarte le plus répandu.
 
|Step_Content=<translate>Pour controller le verin, on va utiliser un raspberry pi, l'ordinateur monocarte le plus répandu.
 
Il est doté de d'une série 40 pins, qu'on peut connecter à divers appareils, appelés "controlleur GPIO".
 
Il est doté de d'une série 40 pins, qu'on peut connecter à divers appareils, appelés "controlleur GPIO".
Ligne 420 : Ligne 435 :
 
-système d'exploitation :  
 
-système d'exploitation :  
  
*dietpi (voir mon autre tuto ici pour l'installation et
+
*dietpi images (debian) disponibles ici:  https://dietpi.com/#download  (voir mon autre tuto ici pour l'installation et l'activation du wifi : https://wiki.lowtechlab.org/wiki/Serveur_orangepi-raspberry_nextcloud_en_photovolta%C3%AFque_autonome)
 
 
l'activation du wifi : https://wiki.lowtechlab.org/wiki/Serveur_orangepi-raspberry_nextcloud_en_photovolta%C3%AFque_autonome)
 
 
 
*raspberry pi os (systeme d'exploitation par défaut)
 
  
Archives et versions de systemes d'exploitation peu "polluées" symboliquement
+
*raspberry pi os (systeme d'exploitation par défaut) images disponibles ici: https://www.raspberrypi.com/software/operating-systems/
  
et/ou utilie pour la rétrocompatibilité:
+
Archives et versions de systemes d'exploitation utiles pour la rétrocompatibilité (tout lecteur soucieux de l'informatique low tech est incité à télécharger et garder des copies en local de ces archives et les partager en torrent!):
  
 
<nowiki>*</nowiki>dietpi:
 
<nowiki>*</nowiki>dietpi:
Ligne 442 : Ligne 453 :
 
attention à bien mettre a jour votre /etc/apt/sources.list en faisant:
 
attention à bien mettre a jour votre /etc/apt/sources.list en faisant:
 
<pre>echo "deb http://legacy.raspbian.org/raspbian/ wheezy main contrib non-free rpi" >> /etc/apt/sources.list</pre>
 
<pre>echo "deb http://legacy.raspbian.org/raspbian/ wheezy main contrib non-free rpi" >> /etc/apt/sources.list</pre>
 +
 +
NB: ChatGPT nous donne les dates suivantes de sortie des raspberry:
 +
 +
#'''Raspberry Pi Model B''' : 29 février 2012
 +
#'''Raspberry Pi Model A''' : 4 février 2013
 +
#'''Raspberry Pi Model B+''' : 14 juillet 2014
 +
#'''Raspberry Pi Model A+''' : 10 novembre 2014
 +
#'''Raspberry Pi 2 Model B''' : 2 février 2015
 +
#'''Raspberry Pi Zero''' : 30 novembre 2015
 +
#'''Raspberry Pi 3 Model B''' : 29 février 2016
 +
#'''Raspberry Pi Zero W''' : 28 février 2017
 +
#'''Raspberry Pi 3 Model B+''' : 14 mars 2018
 +
#'''Raspberry Pi 3 Model A+''' : 15 novembre 2018
 +
#'''Raspberry Pi 4 Model B''' : 24 juin 2019
 +
#'''Raspberry Pi 400''' : 2 novembre 2020
 +
#'''Raspberry Pi Pico''' : 21 janvier 2021
 +
#'''Raspberry Pi Zero 2 W''' : 28 octobre 2021
 +
 +
On espère que c'est vrai (ca vient de chatgpt), mais vous pouvez vérifier sur ce qui est écrit sur le pcb de votre carte. 
 +
 +
Pour vérifier la version de votre raspberry sous raspberry pi os si vous ne savez pas quelle version cest(voir photo):
 +
 +
<pre>sudo usermod -a G gpio pi
 +
pinout</pre>
 +
 +
Ajustez avec le système d'exploitation qui vous parait le plus pertinent au regard des filtres de pertinence que vous utilisez. Vous avez la liste des anciennes versions d'os de dietpi et de raspberry pi os au cas où les versions les plus récentes ne fonctionneraient plus (et je me repette : tout lecteur soucieux de l'informatique low tech est incité à télécharger et garder des copies en local de ces archives et les partager en torrent!). 
  
 
Pour l'install, comme d'habitude, telecharger balenaetcher, flasher une clé usb avec l'image téléchargée,
 
Pour l'install, comme d'habitude, telecharger balenaetcher, flasher une clé usb avec l'image téléchargée,
Ligne 482 : Ligne 519 :
 
Pour cela on peut chercher les infos sur internet:  
 
Pour cela on peut chercher les infos sur internet:  
 
https://wiki.lowtechlab.org/wiki/Serveur_orangepi-raspberry_nextcloud_en_photovolta%C3%AFque_autonome
 
https://wiki.lowtechlab.org/wiki/Serveur_orangepi-raspberry_nextcloud_en_photovolta%C3%AFque_autonome
ou taper la commande "pinout" dans raspberry pi os (voir image)
+
ou taper les commande suivantes dans raspberry pi os (voir image)
 +
 
 +
<pre>
 +
sudo usermod -aG gpio votre_user
 +
pinout
 +
</pre>
  
 
Dans ce tuto, que ce soit avec gpiozero ou RPi.GPIO, on utilisera la numerotation GPIO et pas la numérotation pin
 
Dans ce tuto, que ce soit avec gpiozero ou RPi.GPIO, on utilisera la numerotation GPIO et pas la numérotation pin
Ligne 489 : Ligne 531 :
 
On branche les GPIO 6 et 7 (Ground, la terre) aux deux fiches GND du HBrdige
 
On branche les GPIO 6 et 7 (Ground, la terre) aux deux fiches GND du HBrdige
 
On branche les GPIO 23 et 16 à l'interrupteur 3 du HBridge  
 
On branche les GPIO 23 et 16 à l'interrupteur 3 du HBridge  
 +
Pour tester le "enable" du HBridge, on branche les GPIO 20 et 21 au ENA du HBridge
  
 
On  utilise ensuite les scripts suivants :  
 
On  utilise ensuite les scripts suivants :  
  
 
<pre>
 
<pre>
import sys
 
 
import time
 
import time
 
import RPi.GPIO as GPIO
 
import RPi.GPIO as GPIO
 +
import gpiozero
 +
 +
 +
def forwardzero(wait):
 +
    led16=gpiozero.LED(16) #motor1
 +
    led23=gpiozero.LED(23) #motor1
 +
    led20=gpiozero.LED(20) #enable
 +
    led21=gpiozero.LED(21) #enable
 +
    led1=gpiozero.LED(1)
 +
    led7=gpiozero.LED(7)
 +
    try:
 +
        print("forwardzero")
 +
        #cas où le hbridge necessite un signal enable
 +
        led21.off()
 +
        led20.on()
 +
        #signal à zero sur interupteur 2
 +
        led1.off()
 +
        led7.off()
 +
        #mettre un signal sur l'interrupteur 1
 +
        led23.on()
 +
        led16.off()
 +
        #laisser le signal actif le temps de wait
 +
        for k in range(wait):
 +
            print(k)
 +
            time.sleep(1)
 +
        #eteindre le signal sur l'interrupteur
 +
        led23.off()
 +
        led20.off()
 +
    except KeyboardnInterrupt:
 +
        print("keyboard interrupt")
 +
    except Exception as err:
 +
        print(err)
 +
    finally:
 +
        print("zero")
 +
        #signal à zero sur interupteur 2
 +
        led1.off()
 +
        led7.off()
 +
        #signal à zero sur l'interrupteur 1
 +
        led16.off()
 +
        led23.off()
 +
        #signal à zero sur la fiche enable
 +
        led20.off()
 +
        led21.off()
 +
def backwardzero(wait):
 +
    led1=gpiozero.LED(1)
 +
    led7=gpiozero.LED(7)
 +
    led16=gpiozero.LED(16) #motor1
 +
    led23=gpiozero.LED(23) #motor1
 +
    led20=gpiozero.LED(20) #enable
 +
    led21=gpiozero.LED(21) #enable
 +
    try:
 +
        print("backwardzero")
 +
        #signal zero sur interupteur 1
 +
        led16.on()
 +
        led23.off()
 +
        #cas où le hbrige necessite un signal sur enable
 +
        led20.off()
 +
        led21.on()
 +
        #signal sur interrupteur 2
 +
        led1.off()
 +
        led7.off()
 +
        #wait
 +
        for k in range(wait):
 +
            print(k)
 +
            time.sleep(1)
 +
        #signal off sur interrupteur
 +
        led16.off()
 +
        led21.off()
 +
    except KeyboardInterrupt:
 +
        print("keyboardinterrupt")
 +
    except Exception as err:
 +
        print(err)
 +
    finally:
 +
        print("zero")
 +
        #signal à zero sur interupteur 1
 +
        led16.off()
 +
        led23.off()
 +
        #signal à zero sur l'interupteur 2
 +
        led1.off()
 +
        led7.off()
 +
        #signal à zero sur la fiche enable
 +
        led20.off()
 +
        led21.off()
 +
  
GPIO.setmode(GPIO.BCM)
 
 
#pin16 gpio23
 
#pin16 gpio23
 
#pin36 gpio25
 
#pin36 gpio25
Ligne 503 : Ligne 628 :
 
def forward(wait):
 
def forward(wait):
 
     GPIO.setmode(GPIO.BCM)
 
     GPIO.setmode(GPIO.BCM)
     GPIO.setup(16,GPIO.OUT)
+
     GPIO.setup(16,GPIO.OUT) #motor1
     GPIO.setup(23,GPIO.OUT)
+
     GPIO.setup(23,GPIO.OUT) #motor1
 +
    GPIO.setup(20,GPIO.OUT) #enable
 +
    GPIO.setup(21,GPIO.OUT) #enable
 +
    GPIO.setup(1,GPIO.OUT)  #motor2
 +
    GPIO.setup(7,GPIO.OUT)  #motor2
 
     try:
 
     try:
 
         print("forward")
 
         print("forward")
 +
        GPIO.output(1,GPIO.HIGH)
 +
        GPIO.output(7,GPIO.LOW)
 +
        GPIO.output(21,GPIO.HIGH)
 +
        GPIO.output(20,GPIO.LOW)
 
         GPIO.output(23,GPIO.LOW)
 
         GPIO.output(23,GPIO.LOW)
        GPIO.output(16,GPIO.HIGH)
 
        time.sleep(wait)
 
 
         GPIO.output(16,GPIO.LOW)
 
         GPIO.output(16,GPIO.LOW)
 +
        for k in range(wait):
 +
            print(k)
 +
            time.sleep(1)
 +
        GPIO.output(1,GPIO.LOW)
 +
        GPIO.output(21,GPIO.LOW)
 
     except KeyboardInterrupt:
 
     except KeyboardInterrupt:
 
         print("keyboard interrupt")
 
         print("keyboard interrupt")
Ligne 516 : Ligne 652 :
 
         print(err)
 
         print(err)
 
     finally:
 
     finally:
         print("clean up")
+
         print("zero")
         GPIO.cleanup()
+
         GPIO.output(1,GPIO.LOW)
 +
        GPIO.output(7,GPIO.LOW)
 +
        GPIO.output(20,GPIO.LOW)
 +
        GPIO.output(21,GPIO.LOW)
 +
        GPIO.output(23,GPIO.LOW)
 +
        GPIO.output(16,GPIO.LOW)
 +
        #GPIO.cleanup() chez moi, ca n'enleve pas les +3V
 
def backward(wait):
 
def backward(wait):
 
     GPIO.setmode(GPIO.BCM)
 
     GPIO.setmode(GPIO.BCM)
     GPIO.setup(16,GPIO.OUT)
+
    GPIO.setup(20,GPIO.OUT) #enable
     GPIO.setup(23,GPIO.OUT)
+
    GPIO.setup(21,GPIO.OUT) #enable
 +
    GPIO.setup(1,GPIO.OUT)  #motor2
 +
    GPIO.setup(7,GPIO.OUT)  #motor2
 +
     GPIO.setup(16,GPIO.OUT) #motor1
 +
     GPIO.setup(23,GPIO.OUT) #motor1
 
     try:
 
     try:
 
         print("backward")
 
         print("backward")
 +
        GPIO.output(21,GPIO.HIGH)
 +
        GPIO.output(20,GPIO.LOW)
 
         GPIO.output(16,GPIO.LOW)
 
         GPIO.output(16,GPIO.LOW)
        GPIO.output(23,GPIO.HIGH)
 
        time.sleep(wait)
 
 
         GPIO.output(23,GPIO.LOW)
 
         GPIO.output(23,GPIO.LOW)
 +
        GPIO.output(7,GPIO.HIGH)
 +
        GPIO.output(1,GPIO.LOW)
 +
        for k in range(wait):
 +
            print(k)
 +
            time.sleep(1)
 +
        GPIO.output(7,GPIO.LOW)
 +
        GPIO.output(21,GPIO.HIGH)
 
     except KeyboardInterrupt:
 
     except KeyboardInterrupt:
 
         print("keyboardinterrupt")
 
         print("keyboardinterrupt")
Ligne 533 : Ligne 686 :
 
         print(err)
 
         print(err)
 
     finally:
 
     finally:
         print("clean up")
+
         print("zero")
         GPIO.cleanup()
+
         GPIO.output(20,GPIO.LOW)
forward(10)
+
        GPIO.output(21,GPIO.LOW)
backward(10)
+
        GPIO.output(16,GPIO.LOW)
 
+
        GPIO.output(23,GPIO.LOW)
 +
        GPIO.output(1,GPIO.LOW)
 +
        GPIO.output(7,GPIO.LOW)
 +
        #GPIO.cleanup() chez moi ca n'enleve pas les +3V
 +
forward(2) #rotation horaire motor2
 +
backward(2) #rotation antihoraire motor2
 +
forwardzero(10) #verin extension motor1 111 max
 +
backwardzero(10) #verin retractation motor1 107 max
 
</pre>
 
</pre>
  
Ligne 543 : Ligne 703 :
 
GPIO.HIGH envoie un signal de +3V dans la fiche concernée
 
GPIO.HIGH envoie un signal de +3V dans la fiche concernée
 
GPIO.LOW remet le signal à 0V (GND, la terre)
 
GPIO.LOW remet le signal à 0V (GND, la terre)
Mes tests au voltmetre montrent que le signal est par défaut à +3V dans
+
gpiozero fait la meme chose avec les methodes .on() et .off()
les fiches GPIO, y compris après un GPIO.cleanup(), ce qui semblerait
+
 
indiquer qu'il faut ajuster le script ci-dessus.
+
Update 31.05.24 : vous l'attendiez, voilà l'update du jour avec le hbridge made in europe, ca roule, voir vidéo! :)
Cependant, quelques tests m'ont permis d'activer le verin dans un sens et pas dans l'autre,  
+
Update de l'étalonnage rapidement à reception d'un truc pour mesurer les angles un peu pratique.
ce qui semble me rappeler des discussions avec un vendeur "Dootyfree" sur alixpress
+
 
qui m'a envoyé des promos pour des ciseaux électrique
+
Remarquez que les fiches 20 et 21 ne sont pas branchées au hbridge.
 +
Le "pwm" (pulse width modulation) n'est pas utilisé
 +
pour "activer" (enable dans le code) le moteur car des cavaliers placés sur les
 +
deux fiches ENA du hbridge suffisent (ici on n'a pas besoin de moduler la vitesse du moteur).</translate>
 +
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_apt_install.jpg
 +
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_pinout.jpg
 +
|Step_Picture_02=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_cablage_pi.JPG
 +
|Step_Picture_03=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_moteurs_pi.mp4
 +
}}
 +
{{Tuto Step
 +
|Step_Title=<translate>Fixer les poulies et la transmission 1:100 pour le moteur rotatif</translate>
 +
|Step_Content=<translate>On commence par récupérer deux pédaliers sur des vélos d'occasion à l'ébarbeuse en prenant soin de garder une tige du cadre.
 +
 
 +
 
 +
On va venir y souder les pignons 92T (92 dents).
 +
 
 +
On soude ensuite les pignons 8T (8 dents) dessus.
 +
 
 +
On soude un pignon 8T sur un pignon avec une clavette qui s'adapte à l'axe du moteur.
 +
 
 +
On découpe une tige en fer et on soude un aplat le long de l'axe du lève plaque sur lequel on va venir fixer avec des boulons les tiges découpées dans les cadres de vélo qui prolongent le pédalier ainsi qu'une tige sur laquelle on va fixer le moteur.
 +
 
 +
On assemble et on boulonne.
 +
 
 +
Update à réceptiond de la 3eme chaine et en attendant de réfléchir à un moyen de régler les tensions de chaine.
 +
 
 +
 
 +
Update du 11.6.24: les contraintes de l'axe du leve plaque font qu'on ne peut y fixer un grand pignon pour bénéficier d'un rapport de réduction favorable sur la derniere chaine de transmission. Malgré les réductions des transmissions des autres poulies, le lève plaque n'est pas entrainé en rotation (voir vidéo).
 +
 
 +
update du 16.6.24: réception du verin supplémentaire, date livraison estimée : 28 juin-2juillet
 +
 
 +
 
 +
On va donc entrainer la rotation avec deux verins. Update à réception des verins fin juin (en stage et non dispo pour update ces prochaines semaines)</translate>
 +
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_IMG_20240605_134410.jpg
 +
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_IMG_20240605_174848.jpg
 +
|Step_Picture_02=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_IMG_20240605_175050.jpg
 +
|Step_Picture_03=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_IMG_20240605_202715.jpg
 +
|Step_Picture_04=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_Untitled_Project.mp4
 +
}}
 +
{{Tuto Step
 +
|Step_Title=<translate>Etalonner les moteurs</translate>
 +
|Step_Content=<translate>Le code mis à jour est le suivant: on définit des dictionnaire qui associe chaque angle recherché à un temps d'activation du moteur (à tester à la main et à mesurer)
 +
 
 +
<pre>
 +
#Etalonnage
 +
#dict_angle_verin= sun_degre_horizontal:motoractivationtime
 +
dict_angle_verin={1:0,
 +
                2:0,
 +
                3:0,
 +
                4:0,
 +
                5:0,
 +
                6:0,
 +
                7:0,
 +
                8:0,
 +
                9:0,
 +
                10:0,
 +
                11:1,
 +
                12:1,
 +
                13:2,
 +
                14:3,
 +
                15:4,
 +
                16:5,
 +
                17:6,
 +
                18:7,
 +
                19:8,
 +
                20:9,
 +
                21:10,
 +
                22:11,
 +
                23:12,
 +
                24:13,
 +
                25:14,
 +
                26:15,
 +
                27:16,
 +
                28:16,
 +
                29:17,
 +
                30:18,
 +
                31:19,
 +
                32:20,
 +
                33:21,
 +
                34:22,
 +
                35:23,
 +
                36:24,
 +
                37:25,
 +
                38:27,
 +
                39:28,
 +
                40:29,
 +
                41:30,
 +
                42:32,
 +
                43:33,
 +
                44:34,
 +
                45:35,
 +
                46:37,
 +
                47:38,
 +
                48:39,
 +
                49:41,
 +
                50:42,
 +
                51:44,
 +
                52:45,
 +
                53:47,
 +
                54:48,
 +
                55:50,
 +
                56:51,
 +
                57:52,
 +
                58:54,
 +
                59:56,
 +
                60:58,
 +
                61:59,
 +
                62:59,
 +
                63:59,
 +
                64:60,
 +
                65:63,
 +
                66:64,
 +
                67:65,
 +
                68:66,
 +
                69:68,
 +
                70:68,
 +
                71:71,
 +
                72:73,
 +
                73:75,
 +
                74:77,
 +
                75:79,
 +
                76:81,
 +
                77:82,
 +
                78:83,
 +
                79:85,
 +
                80:86,
 +
                81:87,
 +
                82:89,
 +
                83:91,
 +
                84:94,
 +
                85:96,
 +
                86:98,
 +
                87:100}
 +
</pre>
 +
 
 +
update etalonnage moteur en rotation à reception de la 3eme chaine</translate>
 +
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_IMG_20240604_134029.jpg
 +
}}
 +
{{Tuto Step
 +
|Step_Title=<translate>Coder le tracking en "dur"</translate>
 +
|Step_Content=<translate>Le code mis à jour est le suivant
 +
 
 +
<pre>
 +
import time
 +
import RPi.GPIO as GPIO
 +
import gpiozero
 +
import ephem
 +
 
 +
def forwardzero(wait):
 +
    led16=gpiozero.LED(16) #motor1
 +
    led23=gpiozero.LED(23) #motor1
 +
    led20=gpiozero.LED(20) #enable
 +
    led21=gpiozero.LED(21) #enable
 +
    led1=gpiozero.LED(1)
 +
    led7=gpiozero.LED(7)
 +
    try:
 +
        print("forwardzero")
 +
        #cas où le hbridge necessite un signal enable
 +
        led21.on()
 +
        led20.off()
 +
        #signal à zero sur interupteur 2
 +
        led1.off()
 +
        led7.off()
 +
        #mettre un signal sur l'interrupteur 1
 +
        led23.on()
 +
        led16.off()
 +
        #laisser le signal actif le temps de wait
 +
        for k in range(wait):
 +
            print(k)
 +
            time.sleep(1)
 +
        #eteindre le signal sur l'interrupteur
 +
        led23.off()
 +
        led21.off()
 +
    except KeyboardnInterrupt:
 +
        print("keyboard interrupt")
 +
    except Exception as err:
 +
        print(err)
 +
    finally:
 +
        print("zero")
 +
        #signal à zero sur interupteur 2
 +
        led1.off()
 +
        led7.off()
 +
        #signal à zero sur l'interrupteur 1
 +
        led16.off()
 +
        led23.off()
 +
        #signal à zero sur la fiche enable
 +
        led20.off()
 +
        led21.off()
 +
def backwardzero(wait):
 +
    led1=gpiozero.LED(1)
 +
    led7=gpiozero.LED(7)
 +
    led16=gpiozero.LED(16) #motor1
 +
    led23=gpiozero.LED(23) #motor1
 +
    led20=gpiozero.LED(20) #enable
 +
    led21=gpiozero.LED(21) #enable
 +
    try:
 +
        print("backwardzero")
 +
        #signal zero sur interupteur 1
 +
        led16.on()
 +
        led23.off()
 +
        #cas où le hbrige necessite un signal sur enable
 +
        led20.off()
 +
        led21.on()
 +
        #signal sur interrupteur 2
 +
        led1.off()
 +
        led7.off()
 +
        #wait
 +
        for k in range(wait):
 +
            print(k)
 +
            time.sleep(1)
 +
        #signal off sur interrupteur
 +
        led16.off()
 +
        led21.off()
 +
    except KeyboardInterrupt:
 +
        print("keyboardinterrupt")
 +
    except Exception as err:
 +
        print(err)
 +
    finally:
 +
        print("zero")
 +
        #signal à zero sur interupteur 1
 +
        led16.off()
 +
        led23.off()
 +
        #signal à zero sur l'interupteur 2
 +
        led1.off()
 +
        led7.off()
 +
        #signal à zero sur la fiche enable
 +
        led20.off()
 +
        led21.off()
 +
 
 +
 
 +
#pin16 gpio23
 +
#pin36 gpio25
 +
 
 +
def forward(wait):
 +
    GPIO.setmode(GPIO.BCM)
 +
    GPIO.setup(16,GPIO.OUT) #motor1
 +
    GPIO.setup(23,GPIO.OUT) #motor1
 +
    GPIO.setup(20,GPIO.OUT) #enable
 +
    GPIO.setup(21,GPIO.OUT) #enable
 +
    GPIO.setup(1,GPIO.OUT)  #motor2
 +
    GPIO.setup(7,GPIO.OUT)  #motor2
 +
    try:
 +
        print("forward")
 +
        GPIO.output(1,GPIO.HIGH)
 +
        GPIO.output(7,GPIO.LOW)
 +
        GPIO.output(21,GPIO.HIGH)
 +
        GPIO.output(20,GPIO.LOW)
 +
        GPIO.output(23,GPIO.LOW)
 +
        GPIO.output(16,GPIO.LOW)
 +
        for k in range(wait):
 +
            print(k)
 +
            time.sleep(1)
 +
        GPIO.output(1,GPIO.LOW)
 +
        GPIO.output(21,GPIO.LOW)
 +
    except KeyboardInterrupt:
 +
        print("keyboard interrupt")
 +
    except Exception as err:
 +
        print(err)
 +
    finally:
 +
        print("zero")
 +
        GPIO.output(1,GPIO.LOW)
 +
        GPIO.output(7,GPIO.LOW)
 +
        GPIO.output(20,GPIO.LOW)
 +
        GPIO.output(21,GPIO.LOW)
 +
        GPIO.output(23,GPIO.LOW)
 +
        GPIO.output(16,GPIO.LOW)
 +
        #GPIO.cleanup() chez moi, ca n'enleve pas les +3V
 +
def backward(wait):
 +
    GPIO.setmode(GPIO.BCM)
 +
    GPIO.setup(20,GPIO.OUT) #enable
 +
    GPIO.setup(21,GPIO.OUT) #enable
 +
    GPIO.setup(1,GPIO.OUT)  #motor2
 +
    GPIO.setup(7,GPIO.OUT)  #motor2
 +
    GPIO.setup(16,GPIO.OUT) #motor1
 +
    GPIO.setup(23,GPIO.OUT) #motor1
 +
    try:
 +
        print("backward")
 +
        GPIO.output(21,GPIO.HIGH)
 +
        GPIO.output(20,GPIO.LOW)
 +
        GPIO.output(16,GPIO.LOW)
 +
        GPIO.output(23,GPIO.LOW)
 +
        GPIO.output(7,GPIO.HIGH)
 +
        GPIO.output(1,GPIO.LOW)
 +
        for k in range(wait):
 +
            print(k)
 +
            time.sleep(1)
 +
        GPIO.output(7,GPIO.LOW)
 +
        GPIO.output(21,GPIO.HIGH)
 +
    except KeyboardInterrupt:
 +
        print("keyboardinterrupt")
 +
    except Exception as err:
 +
        print(err)
 +
    finally:
 +
        print("zero")
 +
        GPIO.output(20,GPIO.LOW)
 +
        GPIO.output(21,GPIO.LOW)
 +
        GPIO.output(16,GPIO.LOW)
 +
        GPIO.output(23,GPIO.LOW)
 +
        GPIO.output(1,GPIO.LOW)
 +
        GPIO.output(7,GPIO.LOW)
 +
        #GPIO.cleanup() chez moi ca n'enleve pas les +3V
 +
forward(2) #rotation horaire motor2
 +
backward(2) #rotation antihoraire motor2
 +
forwardzero(10) #verin extension motor1 111 max
 +
backwardzero(10) #verin retractation motor1 107 max
 +
 
 +
#Etalonnage
 +
#dict_angle_verin= sun_degre_horizontal:motoractivationtime
 +
dict_angle_verin={1:1,
 +
                2:2,
 +
                3:3,
 +
                4:4,
 +
                5:5,
 +
                6:6,
 +
                7:7,
 +
                8:8,
 +
                9:9,
 +
                10:10,
 +
                11:11,
 +
                12:12,
 +
                13:13,
 +
                14:14,
 +
                15:15,
 +
                16:16,
 +
                17:17,
 +
                18:18,
 +
                19:19,
 +
                20:20,
 +
                21:21,
 +
                22:22,
 +
                23:23,
 +
                24:24,
 +
                25:25,
 +
                26:26,
 +
                27:27,
 +
                28:28,
 +
                29:29,
 +
                30:30,
 +
                31:31,
 +
                32:32,
 +
                33:33,
 +
                34:34,
 +
                35:35,
 +
                36:36,
 +
                37:37,
 +
                38:38,
 +
                39:39,
 +
                40:40,
 +
                41:41,
 +
                42:42,
 +
                43:43,
 +
                44:44,
 +
                45:45,
 +
                46:46,
 +
                47:47,
 +
                48:48,
 +
                49:49,
 +
                50:50,
 +
                51:51,
 +
                52:52,
 +
                53:53,
 +
                54:54,
 +
                55:55,
 +
                56:56,
 +
                57:57,
 +
                58:58,
 +
                59:59,
 +
                60:60,
 +
                61:61,
 +
                62:62,
 +
                63:63,
 +
                64:64,
 +
                65:65,
 +
                66:66,
 +
                67:67,
 +
                68:68,
 +
                69:69,
 +
                70:70,
 +
                71:71,
 +
                72:72,
 +
                73:73,
 +
                74:74,
 +
                75:75,
 +
                76:76,
 +
                77:77,
 +
                78:78,
 +
                79:79,
 +
                80:80,
 +
                81:81,
 +
                82:82,
 +
                83:83,
 +
                84:84,
 +
                85:85,
 +
                86:86,
 +
                87:87,
 +
                88:88,
 +
                89:89,
 +
                90:90}
 +
 
 +
#dict_angle_rotation= sun_degre_azimut:motoractivationtime
 +
dict_angle_rotation={1:0,
 +
                2:2,
 +
                3:3,
 +
                4:4,
 +
                5:5,
 +
                6:6,
 +
                7:7,
 +
                8:8,
 +
                9:9,
 +
                10:10,
 +
                11:11,
 +
                12:12,
 +
                13:13,
 +
                14:14,
 +
                15:15,
 +
                16:16,
 +
                17:17,
 +
                18:18,
 +
                19:19,
 +
                20:20,
 +
                21:21,
 +
                22:22,
 +
                23:23,
 +
                24:24,
 +
                25:25,
 +
                26:26,
 +
                27:27,
 +
                28:28,
 +
                29:29,
 +
                30:30,
 +
                31:31,
 +
                32:32,
 +
                33:33,
 +
                34:34,
 +
                35:35,
 +
                36:36,
 +
                37:37,
 +
                38:38,
 +
                39:39,
 +
                40:40,
 +
                41:41,
 +
                42:42,
 +
                43:43,
 +
                44:44,
 +
                45:45,
 +
                46:46,
 +
                47:47,
 +
                48:48,
 +
                49:49,
 +
                50:50,
 +
                51:51,
 +
                52:52,
 +
                53:53,
 +
                54:54,
 +
                55:55,
 +
                56:56,
 +
                57:57,
 +
                58:58,
 +
                59:59,
 +
                60:60,
 +
                61:61,
 +
                62:62,
 +
                63:63,
 +
                64:64,
 +
                65:65,
 +
                66:66,
 +
                67:67,
 +
                68:68,
 +
                69:69,
 +
                70:70,
 +
                71:71,
 +
                72:72,
 +
                73:73,
 +
                74:74,
 +
                75:75,
 +
                76:76,
 +
                77:77,
 +
                78:78,
 +
                79:79,
 +
                80:80,
 +
                81:81,
 +
                82:82,
 +
                83:83,
 +
                84:84,
 +
                85:85,
 +
                86:86,
 +
                87:87,
 +
                88:88,
 +
                89:89,
 +
                90:90
 +
                -1:0,
 +
                -2:2,
 +
                -3:3,
 +
                -4:4,
 +
                -5:5,
 +
                -6:6,
 +
                -7:7,
 +
                -8:8,
 +
                -9:9,
 +
                -10:10,
 +
                -11:11,
 +
                -12:12,
 +
                -13:13,
 +
                -14:14,
 +
                -15:15,
 +
                -16:16,
 +
                -17:17,
 +
                -18:18,
 +
                -19:19,
 +
                -20:20,
 +
                -21:21,
 +
                -22:22,
 +
                -23:23,
 +
                -24:24,
 +
                -25:25,
 +
                -26:26,
 +
                -27:27,
 +
                -28:28,
 +
                -29:29,
 +
                -30:30,
 +
                -31:31,
 +
                -32:32,
 +
                -33:33,
 +
                -34:34,
 +
                -35:35,
 +
                -36:36,
 +
                -37:37,
 +
                -38:38,
 +
                -39:39,
 +
                -40:40,
 +
                -41:41,
 +
                -42:42,
 +
                -43:43,
 +
                -44:44,
 +
                -45:45,
 +
                -46:46,
 +
                -47:47,
 +
                -48:48,
 +
                -49:49,
 +
                -50:50,
 +
                -51:51,
 +
                -52:52,
 +
                -53:53,
 +
                -54:54,
 +
                -55:55,
 +
                -56:56,
 +
                -57:57,
 +
                -58:58,
 +
                -59:59,
 +
                -60:60,
 +
                -61:61,
 +
                -62:62,
 +
                -63:63,
 +
                -64:64,
 +
                -65:65,
 +
                -66:66,
 +
                -67:67,
 +
                -68:68,
 +
                -69:69,
 +
                -70:70,
 +
                -71:71,
 +
                -72:72,
 +
                -73:73,
 +
                -74:74,
 +
                -75:75,
 +
                -76:76,
 +
                -77:77,
 +
                -78:78,
 +
                -79:79,
 +
                -80:80,
 +
                -81:81,
 +
                -82:82,
 +
                -83:83,
 +
                -84:84,
 +
                -85:85,
 +
                -86:86,
 +
                -87:87,
 +
                -88:88,
 +
                -89:89,
 +
                -90:90}
 +
def sun_position(time_now,lat,lon):
 +
    now_here = ephem.Observer()
 +
    now_here.lat = lat
 +
    now_here.lon = lon
 +
    #PyEphem only processes and returns dates that are in Universal Time (UT), which is simliar to Standard Time in Greenwich, England, on the Earth's Prime Meridian
 +
    # Europe/Paris is GMT+2
 +
    #tester angles pyephem sur mesures réelles
 +
    utc_now=datetime.datetime.utcnow()
 +
    #is_dst=datetime.datetime(year=utc_now.year,month=utc_now.month,day=utc_now.day).dst()
 +
    #time_diff=datetime.timedelta(hours=(1 if not is_dst else 2))
 +
    now_here.date = time_now #+datetime.timedelta(hours=time_diff) #'2007/10/02 00:50:22'
 +
    sun.compute(now_here)
 +
    sun_degre_azimut=int(sun.az*180/3.141592653589793)
 +
    sun_degre_horizontal=int(sun.alt*180/3.141592653589793)
 +
    return(sun_degre_horizontal,sun_degre_azimut)
 +
 
 +
tracker_degré_horizontal=0
 +
tracker_degré_azimut=0
 +
def init():
 +
    global tracker_degré_horizontal
 +
    forwardzero(111)
 +
    tracker_degré_horizontal=0
 +
def track(time_now,lat,lon):
 +
    global tracker_degré_horizontal
 +
    global tracker_degré_azimut
 +
    init()
 +
    (sun_degre_horizontal,sun_degre_azimut)=sun_position(time_now,lat,lon)
 +
    backwardzero(dict_angle_verin[sun_degre_horizontal])
 +
    tracker_degré_horizontal=sun_degre_horizontal
 +
    if sun_degre_azimut-tracker_degré_azimut<0:
 +
        backward(dict_angle_rotation[sun_degre_azimut-tracker_degré_azimut])
 +
        tracker_degré_azimut=sun_degre_azimut
 +
    else:
 +
        forward(dict_angle_rotation[sun_degre_azimut-tracker_degré_azimut])
 +
        tracker_degré_azimut=sun_degre_azimut
 +
#test Agen
 +
lat=44.2
 +
lon=0.6
 +
while True:
 +
    time.sleep(20*60)#activer le tracking toutes les 20 minutes
 +
    track(datetime.datetime.now(),lat,lon)
 +
 
 +
#Etalonnage
 +
#dict_angle_verin= sun_degre_horizontal:motoractivationtime
 +
dict_angle_verin={1:0,
 +
                2:0,
 +
                3:0,
 +
                4:0,
 +
                5:0,
 +
                6:0,
 +
                7:0,
 +
                8:0,
 +
                9:0,
 +
                10:0,
 +
                11:1,
 +
                12:1,
 +
                13:2,
 +
                14:3,
 +
                15:4,
 +
                16:5,
 +
                17:6,
 +
                18:7,
 +
                19:8,
 +
                20:9,
 +
                21:10,
 +
                22:11,
 +
                23:12,
 +
                24:13,
 +
                25:14,
 +
                26:15,
 +
                27:16,
 +
                28:16,
 +
                29:17,
 +
                30:18,
 +
                31:19,
 +
                32:20,
 +
                33:21,
 +
                34:22,
 +
                35:23,
 +
                36:24,
 +
                37:25,
 +
                38:27,
 +
                39:28,
 +
                40:29,
 +
                41:30,
 +
                42:32,
 +
                43:33,
 +
                44:34,
 +
                45:35,
 +
                46:37,
 +
                47:38,
 +
                48:39,
 +
                49:41,
 +
                50:42,
 +
                51:44,
 +
                52:45,
 +
                53:47,
 +
                54:48,
 +
                55:50,
 +
                56:51,
 +
                57:52,
 +
                58:54,
 +
                59:56,
 +
                60:58,
 +
                61:59,
 +
                62:59,
 +
                63:59,
 +
                64:60,
 +
                65:63,
 +
                66:64,
 +
                67:65,
 +
                68:66,
 +
                69:68,
 +
                70:68,
 +
                71:71,
 +
                72:73,
 +
                73:75,
 +
                74:77,
 +
                75:79,
 +
                76:81,
 +
                77:82,
 +
                78:83,
 +
                79:85,
 +
                80:86,
 +
                81:87,
 +
                82:89,
 +
                83:91,
 +
                84:94,
 +
                85:96,
 +
                86:98,
 +
                87:100}
 +
 
 +
#dict_angle_rotation= sun_degre_azimut:motoractivationtime
 +
dict_angle_rotation={1:0,
 +
                2:2,
 +
                3:3,
 +
                4:4,
 +
                5:5,
 +
                6:6,
 +
                7:7,
 +
                8:8,
 +
                9:9,
 +
                10:10,
 +
                11:11,
 +
                12:12,
 +
                13:13,
 +
                14:14,
 +
                15:15,
 +
                16:16,
 +
                17:17,
 +
                18:18,
 +
                19:19,
 +
                20:20,
 +
                21:21,
 +
                22:22,
 +
                23:23,
 +
                24:24,
 +
                25:25,
 +
                26:26,
 +
                27:27,
 +
                28:28,
 +
                29:29,
 +
                30:30,
 +
                31:31,
 +
                32:32,
 +
                33:33,
 +
                34:34,
 +
                35:35,
 +
                36:36,
 +
                37:37,
 +
                38:38,
 +
                39:39,
 +
                40:40,
 +
                41:41,
 +
                42:42,
 +
                43:43,
 +
                44:44,
 +
                45:45,
 +
                46:46,
 +
                47:47,
 +
                48:48,
 +
                49:49,
 +
                50:50,
 +
                51:51,
 +
                52:52,
 +
                53:53,
 +
                54:54,
 +
                55:55,
 +
                56:56,
 +
                57:57,
 +
                58:58,
 +
                59:59,
 +
                60:60,
 +
                61:61,
 +
                62:62,
 +
                63:63,
 +
                64:64,
 +
                65:65,
 +
                66:66,
 +
                67:67,
 +
                68:68,
 +
                69:69,
 +
                70:70,
 +
                71:71,
 +
                72:72,
 +
                73:73,
 +
                74:74,
 +
                75:75,
 +
                76:76,
 +
                77:77,
 +
                78:78,
 +
                79:79,
 +
                80:80,
 +
                81:81,
 +
                82:82,
 +
                83:83,
 +
                84:84,
 +
                85:85,
 +
                86:86,
 +
                87:87,
 +
                88:88,
 +
                89:89,
 +
                90:90
 +
                -1:0,
 +
                -2:2,
 +
                -3:3,
 +
                -4:4,
 +
                -5:5,
 +
                -6:6,
 +
                -7:7,
 +
                -8:8,
 +
                -9:9,
 +
                -10:10,
 +
                -11:11,
 +
                -12:12,
 +
                -13:13,
 +
                -14:14,
 +
                -15:15,
 +
                -16:16,
 +
                -17:17,
 +
                -18:18,
 +
                -19:19,
 +
                -20:20,
 +
                -21:21,
 +
                -22:22,
 +
                -23:23,
 +
                -24:24,
 +
                -25:25,
 +
                -26:26,
 +
                -27:27,
 +
                -28:28,
 +
                -29:29,
 +
                -30:30,
 +
                -31:31,
 +
                -32:32,
 +
                -33:33,
 +
                -34:34,
 +
                -35:35,
 +
                -36:36,
 +
                -37:37,
 +
                -38:38,
 +
                -39:39,
 +
                -40:40,
 +
                -41:41,
 +
                -42:42,
 +
                -43:43,
 +
                -44:44,
 +
                -45:45,
 +
                -46:46,
 +
                -47:47,
 +
                -48:48,
 +
                -49:49,
 +
                -50:50,
 +
                -51:51,
 +
                -52:52,
 +
                -53:53,
 +
                -54:54,
 +
                -55:55,
 +
                -56:56,
 +
                -57:57,
 +
                -58:58,
 +
                -59:59,
 +
                -60:60,
 +
                -61:61,
 +
                -62:62,
 +
                -63:63,
 +
                -64:64,
 +
                -65:65,
 +
                -66:66,
 +
                -67:67,
 +
                -68:68,
 +
                -69:69,
 +
                -70:70,
 +
                -71:71,
 +
                -72:72,
 +
                -73:73,
 +
                -74:74,
 +
                -75:75,
 +
                -76:76,
 +
                -77:77,
 +
                -78:78,
 +
                -79:79,
 +
                -80:80,
 +
                -81:81,
 +
                -82:82,
 +
                -83:83,
 +
                -84:84,
 +
                -85:85,
 +
                -86:86,
 +
                -87:87,
 +
                -88:88,
 +
                -89:89,
 +
                -90:90}
 +
def sun_position(time_now,lat,lon):
 +
    now_here = ephem.Observer()
 +
    now_here.lat = lat
 +
    now_here.lon = lon
 +
    # PyEphem only processes and returns dates that are in Universal Time (UT), which is simliar to Standard Time in Greenwich, England, on the Earth's Prime Meridian
 +
    # Europe/Paris is GMT+2
 +
    # hypothesis system clock is already automatically synced with daytime saving time
 +
    utc_now=datetime.datetime.utcnow()
 +
    now_here.date = utc_now
 +
    sun.compute(now_here)
 +
    sun_degre_azimut=int(sun.az*180/3.141592653589793)
 +
    sun_degre_horizontal=int(sun.alt*180/3.141592653589793)
 +
    return(sun_degre_horizontal,sun_degre_azimut)
 +
 
 +
tracker_degré_horizontal=0
 +
tracker_degré_azimut=0
 +
def init():
 +
    global tracker_degré_horizontal
 +
    forwardzero(111)
 +
    tracker_degré_horizontal=0
 +
def track(time_now,lat,lon):
 +
    global tracker_degré_horizontal
 +
    global tracker_degré_azimut
 +
    init()
 +
    (sun_degre_horizontal,sun_degre_azimut)=sun_position(time_now,lat,lon)
 +
    sun_degre_azimut_motor_referential=-(180-sun_degre_azimut) # pyephem azimut 0 is north. we assume in motor referential 0° is south and -1° is directed to the east.
 +
    backwardzero(dict_angle_verin[sun_degre_horizontal])
 +
    tracker_degré_horizontal=sun_degre_horizontal
 +
    if sun_degre_azimut_motor_referential-tracker_degré_azimut<0:
 +
        backward(dict_angle_rotation[sun_degre_azimut_motor_referential-tracker_degré_azimut])
 +
        tracker_degré_azimut=sun_degre_azimut
 +
    else:
 +
        forward(dict_angle_rotation[sun_degre_azimut_motor_referential-tracker_degré_azimut])
 +
        tracker_degré_azimut=sun_degre_azimut
 +
#test Agen
 +
lat=44.2
 +
lon=0.6
 +
while True:
 +
    time.sleep(20*60)#activer le tracking toutes les 20 minutes
 +
    track(datetime.datetime.now(),lat,lon)
 +
 
 +
 
 +
</pre></translate>
 +
}}
 +
{{Tuto Step
 +
|Step_Title=<translate>Coder le tracking avec une IA</translate>
 +
|Step_Content=<translate>On va maintenant coder une "IA lowtech" pour le côté pédagogique (du ML pour machine learning, comme utilisé massivement depuis une quinzaine d'année dans de nombreux secteurs d'activité, cad pas l'ia au sens chatgptesque du terme en 2024). On pourrait dire que l'ia lowtech est l'ia dont les résultats ne relèvent pas de la pensée magique, dont les data et le code sont open source, non volés, dont les data sont à nous, par nous et pour nous et sur laquelle on a la main (ce dernier point est essentiel mais l'expérience de sortir du rang me pousse au pessimisme à ce sujet car la vérification s'il y a interférence ou pas dans les résultats du machine learning est assez difficile à détecter)
 +
 
 +
 
 +
On branche une webcam, on enregistre les images comme données d'entrée, on traite l'image pour en faire un tableaux de chiffres correspondant à des variables avec lesquelles on va chercher à corréler avec un signal positif ou négatif (tourner le moter dans un sens ou moteur à l'arret).
 +
 
 +
 
 +
Ici, les données d'entrées sont uniquement les 640*480*3=921600 variables des pixels des images de la vidéo (921600 colonnes/variables par lignes, à partir desquelles on cherche une corrélation avec le signal positif 1 ou 0 de la dernière colonne).
 +
 
 +
 
 +
Pour faire fonctionner le tracker, ca ne fonctionnera pas bien, il faudrait faire du "feature engineering" (nom compliqué pour dire rajouter des colones de variables plus proabablement corrélées au signal positif) en rajoutant la luminosité et/ou la date et l'heure, sur des échantillons de vidéos couvrant toutes les saisons sur plusieurs années.
 +
 
 +
 
 +
Si vous voulez apprendre les bases sur lesquelles reposent ce code, je recommande le cours "Applied Data Science with Python" de l'université du michigan dans lequelvous apprendrez des bases de python,pandas, et machine learning "no bullshit".
 +
 
 +
 
 +
<pre>
 +
import cv2
 +
import time
 +
import numpy as np
 +
import os
 +
import argparse
 +
import pandas as pd
 +
import matplotlib.pyplot as plt
 +
import numpy as np
 +
import seaborn as sns
 +
import sklearn.model_selection
 +
import sklearn.metrics
 +
import sklearn.decomposition
 +
from sklearn.neighbors import KNeighborsClassifier
 +
from sklearn.naive_bayes import GaussianNB
 +
from sklearn.ensemble import RandomForestClassifier
 +
from sklearn.ensemble import GradientBoostingClassifier
 +
from sklearn.neural_network import MLPClassifier
 +
 
 +
 
 +
def enregistrer_video(out_file,fps,capture_duration):
 +
    # Open the webcam
 +
    cap = cv2.VideoCapture(0)  # Use 0 for default webcam
 +
 
 +
   
 +
    fourcc = cv2.VideoWriter_fourcc(*'XVID')  # Codec (e.g., XVID)
 +
 
 +
    frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # Get webcam frame width
 +
    frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # Get webcam frame height
 +
 
 +
 
 +
    # Check if webcam opened successfully
 +
    if not cap.isOpened():
 +
        print("Error opening webcam")
 +
        exit()
 +
 
 +
    # Create the VideoWriter object
 +
    out = cv2.VideoWriter(out_file, fourcc, fps, (frame_width, frame_height))
 +
 
 +
    # Start time for tracking duration
 +
    start_time = time.time()
 +
    while time.time() - start_time < capture_duration:
 +
        # Capture frame-by-frame
 +
        ret, frame = cap.read()
 +
 
 +
        # Check if frame captured successfully
 +
        if not ret:
 +
            print("Error capturing frame")
 +
            break
 +
 
 +
        # Write the frame to the video file
 +
        out.write(frame)
 +
   
 +
        # Display the captured frame (optional)
 +
        cv2.imshow('Webcam Video', frame)
 +
   
 +
        # Check if the user wants to quit (press 'q')
 +
        if cv2.waitKey(1) & 0xFF == ord('q'):
 +
            break
 +
 
 +
    # Close resources
 +
    cap.release()
 +
    out.release()
 +
    cv2.destroyAllWindows()
 +
 
 +
    print(f"1 minute video saved successfully as {out_file}!")
 +
 
 +
def charger_images_video(video_filename):
 +
  """
 +
  Charge les images vidéo d'un fichier.
 +
 
 +
  Args:
 +
      video_filename: Le chemin d'accès au fichier vidéo.
 +
 
 +
  Returns:
 +
      Un tableau NumPy contenant les images vidéo (3D array: frames, rows, cols).
 +
  """
 +
 
 +
  # Ouvrir la vidéo avec OpenCV
 +
  cap = cv2.VideoCapture(video_filename)
 +
 
 +
  # Vérifier l'ouverture réussie
 +
  if not cap.isOpened():
 +
      print("Erreur d'ouverture du fichier vidéo:", video_filename)
 +
      return None
 +
 
 +
  # Liste vide pour stocker les images vidéo
 +
  images_list = []
 +
 
 +
  # Lire les images vidéo image par image
 +
  while True:
 +
      ret, frame = cap.read()
 +
 
 +
      # Vérifier la lecture de l'image
 +
      if not ret:
 +
          break
 +
 
 +
      # Convertir l'image en nuance de gris (optionnel pour la normalisation)
 +
      # frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # Décommenter si nécessaire
 +
 
 +
      # Ajouter l'image à la liste
 +
      images_list.append(frame)
 +
 
 +
  # Fermer la capture vidéo
 +
  cap.release()
 +
 
 +
  return images_list
 +
 
 +
def flatten_images_video(images_array):
 +
   
 +
    # Initier une liste de resultat
 +
    result=[]
 +
   
 +
    # Iterer sur le numpy array en input
 +
    for k in images_array:
 +
        result.append(k.flatten())
 +
    # convertir la liste np.array
 +
    result=np.asarray(result)
 +
 
 +
    return result
 +
 
 +
# Exemple d'utilisation
 +
# Define video parameters
 +
out_file = "webcam_video_1min.mp4"  # Output video filename
 +
fps = 20.0  # Frames per second
 +
capture_duration = 60  # Seconds
 +
#enregistrer_video(out_file,fps,capture_duration)
 +
images_video = charger_images_video("webcam_video_1min.mp4")  # Fonction pour charger les images vidéo
 +
print(images_video)
 +
print(len(images_video))
 +
 
 +
 
 +
# Flattening and normalizing
 +
images_video = flatten_images_video(images_video)
 +
print(images_video[0])
 +
print(len(images_video[0]))
 +
 
 +
# compression/normalisation
 +
scaling_factor = 1.0 / 255.0  # Divide by 255 to normalize between 0 and 1
 +
images_video = images_video * scaling_factor
 +
print(images_video[0])
 +
print(len(images_video[0]))
 +
 
 +
# checking data shape
 +
#np.set_printoptions(threshold=np.inf)  # Set threshold to infinity
 +
#print(np.array2string(images_video[0], suppress_small=True))
 +
#print(len(images_video[0]))
 +
 
 +
# Get the array shape
 +
image_shape = images_video.shape
 +
 
 +
# Print the dimensions
 +
print("Image shape", image_shape)
 +
print("Number of dimensions:", len(image_shape))
 +
print("Image height:", image_shape[0]) 
 +
print("Image width:", image_shape[1])
 +
print("Number of color channels:", image_shape[2])
 +
 
 +
# Total number of elements (height x width x color channels)
 +
total_elements = image_shape[0] * image_shape[1] * image_shape[2]
 +
print("Total elements:", total_elements)E
 +
 
 +
# création du dataset pour dire "oui" pour tourner à gauche (par exemple)
 +
# ND: c'est à cette étape que les géants de la tech emploient des kenyans sous payés
 +
# dans une forme d'esclavage moderne
 +
# il s'agit de définir, pour chaque image, si on doit activer le moteur vers
 +
# la gauche (cad définir un signal positif pour que la machine fasse des corrélations
 +
# positives avec cette image)
 +
 
 +
positives=np.zeros_like(images_video)
 +
#Si les 14 premieres images définissent un signal positif, on fera:
 +
#en réalité il faudra traiter des segments de vidéos positivement en définissant
 +
#chaque image auxquelles on va associer le signal positif
 +
positives[0:14] = 1
 +
 
 +
class ML():
 +
    @staticmethod
 +
    def ml(X,y,classifier):
 +
        "process machine learning on X data set, y yes/no data with classifier"
 +
        # dataset
 +
        #X = images_video
 +
        #y = positives
 +
        # classifier model training
 +
        clf = ML.dico_classifier[classifier]()
 +
        X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(
 +
            X, y, random_state=0)
 +
        clf.fit(X_train, y_train)
 +
        # predictions
 +
        _predicted = clf.predict(X_test)
 +
        # scores
 +
        _accuracy, _precision, _recall = ML.compute_scores(y_test, _predicted,
 +
                                                        classifier)
 +
        # confusion matrix
 +
        ML.compute_confusion_matrix(y_test, _predicted, _accuracy, classifier)
 +
        # courbes precision-recall
 +
        ML.plot_precision_recall(clf, X_test, y_test, classifier, _predicted)
 +
        # roc
 +
        roc_auc_clf = ML.plot_roc(clf, X_test, y_test, classifier)[0]
 +
        return pd.DataFrame(data=(_accuracy, _precision, _recall, roc_auc_clf),
 +
                            index=['accuracy', 'precision', 'recall', 'AUC'],
 +
                            columns=[classifier])
 +
 
 +
    @staticmethod
 +
    def compute_scores(y_test, _predicted, classifier):
 +
        "compute machine learning scores"
 +
        _accuracy = sklearn.metrics.accuracy_score(y_test, _predicted)
 +
        _precision = sklearn.metrics.precision_score(y_test, _predicted)
 +
        _recall = sklearn.metrics.recall_score(y_test, _predicted)
 +
        print(str(classifier) + ' Accuracy: {:.2f}'.format(_accuracy))
 +
        print(str(classifier) + ' Precision: {:.2f}'.format(_precision))
 +
        print(str(classifier) + ' Recall: {:.2f}'.format(_recall))
 +
        return (_accuracy, _precision, _recall)
 +
 
 +
    @staticmethod
 +
    def compute_confusion_matrix(y_test, _predicted, _accuracy, classifier):
 +
        "compute confusion matrix"
 +
        confusion_clf = sklearn.metrics.confusion_matrix(y_test, _predicted)
 +
        df_clf = pd.DataFrame(confusion_clf,
 +
                              index=list(range(0, 2)),
 +
                              columns=list(range(0, 2)))
 +
        plt.figure(figsize=(5.5, 4))
 +
        ax_heatmap=sns.heatmap(df_clf, annot=True, vmin=0, vmax=11, cmap="Blues")
 +
        plt.title(str(classifier) + ' \nAccuracy:{0:.3f}'.format(_accuracy))
 +
        plt.ylabel('True label')
 +
        plt.xlabel('Predicted label')
 +
        return df_clf,ax_heatmap
 +
    @staticmethod
 +
    def plot_precision_recall(clf, X_test, y_test, classifier, _predicted):
 +
        "plot precision recall curve"
 +
        _precision = sklearn.metrics.precision_score(y_test, _predicted)
 +
        _recall = sklearn.metrics.recall_score(y_test, _predicted)
 +
        y_score_clf = clf.predict_proba(X_test)
 +
        y_score_df = pd.DataFrame(data=y_score_clf)
 +
        precision, recall, thresholds = sklearn.metrics.precision_recall_curve(
 +
            y_test, y_score_df[1])
 +
        closest_zero = np.argmin(np.abs(thresholds))
 +
        closest_zero_p = precision[closest_zero]
 +
        closest_zero_r = recall[closest_zero]
 +
        plt.figure()
 +
        plt.xlim([0.0, 1.01])
 +
        plt.ylim([0.0, 1.01])
 +
        result,=plt.plot(precision, recall)
 +
        plt.title(
 +
            str(classifier) +
 +
            ' Precision-Recall Curve \nprecision :{:0.2f}'.format(_precision) +
 +
            ' recall: {:0.2f}'.format(_recall))
 +
        plt.plot(closest_zero_p,
 +
                closest_zero_r,
 +
                'o',
 +
                markersize=12,
 +
                fillstyle='none',
 +
                c='r',
 +
                mew=3)
 +
        plt.xlabel('Precision', fontsize=16)
 +
        plt.ylabel('Recall', fontsize=16)
 +
        plt.show()
 +
        return result
 +
    @staticmethod
 +
    def plot_roc(clf, X_test, y_test, classifier):
 +
        "plot roc curve"
 +
        y_score_clf = clf.predict_proba(X_test)
 +
        y_score_df = pd.DataFrame(data=y_score_clf)
 +
        fpr_clf, tpr_clf, _ = sklearn.metrics.roc_curve(y_test, y_score_df[1])
 +
        roc_auc_clf = sklearn.metrics.auc(fpr_clf, tpr_clf)
 +
        plt.figure()
 +
        plt.xlim([-0.01, 1.00])
 +
        plt.ylim([-0.01, 1.01])
 +
        result,=plt.plot(fpr_clf,
 +
                tpr_clf,
 +
                lw=3,
 +
                label=str(classifier) +
 +
                ' ROC curve (area = {:0.2f})'.format(roc_auc_clf))
 +
        plt.xlabel('False Positive Rate', fontsize=16)
 +
        plt.ylabel('True Positive Rate', fontsize=16)
 +
        plt.title('ROC curve ' + str(classifier) +
 +
                  ' \nAUC:{0:.3f}'.format(roc_auc_clf),
 +
                  fontsize=16)
 +
        plt.legend(loc='lower right', fontsize=13)
 +
        plt.plot([0, 1], [0, 1], color='navy', lw=3, linestyle='--')
 +
        plt.show()
 +
        return roc_auc_clf,result
 +
 
 +
    dico_classifier = { 'knn': KNeighborsClassifier,
 +
                        'naiveb': GaussianNB,
 +
                        'randomforest': RandomForestClassifier,
 +
                        'gtree': GradientBoostingClassifier,
 +
                        'neural': MLPClassifier}
 +
 
 +
 
 +
    @staticmethod
 +
    def plot_heatmap(dataframe):
 +
        "plot heatmap of accuracy, precision, recall, AUC"
 +
        plt.figure()
 +
        sns.heatmap(dataframe, annot=True, vmin=0, vmax=1, cmap="Blues")
 +
        plt.title('scores des classifiers ')
 +
        plt.ylabel('scores')
 +
        plt.xlabel('modeles')
 +
        plt.show()
 +
 
 +
#process machine learning for all classifiers in dico_classifier
 +
#and plot a heatmap of their accuracy, precision, recall, AUC
 +
df_result = pd.DataFrame(data=(0, 0, 0, 0),
 +
                                columns=['init'],
 +
                                index=['accuracy', 'precision', 'recall', 'AUC'])
 +
for clf in ML.dico_classifier:
 +
    print(clf)
 +
    result_ml = ML.ml(images_video, positives, clf)
 +
    df_result = pd.merge(df_result,
 +
                                result_ml,
 +
                                right_index=True,
 +
                                left_index=True)
 +
df_result.drop('init', axis=1, inplace=True)
 +
ML.plot_heatmap(df_result)
 +
return df_result
 +
</pre>
 +
 
 +
Conclusion:
 +
Voilà, maintenant que vous savez coder une IA, vous pouvez la critiquer d'autant mieux,et promouvoir les lowtech en connaissance de cause.
 +
 
 +
 
 +
Vous noterez que les algorithmes d'ia sont open sources et assez faciles à utiliser en tant que développeur "simple utilisateur".
 +
 
 +
Et aussi que sans data, l'ia ne sert absolument à rien.
 +
 
 +
 
 +
C'est pour cette raison que les géants de la tech veulent toujours plus de données et emploient des gens dans des conditions proches de l'esclavage dans de nombreux pays pour les traiter avant d'entrainer leurs modèles.
 +
 
 +
 
 +
Tout comme pour les "données personnelles", la question clé des ia repose sur les données.
 +
 
 +
 
 +
Voir l'excellente conf de benjamin bayart " Géopolitique de la data (Benjamin BAYART) " sur youtube ou en vidéo ici.
 +
 
 +
 
 +
Vous pouvez aussi faire un tracker lowtech, mais aussi adapter le code pour créer un véhicule  autonome lowtech avec 4 datasets/signaux positifs distincts pour entrainer l'activation de "tourner à gauche","accélérer", "tourner à droite", "freiner".  C'est ce qu'a fait [https://fr.wikipedia.org/wiki/George_Hotz#Conduite_autonome George Hotz] en proclamant qu'il suffisait d'une trentaine d'heures de vidéos de conduite en enregistrant avec des capteurs pour avoir les signaux positifs correspondant aux images enregistrées pour que le machine learning fonctionne.
  
  
Update dès que les interferences s'atténuent et que mes tests me permettent de faire des hypothèses
+
Evidemment, on espere qu'il n'y aura pas de hack ou que le système n'a pas de controle commande à distance sur ce type d'algorithme.</translate>
testables.</translate>
+
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_G_opolitique_de_la_data_Benjamin_BAYART_.mp4
 
}}
 
}}
 
{{Notes
 
{{Notes

Version actuelle datée du 16 juin 2024 à 23:02

Tutorial de avatarAurelpere | Catégories : Énergie

Matériaux

tracker:

Leve plaque BRD : 150€

verin "Actionneur linéaire 12V DC , 1320LBS(6000N) 20 pouces (500mm) moteur électrique"  : 69€ sur aliexpress, disponible sur amazon un peu plus cher

Hbridge L298n 7a: 11€ livré sur aliexpress ("Moteur d'entrainement PWM 160W 7A 12V 24V, Module de commande L298, Signal de commande logique, optocoupleur, frein")


2 Pignon chaine 92T: "Pignon arrière 25H JO98/108/138 maillons 55T 65T 68T 70T 80T 92T, pour 47CC 49CC Mini Moto RL facades D343 Pit Pocket Bike" : 40€

2 Pignons "Pignon pour scooter électrique 8T 9T 11T 13T 25H 410 420, pour moteur à courant continu 25H JOMotor MY1020 BM1109 MY1016Z MY1018"

10€

2 Pignons "Pignon de moteur électrique pour Pit-Bike, pignon de moteur à courant continu, pièces RL, D343, 9T, 11T, 13T, 25H, JOMotor 25H"

4€

Moteur "Moteur à engrenages CC à vis sans fin autobloquant, couple de bain, moteur de boîte de vitesses turbo en métal, inversé, basse vitesse, DC 12V, 24V, 200kg.cm" 62€


Module

Module photovoltaique Voltech 2mx1m 375W: 200€


Pilotage:

raspberry: environ 100€

Outils

metre facom: 20€

kit de poids soviétique: 60€

niveau à bulle: 5€

equerre alu: 6€

ebarbeuse: 50€

perceuse: 50€

poste à souder: 100€

Étape 1 - Dimensionner : mesurer les moments d'inertie

Nous posons deux axes pour notre module:

un axe Oz perpendiculaire au plan du module (vertical lorsque le module est à plat) et un axe Ox paralelle au plan du module (horizontal lorsque le module est à plat)


Caractéristiques du module:

poids 21,2kg

Longueur: 1,8m

Largeur: 1m


La théorie de wikipedia nous dit que le moment d'inertie selon l'axe Oz est donc :

Jdelta=1/12*m*L (avec m masse du module et L longueur du module)

Jdelta=1/12*1,8*21,2=3,18Nm

Ndt: on assimile le module à une "barre" car ici l'axe de rotatation est l'axe paralelle au plan du rectangle formé par le module et non l'axe perpendiculaire


On verifie maintenant experiementalement :

Centrer le module sur le leve plaque et le mettre horizontalement.

verifier qu'il n'y a pas de vent

faire le niveau de la surface sur laquelle le mat du leve plaque est posé

fixer un repere horizontal au niveau du bas du module

poser un poids de 500g à l'extremité du module

mesurer la distance entre la position à l'équilibre et la position avec le poids


On a:

Jdelta=d*F

avec d distance en metre à l'axe de rotation (bras de levier)

F force appliquée au solide (ici mg avec m masse du solide en kg, et g constance gravitationelle)


On a donc

Jdelta=0,9*0,5*9,8=4,41Nm


On mesure la rotation : dans notre cas, la distance de rotation à l'extrémité du module varie entre 3 et 10 cm. Les variations importantes

sont dus aux frotements de l'axe qui peut etre en force ou coulisser plus librement.

L'ordre de grandeur du moment d'inertie est bien vérifié.

Refaire avec poids exact?


Pour le moment d'inertie selon l'axe Ox, il est plus difficile de vérifier expérimentalement, car l'axe du lève plaque ne permet pas

d'avoir une position à l'équilibre avec le poids du module (qui viendra caler sur la butée en subissant son poids)


On se contentera donc de la théorie:

La theorie nous dit

Jdelta=1/12*m(b²+c²) avec m masse du module, b longueur du petit coté, c longueur du grand coté

Jdelta=1/12*21,2*(1,8²+1²)=7,49NM


Ce resultat theorique étant tres fortement inferieur au poids du module, on dimensionnera à partir de la force necessaire pour soulever le poids du module (l'axe etant soumis au poids):

F=mg=21,2*9,8


Soit 200Nm en ordre de grandeur (1m de bras de levier en ordre de grandeur)

On a donc maintenant les caractéristiques pour dimensionner les moteurs d'entrainement de notre module selon les deux axes de rotation.

Mais les trackers ont une prise au vent conséquente.

Si on souhaite prendre en compte la résitance au vent il faut mesurer la force appliqué au module selon la vitesse du vent:

Fp​=1/2*ρ*v²*S*Cp


Avec ​ρ densité de l'air egal à 1,2 kg/m3 en ordre de grandeur pour des conditions de temperature et de pressions "standards".

v vitesse du vent en m/s

S surface de l'objet en m²

Cp coefficient de pression sans dimension égal à 2 pour une plaque rectangulaire en métal


On a donc :

Fp=1/2*1,2*1,8*2*v²=2,16*v²


Chatgpt nous donne les abaques des vitesses de vent en km/h et leurs conversions en m/s et le nom generique en météorologie:

Calme : Moins de 1 km/h (Moins de 0.3 m/s)

Très légère brise : 1-5 km/h (0.3-1.5 m/s)

Légère brise : 6-11 km/h (1.6-3.0 m/s)

Petite brise : 12-19 km/h (3.4-5.4 m/s)

Jolie brise : 20-28 km/h (5.5-7.9 m/s)

Bonne brise : 29-38 km/h (8.0-10.7 m/s)

Vent frais : 39-49 km/h (10.8-13.8 m/s)

Vent modéré : 50-61 km/h (13.9-16.9 m/s)

Vent assez fort : 62-74 km/h (17.2-20.6 m/s)

Fort vent : 75-88 km/h (20.8-24.4 m/s)

Tempête : 89-102 km/h (24.7-28.3 m/s)

Violente tempête : 103-117 km/h (28.6-32.5 m/s)

Ouragan : Au moins 118 km/h (Au moins 32.8 m/s)


Nous avons donc une Force Fp qui peut varier de

Un ordre de grandeur de 20N pour une legere brise

à

Un ordre de grandeur de 2000N pour une tempete

(Ndt: la force de gravité d'1 kg est d'environ 10N donc la force de 2000N correspond en ordre de grandeur à la force de gravité de 200kg).

Les moments d'inertie sur les axes sont du meme ordre de grandeur (20Nm et 2000Nm) puisque les dimensions du modules sont de l'ordre du metre.

Si vous souhaitez construire un tracker qui resiste donc à des conditions de tempete, il est conseillé de dimensionner le tracker en conséquence

d'une part avec des attaches au sol suffisante, d'autres part avec une armature adaptée -voir astuce caravane a la fin de cette etape-, et désactiver

lors des vents de tempetes ou plus important.)


Le dimensionnement pour la résistance au vent est une des raisons pour lesquelles les trackers sont cher et donc moins répandus que les installations photovoltaïques fixes.


Les moteurs pas à pas et servomoteurs (step motors en anglais) qui ont un couple suffisant pour résister à des vents importants sont chers, et ca peut se comprendre pour des moteurs conçu pour de la précision dans les pas.

(par exemple là: https://www.distrelec.fr/fr/automatisation/moteurs-et-entrainements/moteurs-pas-pas-et-servocommandes/c/cat-L3D_525513 )


Pour les verins (hydraulic cylinder en anglais), la force de poussée est généralement dans des ordres de grandeurs suffisant pour resister aux tempetes.


Pour notre tutoriel low-tech, on sait que les moteurs de carotteuse ont des couples (torque en anglais) d'un ordre de grandeur suffisant pour résiter à des tempêtes (1W correspond à 1 newton que multiplie 1 mètre par seconde, une carotteuse de 2000W devrait donc avoir un couple d'un ordre de grandeur plus ou moins dans les 2000Nm).


Après vérification sur les caractéristiques techniques des perceuses, visseuses à choc, et caroteuses, et une vérification manuelle des résistances (frein/embreillage) lorsque le moteur n'est pas alimenté, ce type de moteur ne conviendra pas.


Pour pouvoir dimensionner une résistance à des vents entre la violente tempête et l'ouragan, on va donc procéder différemment:

on trouve sur aliexpress à des prix abordables (70€) des moteurs pas à pas ou des moteurs à engrenage/vis sans fin dont le torque (le couple) est d'un ordre de grandeur de 20Nm (200kgcm). On va donc utiliser ce moteur avec deux réducteurs 1:10 pour obtenir un couple résistant à 2000Nm.


Rappel : le couple exprimé en Nm est une force de rotation produite par le moteur qui se calcule selon le meme principe que le moment d'inertie: la force en newton x la distance à l'axe de rotation en metre

Les transmissions par couroie ou chaine permettent de réduire ce torque/couple, et cela se calcule très simplement:

C1=(R1/R2)*C2

Avec C1 couple sur la poulie 1

R1 rayon de la poulie 1

C2 couple sur la poulie2

R2 rayon de la poulie 2


Le rayon etant directement proportionnel aux nombre de dents d'une roue dentée (un pignon de vélo ou de moto par exemple), on peut facilement calculer les rapports de transmission en divisant le nombre de dents du grand pignon par le nombre de dents du petit pignon (en vérifiant que cest bien le meme standard de dents).

Ainsi une transmission 80 dents/10 dents (80T / 10T en anglais avec T pour tooth) produira une réduction de 1:8, comme ici sur amazon pour du vélo électrique ("Keenso Kit Chaîne et Pignon 80T 25H 34mm 3 Trous Pignon 10T H Trou Chaîne Pignon 146 Maillons Chaîne") à 30€.

On remarquera que les transmission de vélo standard ont des rapport de réduction maximum de 50dents/11dents, soit une réduction de 1:5, ce qui est insuffisant pour des réductions efficaces sans multiplier les poulies.


Il est difficile de trouver des transmissions avec des réductions de 1:10, mais on en trouve sur aliexpress et on pourra assez facilement adapter un pédalier de vélo sur lequel on va venir souder des pignons, ce qui nous permettra de faire une reduction 1:100 avec un seul axe ajouté en plus de l'axe principal et de l'axe du moteur.


On va donc à utiliser un moteur pas à pas ou un moteur à engrenage avec vis sans fin (test de résistance non alimenté à réception des pièces) commandé par un raspberry pi (mais le porte plaque etant sur roulette, on prendra la précaution de ranger le tracker en cas de tempete ;))


Astuce caravane: pour dimensionner l'épaisseur de l'armature en métal, les normes modernes semblent être moins faites sur des bases scientifiques que sur des bases commerciales pour faite vendre. On pourra donc utilement mesurer l'épaisseur de vieux matériel (années 70). Par exemple l'armature de cette vieille caravane homologuée carte grise 750kg a une armature métal de 5mm d'épaisseur. On pourra ensuite faire une règle de 3 pour vérifier que l'épaisseur de notre armature est convenable.

C'est rustique (les calculs de resistance des matériaux rigoureux sont complexes), mais ca permet d'avoir une première approximation "a visto de nas" comme on dit en gascon

Étape 3 - Dimensionner : mesurer les angles et débattements

On va d'abord s'interesser à la trajectoire solaire ou solar trajectory en anglais.

On mesure typiquement la position du soleil selon deux systemes de coordonnées:

le systeme equatorial avec des coordonnées exprimées en:

ascension droite equivalente à la longitude terrestre mesurée en heures minutes secondes

déclinaison équivalente à la latitude terrestre mesurée en degré minutes secondes


le systeme horizontal avec des coordonnées exprimées en:

degré d'azimut

degré d'altitude ou de hauteur


Les abaques de trajectoire solaire (par exemple disponibles ici : https://www.astrolabe-science.fr/diagramme-solaire-azimut-hauteur ) nous donnent les trajectoires du soleil dans une journée (généralement plusieurs journées typiques de plusieurs saisons) exprimées en degrés horizontal.

Pour lire un graphique de ce type:

si on suit le graphique inséré dans ce tuto et issu du lien ci-dessus, lorsqu'on suit par exemple la courbe rouge pour paris, voici ce qu'on peut lire:

le 21 décembre, lorsqu'on regarde le sud, le soleil suit une trajectoire qui commence à -50° d'azimut (vers l'Est sur l'axe horizontal) lorsque le soleil se lève, puis lorsque le soleil va vers l'ouest tout au long de la journée (on suit la courbe rouge), il prend de la hauteur jusqu'à atteindre 17° de hauteur (sur l'axe vertical) à midi (position 0° d'azimut sur l'axe horizontal) puis redescend jusqu'à 0° de heuteur lorsqu'il se couche (vers l'Ouest sur l'axe horizontal).


Pour paris, on a :

un degré d'azimut qui varie de -130° à +130° selon l'heure et la saison

un degré d'altitude ou de hauteur qui varie de 0° à 64° selon l'heure et la saison


Pour notre tracker,

Pour calculer notre débattement horizontal (selon un axe Oz vertical si le module est posé au sol), on n'a pas vraiment de contrainte sur le lève plaque utilisé puisque l'axe tourne à 360° sans probleme. Donc on pourra suivre le soleil de -130° d'azimut à +130° d'azimut sans probleme.

Pour calculer notre debattement vertical (selon l'axe Ox horizontal si le module est posé au sol), on a une contrainte sur l'angle maximal.


N'ayant pas de décimetre sous la main, on va utiliser pythagore (voir photo):

64cm*150cm*134cm

socatoa:

sinus phi=opposé/hypothenus

sinus phi=134/150

sinus phi=0,8933

phi=1,1046 rad

phi=1,1046*180/pi=63°


On a donc une contrainte pour notre leve plaque qui accepte des angles selon l'axe Ox de 0° à 63°.


Lorsque le tracker est à son angle maximum (63°), on est perpendiculaire au soleil lorsque le soleil est à un angle phi de phi=180-63-90=27°

Lorsque le soleil a un angle plus faible que 27°, le tracker ne pourra pas suivre en étant perpendiculaire au soleil.

On voit cependant que la butée est assurée par le ressort (sur la photo on voit la marque au niveau de la peinture). On peut donc gagner en amplitude sur la butée en perçant et en faisant une encoche dans la potence.


La mesure manuelle du débattement entre l'axe du tube sur lequel est fixé la manivelle et le dos du module lorsque le leve plaque est incliné à son angle maximum nous donne 42cm. (voir photo)

Étape 4 - Installer le verin sur l'axe horizontal

On va fixer une potence sur la partie fixe du porte plaque qui tourne avec l'axe vertical, afin d'y fixer une tige sur laquelle on fixera le verin qui pourra tourner avec l'axe vertical afin d'ajuster l'angle sur l'axe horizontal.

On commence par fixer la potence en metal en la soudant à la partie fixe vis à vis de l'axe vertical. Il faut bien poncer la peinture avant de faire la soudure. (voir photo). On fait ici une soudure à l'arc.

On perce ensuite une tige en metal qu'on vient boulonner à la potence (voir photo).

On perce et on fixe également une tige en métal qu'on vient boulonner à la partie mobile qui ajuste l'angle sur l'axe horizontal, cad les "bras" qui permettent de porter la plaque ou le module photovoltaïque (voir photo).

On fixe ensuite le verin aux deux tiges en métal. Le verin est équipé de fixations avec des chevilles qui permettent de "suivre" l'angle pris par les tiges sur lesquelles il est fixé. (voir photo)

Remarquez qu'on a pris une tige en métal en angle droit afin d'éviter que cette fixation soit entièrement libre. Elle vient buter sur une partie de la tige, ce qui permet par la suite d'étaloner plus précisément l'amplitude du verin.

On teste ensuite la course du verin. Il faudra faire attention, car on arrive sur la butée du porte plaque à une avancée du verin d'environ 40cm et ce modèle a une course de 50cm. (voir photos)

Étape 5 - Augmenter l'angle maximum du lève plaque

Après observation des éléments bloquant, on va modifier la potence pour éviter la butée lorsque le soleil a un degré d'altitude inférieur à 27°.

Pour cela on va :

-laisser passer le ressort en évidant la potence (pour éviter que le ressort fasse butée)

-augmenter l'angle en abaissant la fixation du ressort en perçant la potence

-augmenter l'angle en coupant les bords de la potence


Voir photos :

1/2: observation dessus/dessous

3/4: demontage potence

4/5 : observation dessous, angle max à l'equerre


On arrive ainsi à un angle maximum de quasi 90° et on peut donc suivre le soleil sous tous les angles!

Étape 6 - Tester les commandes du verin et du moteur rotatif

Pour controller le verin, on va utiliser un raspberry pi, l'ordinateur monocarte le plus répandu. Il est doté de d'une série 40 pins, qu'on peut connecter à divers appareils, appelés "controlleur GPIO".

Une première lecture des tutos et librairies disponibles pour utiliser le gpio et des un certain temps passer à tester des hypothèses éronnées pour installer correctement en utilisant les bonnes versions m'amène à vous exposer les options possibles:

-système d'exploitation :

Archives et versions de systemes d'exploitation utiles pour la rétrocompatibilité (tout lecteur soucieux de l'informatique low tech est incité à télécharger et garder des copies en local de ces archives et les partager en torrent!):

*dietpi:

https://dietpi.com/downloads/images/

*raspberry pi os:

https://downloads.raspberrypi.org/raspbian/images

si vous utilisez wheezy,

attention à bien mettre a jour votre /etc/apt/sources.list en faisant:

echo "deb http://legacy.raspbian.org/raspbian/ wheezy main contrib non-free rpi" >> /etc/apt/sources.list

NB: ChatGPT nous donne les dates suivantes de sortie des raspberry:

  1. Raspberry Pi Model B : 29 février 2012
  2. Raspberry Pi Model A : 4 février 2013
  3. Raspberry Pi Model B+ : 14 juillet 2014
  4. Raspberry Pi Model A+ : 10 novembre 2014
  5. Raspberry Pi 2 Model B : 2 février 2015
  6. Raspberry Pi Zero : 30 novembre 2015
  7. Raspberry Pi 3 Model B : 29 février 2016
  8. Raspberry Pi Zero W : 28 février 2017
  9. Raspberry Pi 3 Model B+ : 14 mars 2018
  10. Raspberry Pi 3 Model A+ : 15 novembre 2018
  11. Raspberry Pi 4 Model B : 24 juin 2019
  12. Raspberry Pi 400 : 2 novembre 2020
  13. Raspberry Pi Pico : 21 janvier 2021
  14. Raspberry Pi Zero 2 W : 28 octobre 2021

On espère que c'est vrai (ca vient de chatgpt), mais vous pouvez vérifier sur ce qui est écrit sur le pcb de votre carte.

Pour vérifier la version de votre raspberry sous raspberry pi os si vous ne savez pas quelle version cest(voir photo):

sudo usermod -a G gpio pi
pinout

Ajustez avec le système d'exploitation qui vous parait le plus pertinent au regard des filtres de pertinence que vous utilisez. Vous avez la liste des anciennes versions d'os de dietpi et de raspberry pi os au cas où les versions les plus récentes ne fonctionneraient plus (et je me repette : tout lecteur soucieux de l'informatique low tech est incité à télécharger et garder des copies en local de ces archives et les partager en torrent!).

Pour l'install, comme d'habitude, telecharger balenaetcher, flasher une clé usb avec l'image téléchargée, booter. Les login/mdp par défaut pour dietpi sont root/dietpi et pour raspberry pi os pi/raspberry (attention au clavier en qwerty par défaut sous raspberry pi os au démmarage). Pour configurer le clavier, les locales, la timezone et le wifi, faire

sudo dietpi-config
sous dietpi et
sudo raspi-config
sous raspberry pi.

-driver utilisé pour controler le gpio

  • RPi.GPIO(dev independant) ou RPi.GPIO2(dev redhat, repo recent)

Essais infructueux avec pip et le depot pypi (erreur de compilations etc.). Installer en passant en root avec la commande

sudo -s 

le plus simple est alors d'installer une version déjà compilée avec apt:

sudo apt install python3-rpi.gpio

faire un test pour voir si ca fonctionne bien (toujours en etant utilisateur root):

python3
import RPi.GPIO

Se reporter à la doc de sourceforge, plus explicite que celle de pypi: http://sourceforge.net/p/raspberry-gpio-python/wiki/Home/


  • gpiozero

installé par défaut sous raspberry pi

sous dietpi faire

sudo apt install python3 python3-venv python3-pip
python3 -m venv venv
source venv/bin/activate
pip install lgpio gpiozero

Le premier point à comprendre est la numérotation des fiches: les pins (fiches) ont chacune un numéro qui va de 1 à 40 en suivant un ordre de bas en haut et de gauche à droite, et chaque pin a également un numéro GPIO qui est différent du numero de pin.

Pour cela on peut chercher les infos sur internet: https://wiki.lowtechlab.org/wiki/Serveur_orangepi-raspberry_nextcloud_en_photovolta%C3%AFque_autonome ou taper les commande suivantes dans raspberry pi os (voir image)

sudo usermod -aG gpio votre_user
pinout

Dans ce tuto, que ce soit avec gpiozero ou RPi.GPIO, on utilisera la numerotation GPIO et pas la numérotation pin

On branche les GPIO 2 et 4 (alim +5V) aux deux fiches +5V du HBrdige On branche les GPIO 6 et 7 (Ground, la terre) aux deux fiches GND du HBrdige On branche les GPIO 23 et 16 à l'interrupteur 3 du HBridge Pour tester le "enable" du HBridge, on branche les GPIO 20 et 21 au ENA du HBridge

On utilise ensuite les scripts suivants :

import time
import RPi.GPIO as GPIO
import gpiozero


def forwardzero(wait):
    led16=gpiozero.LED(16) #motor1
    led23=gpiozero.LED(23) #motor1
    led20=gpiozero.LED(20) #enable
    led21=gpiozero.LED(21) #enable
    led1=gpiozero.LED(1)
    led7=gpiozero.LED(7)
    try:
        print("forwardzero")
        #cas où le hbridge necessite un signal enable
        led21.off()
        led20.on()
        #signal à zero sur interupteur 2
        led1.off()
        led7.off()
        #mettre un signal sur l'interrupteur 1
        led23.on()
        led16.off()
        #laisser le signal actif le temps de wait
        for k in range(wait):
            print(k)
            time.sleep(1)
        #eteindre le signal sur l'interrupteur
        led23.off()
        led20.off()
    except KeyboardnInterrupt:
        print("keyboard interrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        #signal à zero sur interupteur 2
        led1.off()
        led7.off()
        #signal à zero sur l'interrupteur 1
        led16.off()
        led23.off()
        #signal à zero sur la fiche enable
        led20.off()
        led21.off()
def backwardzero(wait):
    led1=gpiozero.LED(1)
    led7=gpiozero.LED(7)
    led16=gpiozero.LED(16) #motor1
    led23=gpiozero.LED(23) #motor1
    led20=gpiozero.LED(20) #enable
    led21=gpiozero.LED(21) #enable
    try:
        print("backwardzero")
        #signal zero sur interupteur 1
        led16.on()
        led23.off()
        #cas où le hbrige necessite un signal sur enable
        led20.off()
        led21.on()
        #signal sur interrupteur 2
        led1.off()
        led7.off()
        #wait
        for k in range(wait):
            print(k)
            time.sleep(1)
        #signal off sur interrupteur
        led16.off()
        led21.off()
    except KeyboardInterrupt:
        print("keyboardinterrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        #signal à zero sur interupteur 1
        led16.off()
        led23.off()
        #signal à zero sur l'interupteur 2
        led1.off()
        led7.off()
        #signal à zero sur la fiche enable
        led20.off()
        led21.off()


#pin16 gpio23
#pin36 gpio25

def forward(wait):
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(16,GPIO.OUT) #motor1
    GPIO.setup(23,GPIO.OUT) #motor1
    GPIO.setup(20,GPIO.OUT) #enable
    GPIO.setup(21,GPIO.OUT) #enable
    GPIO.setup(1,GPIO.OUT)  #motor2
    GPIO.setup(7,GPIO.OUT)  #motor2
    try:
        print("forward")
        GPIO.output(1,GPIO.HIGH)
        GPIO.output(7,GPIO.LOW)
        GPIO.output(21,GPIO.HIGH)
        GPIO.output(20,GPIO.LOW)
        GPIO.output(23,GPIO.LOW)
        GPIO.output(16,GPIO.LOW)
        for k in range(wait):
            print(k)
            time.sleep(1)
        GPIO.output(1,GPIO.LOW)
        GPIO.output(21,GPIO.LOW)
    except KeyboardInterrupt:
        print("keyboard interrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        GPIO.output(1,GPIO.LOW)
        GPIO.output(7,GPIO.LOW)
        GPIO.output(20,GPIO.LOW)
        GPIO.output(21,GPIO.LOW)
        GPIO.output(23,GPIO.LOW)
        GPIO.output(16,GPIO.LOW)
        #GPIO.cleanup() chez moi, ca n'enleve pas les +3V
def backward(wait):
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(20,GPIO.OUT) #enable
    GPIO.setup(21,GPIO.OUT) #enable
    GPIO.setup(1,GPIO.OUT)  #motor2
    GPIO.setup(7,GPIO.OUT)  #motor2
    GPIO.setup(16,GPIO.OUT) #motor1
    GPIO.setup(23,GPIO.OUT) #motor1
    try:
        print("backward")
        GPIO.output(21,GPIO.HIGH)
        GPIO.output(20,GPIO.LOW)
        GPIO.output(16,GPIO.LOW)
        GPIO.output(23,GPIO.LOW)
        GPIO.output(7,GPIO.HIGH)
        GPIO.output(1,GPIO.LOW)
        for k in range(wait):
            print(k)
            time.sleep(1)
        GPIO.output(7,GPIO.LOW)
        GPIO.output(21,GPIO.HIGH)
    except KeyboardInterrupt:
        print("keyboardinterrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        GPIO.output(20,GPIO.LOW)
        GPIO.output(21,GPIO.LOW)
        GPIO.output(16,GPIO.LOW)
        GPIO.output(23,GPIO.LOW)
        GPIO.output(1,GPIO.LOW)
        GPIO.output(7,GPIO.LOW)
        #GPIO.cleanup() chez moi ca n'enleve pas les +3V
forward(2) #rotation horaire motor2
backward(2) #rotation antihoraire motor2
forwardzero(10) #verin extension motor1 111 max
backwardzero(10) #verin retractation motor1 107 max

GPIO.BCM permet d'utiliser la numerotation GPIO GPIO.HIGH envoie un signal de +3V dans la fiche concernée GPIO.LOW remet le signal à 0V (GND, la terre) gpiozero fait la meme chose avec les methodes .on() et .off()

Update 31.05.24 : vous l'attendiez, voilà l'update du jour avec le hbridge made in europe, ca roule, voir vidéo! :) Update de l'étalonnage rapidement à reception d'un truc pour mesurer les angles un peu pratique.

Remarquez que les fiches 20 et 21 ne sont pas branchées au hbridge. Le "pwm" (pulse width modulation) n'est pas utilisé pour "activer" (enable dans le code) le moteur car des cavaliers placés sur les deux fiches ENA du hbridge suffisent (ici on n'a pas besoin de moduler la vitesse du moteur).


Étape 7 - Fixer les poulies et la transmission 1:100 pour le moteur rotatif

On commence par récupérer deux pédaliers sur des vélos d'occasion à l'ébarbeuse en prenant soin de garder une tige du cadre.


On va venir y souder les pignons 92T (92 dents).

On soude ensuite les pignons 8T (8 dents) dessus.

On soude un pignon 8T sur un pignon avec une clavette qui s'adapte à l'axe du moteur.

On découpe une tige en fer et on soude un aplat le long de l'axe du lève plaque sur lequel on va venir fixer avec des boulons les tiges découpées dans les cadres de vélo qui prolongent le pédalier ainsi qu'une tige sur laquelle on va fixer le moteur.

On assemble et on boulonne.

Update à réceptiond de la 3eme chaine et en attendant de réfléchir à un moyen de régler les tensions de chaine.


Update du 11.6.24: les contraintes de l'axe du leve plaque font qu'on ne peut y fixer un grand pignon pour bénéficier d'un rapport de réduction favorable sur la derniere chaine de transmission. Malgré les réductions des transmissions des autres poulies, le lève plaque n'est pas entrainé en rotation (voir vidéo).

update du 16.6.24: réception du verin supplémentaire, date livraison estimée : 28 juin-2juillet


On va donc entrainer la rotation avec deux verins. Update à réception des verins fin juin (en stage et non dispo pour update ces prochaines semaines)

Étape 8 - Etalonner les moteurs

Le code mis à jour est le suivant: on définit des dictionnaire qui associe chaque angle recherché à un temps d'activation du moteur (à tester à la main et à mesurer)

#Etalonnage
#dict_angle_verin= sun_degre_horizontal:motoractivationtime
dict_angle_verin={1:0,
                2:0,
                3:0,
                4:0,
                5:0,
                6:0,
                7:0,
                8:0,
                9:0,
                10:0,
                11:1,
                12:1,
                13:2,
                14:3,
                15:4,
                16:5,
                17:6,
                18:7,
                19:8,
                20:9,
                21:10,
                22:11,
                23:12,
                24:13,
                25:14,
                26:15,
                27:16,
                28:16,
                29:17,
                30:18,
                31:19,
                32:20,
                33:21,
                34:22,
                35:23,
                36:24,
                37:25,
                38:27,
                39:28,
                40:29,
                41:30,
                42:32,
                43:33,
                44:34,
                45:35,
                46:37,
                47:38,
                48:39,
                49:41,
                50:42,
                51:44,
                52:45,
                53:47,
                54:48,
                55:50,
                56:51,
                57:52,
                58:54,
                59:56,
                60:58,
                61:59,
                62:59,
                63:59,
                64:60,
                65:63,
                66:64,
                67:65,
                68:66,
                69:68,
                70:68,
                71:71,
                72:73,
                73:75,
                74:77,
                75:79,
                76:81,
                77:82,
                78:83,
                79:85,
                80:86,
                81:87,
                82:89,
                83:91,
                84:94,
                85:96,
                86:98,
                87:100}

update etalonnage moteur en rotation à reception de la 3eme chaine




Étape 9 - Coder le tracking en "dur"

Le code mis à jour est le suivant

import time
import RPi.GPIO as GPIO
import gpiozero
import ephem

def forwardzero(wait):
    led16=gpiozero.LED(16) #motor1
    led23=gpiozero.LED(23) #motor1
    led20=gpiozero.LED(20) #enable
    led21=gpiozero.LED(21) #enable
    led1=gpiozero.LED(1)
    led7=gpiozero.LED(7)
    try:
        print("forwardzero")
        #cas où le hbridge necessite un signal enable
        led21.on()
        led20.off()
        #signal à zero sur interupteur 2
        led1.off()
        led7.off()
        #mettre un signal sur l'interrupteur 1
        led23.on()
        led16.off()
        #laisser le signal actif le temps de wait
        for k in range(wait):
            print(k)
            time.sleep(1)
        #eteindre le signal sur l'interrupteur
        led23.off()
        led21.off()
    except KeyboardnInterrupt:
        print("keyboard interrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        #signal à zero sur interupteur 2
        led1.off()
        led7.off()
        #signal à zero sur l'interrupteur 1
        led16.off()
        led23.off()
        #signal à zero sur la fiche enable
        led20.off()
        led21.off()
def backwardzero(wait):
    led1=gpiozero.LED(1)
    led7=gpiozero.LED(7)
    led16=gpiozero.LED(16) #motor1
    led23=gpiozero.LED(23) #motor1
    led20=gpiozero.LED(20) #enable
    led21=gpiozero.LED(21) #enable
    try:
        print("backwardzero")
        #signal zero sur interupteur 1
        led16.on()
        led23.off()
        #cas où le hbrige necessite un signal sur enable
        led20.off()
        led21.on()
        #signal sur interrupteur 2
        led1.off()
        led7.off()
        #wait
        for k in range(wait):
            print(k)
            time.sleep(1)
        #signal off sur interrupteur
        led16.off()
        led21.off()
    except KeyboardInterrupt:
        print("keyboardinterrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        #signal à zero sur interupteur 1
        led16.off()
        led23.off()
        #signal à zero sur l'interupteur 2
        led1.off()
        led7.off()
        #signal à zero sur la fiche enable
        led20.off()
        led21.off()


#pin16 gpio23
#pin36 gpio25

def forward(wait):
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(16,GPIO.OUT) #motor1
    GPIO.setup(23,GPIO.OUT) #motor1
    GPIO.setup(20,GPIO.OUT) #enable
    GPIO.setup(21,GPIO.OUT) #enable
    GPIO.setup(1,GPIO.OUT)  #motor2
    GPIO.setup(7,GPIO.OUT)  #motor2
    try:
        print("forward")
        GPIO.output(1,GPIO.HIGH)
        GPIO.output(7,GPIO.LOW)
        GPIO.output(21,GPIO.HIGH)
        GPIO.output(20,GPIO.LOW)
        GPIO.output(23,GPIO.LOW)
        GPIO.output(16,GPIO.LOW)
        for k in range(wait):
            print(k)
            time.sleep(1)
        GPIO.output(1,GPIO.LOW)
        GPIO.output(21,GPIO.LOW)
    except KeyboardInterrupt:
        print("keyboard interrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        GPIO.output(1,GPIO.LOW)
        GPIO.output(7,GPIO.LOW)
        GPIO.output(20,GPIO.LOW)
        GPIO.output(21,GPIO.LOW)
        GPIO.output(23,GPIO.LOW)
        GPIO.output(16,GPIO.LOW)
        #GPIO.cleanup() chez moi, ca n'enleve pas les +3V
def backward(wait):
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(20,GPIO.OUT) #enable
    GPIO.setup(21,GPIO.OUT) #enable
    GPIO.setup(1,GPIO.OUT)  #motor2
    GPIO.setup(7,GPIO.OUT)  #motor2
    GPIO.setup(16,GPIO.OUT) #motor1
    GPIO.setup(23,GPIO.OUT) #motor1
    try:
        print("backward")
        GPIO.output(21,GPIO.HIGH)
        GPIO.output(20,GPIO.LOW)
        GPIO.output(16,GPIO.LOW)
        GPIO.output(23,GPIO.LOW)
        GPIO.output(7,GPIO.HIGH)
        GPIO.output(1,GPIO.LOW)
        for k in range(wait):
            print(k)
            time.sleep(1)
        GPIO.output(7,GPIO.LOW)
        GPIO.output(21,GPIO.HIGH)
    except KeyboardInterrupt:
        print("keyboardinterrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        GPIO.output(20,GPIO.LOW)
        GPIO.output(21,GPIO.LOW)
        GPIO.output(16,GPIO.LOW)
        GPIO.output(23,GPIO.LOW)
        GPIO.output(1,GPIO.LOW)
        GPIO.output(7,GPIO.LOW)
        #GPIO.cleanup() chez moi ca n'enleve pas les +3V
forward(2) #rotation horaire motor2
backward(2) #rotation antihoraire motor2
forwardzero(10) #verin extension motor1 111 max
backwardzero(10) #verin retractation motor1 107 max

#Etalonnage
#dict_angle_verin= sun_degre_horizontal:motoractivationtime
dict_angle_verin={1:1,
                2:2,
                3:3,
                4:4,
                5:5,
                6:6,
                7:7,
                8:8,
                9:9,
                10:10,
                11:11,
                12:12,
                13:13,
                14:14,
                15:15,
                16:16,
                17:17,
                18:18,
                19:19,
                20:20,
                21:21,
                22:22,
                23:23,
                24:24,
                25:25,
                26:26,
                27:27,
                28:28,
                29:29,
                30:30,
                31:31,
                32:32,
                33:33,
                34:34,
                35:35,
                36:36,
                37:37,
                38:38,
                39:39,
                40:40,
                41:41,
                42:42,
                43:43,
                44:44,
                45:45,
                46:46,
                47:47,
                48:48,
                49:49,
                50:50,
                51:51,
                52:52,
                53:53,
                54:54,
                55:55,
                56:56,
                57:57,
                58:58,
                59:59,
                60:60,
                61:61,
                62:62,
                63:63,
                64:64,
                65:65,
                66:66,
                67:67,
                68:68,
                69:69,
                70:70,
                71:71,
                72:72,
                73:73,
                74:74,
                75:75,
                76:76,
                77:77,
                78:78,
                79:79,
                80:80,
                81:81,
                82:82,
                83:83,
                84:84,
                85:85,
                86:86,
                87:87,
                88:88,
                89:89,
                90:90}

#dict_angle_rotation= sun_degre_azimut:motoractivationtime
dict_angle_rotation={1:0,
                2:2,
                3:3,
                4:4,
                5:5,
                6:6,
                7:7,
                8:8,
                9:9,
                10:10,
                11:11,
                12:12,
                13:13,
                14:14,
                15:15,
                16:16,
                17:17,
                18:18,
                19:19,
                20:20,
                21:21,
                22:22,
                23:23,
                24:24,
                25:25,
                26:26,
                27:27,
                28:28,
                29:29,
                30:30,
                31:31,
                32:32,
                33:33,
                34:34,
                35:35,
                36:36,
                37:37,
                38:38,
                39:39,
                40:40,
                41:41,
                42:42,
                43:43,
                44:44,
                45:45,
                46:46,
                47:47,
                48:48,
                49:49,
                50:50,
                51:51,
                52:52,
                53:53,
                54:54,
                55:55,
                56:56,
                57:57,
                58:58,
                59:59,
                60:60,
                61:61,
                62:62,
                63:63,
                64:64,
                65:65,
                66:66,
                67:67,
                68:68,
                69:69,
                70:70,
                71:71,
                72:72,
                73:73,
                74:74,
                75:75,
                76:76,
                77:77,
                78:78,
                79:79,
                80:80,
                81:81,
                82:82,
                83:83,
                84:84,
                85:85,
                86:86,
                87:87,
                88:88,
                89:89,
                90:90
                -1:0,
                -2:2,
                -3:3,
                -4:4,
                -5:5,
                -6:6,
                -7:7,
                -8:8,
                -9:9,
                -10:10,
                -11:11,
                -12:12,
                -13:13,
                -14:14,
                -15:15,
                -16:16,
                -17:17,
                -18:18,
                -19:19,
                -20:20,
                -21:21,
                -22:22,
                -23:23,
                -24:24,
                -25:25,
                -26:26,
                -27:27,
                -28:28,
                -29:29,
                -30:30,
                -31:31,
                -32:32,
                -33:33,
                -34:34,
                -35:35,
                -36:36,
                -37:37,
                -38:38,
                -39:39,
                -40:40,
                -41:41,
                -42:42,
                -43:43,
                -44:44,
                -45:45,
                -46:46,
                -47:47,
                -48:48,
                -49:49,
                -50:50,
                -51:51,
                -52:52,
                -53:53,
                -54:54,
                -55:55,
                -56:56,
                -57:57,
                -58:58,
                -59:59,
                -60:60,
                -61:61,
                -62:62,
                -63:63,
                -64:64,
                -65:65,
                -66:66,
                -67:67,
                -68:68,
                -69:69,
                -70:70,
                -71:71,
                -72:72,
                -73:73,
                -74:74,
                -75:75,
                -76:76,
                -77:77,
                -78:78,
                -79:79,
                -80:80,
                -81:81,
                -82:82,
                -83:83,
                -84:84,
                -85:85,
                -86:86,
                -87:87,
                -88:88,
                -89:89,
                -90:90}
def sun_position(time_now,lat,lon):
    now_here = ephem.Observer()
    now_here.lat = lat
    now_here.lon = lon
    #PyEphem only processes and returns dates that are in Universal Time (UT), which is simliar to Standard Time in Greenwich, England, on the Earth's Prime Meridian
    # Europe/Paris is GMT+2
    #tester angles pyephem sur mesures réelles
    utc_now=datetime.datetime.utcnow()
    #is_dst=datetime.datetime(year=utc_now.year,month=utc_now.month,day=utc_now.day).dst()
    #time_diff=datetime.timedelta(hours=(1 if not is_dst else 2))
    now_here.date = time_now #+datetime.timedelta(hours=time_diff) #'2007/10/02 00:50:22'
    sun.compute(now_here)
    sun_degre_azimut=int(sun.az*180/3.141592653589793)
    sun_degre_horizontal=int(sun.alt*180/3.141592653589793)
    return(sun_degre_horizontal,sun_degre_azimut)

tracker_degré_horizontal=0
tracker_degré_azimut=0
def init():
    global tracker_degré_horizontal
    forwardzero(111)
    tracker_degré_horizontal=0
def track(time_now,lat,lon):
    global tracker_degré_horizontal
    global tracker_degré_azimut
    init()
    (sun_degre_horizontal,sun_degre_azimut)=sun_position(time_now,lat,lon)
    backwardzero(dict_angle_verin[sun_degre_horizontal])
    tracker_degré_horizontal=sun_degre_horizontal
    if sun_degre_azimut-tracker_degré_azimut<0:
        backward(dict_angle_rotation[sun_degre_azimut-tracker_degré_azimut])
        tracker_degré_azimut=sun_degre_azimut
    else:
        forward(dict_angle_rotation[sun_degre_azimut-tracker_degré_azimut])
        tracker_degré_azimut=sun_degre_azimut
#test Agen
lat=44.2
lon=0.6
while True:
    time.sleep(20*60)#activer le tracking toutes les 20 minutes
    track(datetime.datetime.now(),lat,lon)

#Etalonnage
#dict_angle_verin= sun_degre_horizontal:motoractivationtime
dict_angle_verin={1:0,
                2:0,
                3:0,
                4:0,
                5:0,
                6:0,
                7:0,
                8:0,
                9:0,
                10:0,
                11:1,
                12:1,
                13:2,
                14:3,
                15:4,
                16:5,
                17:6,
                18:7,
                19:8,
                20:9,
                21:10,
                22:11,
                23:12,
                24:13,
                25:14,
                26:15,
                27:16,
                28:16,
                29:17,
                30:18,
                31:19,
                32:20,
                33:21,
                34:22,
                35:23,
                36:24,
                37:25,
                38:27,
                39:28,
                40:29,
                41:30,
                42:32,
                43:33,
                44:34,
                45:35,
                46:37,
                47:38,
                48:39,
                49:41,
                50:42,
                51:44,
                52:45,
                53:47,
                54:48,
                55:50,
                56:51,
                57:52,
                58:54,
                59:56,
                60:58,
                61:59,
                62:59,
                63:59,
                64:60,
                65:63,
                66:64,
                67:65,
                68:66,
                69:68,
                70:68,
                71:71,
                72:73,
                73:75,
                74:77,
                75:79,
                76:81,
                77:82,
                78:83,
                79:85,
                80:86,
                81:87,
                82:89,
                83:91,
                84:94,
                85:96,
                86:98,
                87:100}

#dict_angle_rotation= sun_degre_azimut:motoractivationtime
dict_angle_rotation={1:0,
                2:2,
                3:3,
                4:4,
                5:5,
                6:6,
                7:7,
                8:8,
                9:9,
                10:10,
                11:11,
                12:12,
                13:13,
                14:14,
                15:15,
                16:16,
                17:17,
                18:18,
                19:19,
                20:20,
                21:21,
                22:22,
                23:23,
                24:24,
                25:25,
                26:26,
                27:27,
                28:28,
                29:29,
                30:30,
                31:31,
                32:32,
                33:33,
                34:34,
                35:35,
                36:36,
                37:37,
                38:38,
                39:39,
                40:40,
                41:41,
                42:42,
                43:43,
                44:44,
                45:45,
                46:46,
                47:47,
                48:48,
                49:49,
                50:50,
                51:51,
                52:52,
                53:53,
                54:54,
                55:55,
                56:56,
                57:57,
                58:58,
                59:59,
                60:60,
                61:61,
                62:62,
                63:63,
                64:64,
                65:65,
                66:66,
                67:67,
                68:68,
                69:69,
                70:70,
                71:71,
                72:72,
                73:73,
                74:74,
                75:75,
                76:76,
                77:77,
                78:78,
                79:79,
                80:80,
                81:81,
                82:82,
                83:83,
                84:84,
                85:85,
                86:86,
                87:87,
                88:88,
                89:89,
                90:90
                -1:0,
                -2:2,
                -3:3,
                -4:4,
                -5:5,
                -6:6,
                -7:7,
                -8:8,
                -9:9,
                -10:10,
                -11:11,
                -12:12,
                -13:13,
                -14:14,
                -15:15,
                -16:16,
                -17:17,
                -18:18,
                -19:19,
                -20:20,
                -21:21,
                -22:22,
                -23:23,
                -24:24,
                -25:25,
                -26:26,
                -27:27,
                -28:28,
                -29:29,
                -30:30,
                -31:31,
                -32:32,
                -33:33,
                -34:34,
                -35:35,
                -36:36,
                -37:37,
                -38:38,
                -39:39,
                -40:40,
                -41:41,
                -42:42,
                -43:43,
                -44:44,
                -45:45,
                -46:46,
                -47:47,
                -48:48,
                -49:49,
                -50:50,
                -51:51,
                -52:52,
                -53:53,
                -54:54,
                -55:55,
                -56:56,
                -57:57,
                -58:58,
                -59:59,
                -60:60,
                -61:61,
                -62:62,
                -63:63,
                -64:64,
                -65:65,
                -66:66,
                -67:67,
                -68:68,
                -69:69,
                -70:70,
                -71:71,
                -72:72,
                -73:73,
                -74:74,
                -75:75,
                -76:76,
                -77:77,
                -78:78,
                -79:79,
                -80:80,
                -81:81,
                -82:82,
                -83:83,
                -84:84,
                -85:85,
                -86:86,
                -87:87,
                -88:88,
                -89:89,
                -90:90}
def sun_position(time_now,lat,lon):
    now_here = ephem.Observer()
    now_here.lat = lat
    now_here.lon = lon
    # PyEphem only processes and returns dates that are in Universal Time (UT), which is simliar to Standard Time in Greenwich, England, on the Earth's Prime Meridian
    # Europe/Paris is GMT+2
    # hypothesis system clock is already automatically synced with daytime saving time
    utc_now=datetime.datetime.utcnow()
    now_here.date = utc_now 
    sun.compute(now_here)
    sun_degre_azimut=int(sun.az*180/3.141592653589793)
    sun_degre_horizontal=int(sun.alt*180/3.141592653589793)
    return(sun_degre_horizontal,sun_degre_azimut)

tracker_degré_horizontal=0
tracker_degré_azimut=0
def init():
    global tracker_degré_horizontal
    forwardzero(111)
    tracker_degré_horizontal=0
def track(time_now,lat,lon):
    global tracker_degré_horizontal
    global tracker_degré_azimut
    init()
    (sun_degre_horizontal,sun_degre_azimut)=sun_position(time_now,lat,lon)
    sun_degre_azimut_motor_referential=-(180-sun_degre_azimut) # pyephem azimut 0 is north. we assume in motor referential 0° is south and -1° is directed to the east.
    backwardzero(dict_angle_verin[sun_degre_horizontal])
    tracker_degré_horizontal=sun_degre_horizontal
    if sun_degre_azimut_motor_referential-tracker_degré_azimut<0:
        backward(dict_angle_rotation[sun_degre_azimut_motor_referential-tracker_degré_azimut])
        tracker_degré_azimut=sun_degre_azimut
    else:
        forward(dict_angle_rotation[sun_degre_azimut_motor_referential-tracker_degré_azimut])
        tracker_degré_azimut=sun_degre_azimut
#test Agen
lat=44.2
lon=0.6
while True:
    time.sleep(20*60)#activer le tracking toutes les 20 minutes
    track(datetime.datetime.now(),lat,lon)


Étape 10 - Coder le tracking avec une IA

On va maintenant coder une "IA lowtech" pour le côté pédagogique (du ML pour machine learning, comme utilisé massivement depuis une quinzaine d'année dans de nombreux secteurs d'activité, cad pas l'ia au sens chatgptesque du terme en 2024). On pourrait dire que l'ia lowtech est l'ia dont les résultats ne relèvent pas de la pensée magique, dont les data et le code sont open source, non volés, dont les data sont à nous, par nous et pour nous et sur laquelle on a la main (ce dernier point est essentiel mais l'expérience de sortir du rang me pousse au pessimisme à ce sujet car la vérification s'il y a interférence ou pas dans les résultats du machine learning est assez difficile à détecter)


On branche une webcam, on enregistre les images comme données d'entrée, on traite l'image pour en faire un tableaux de chiffres correspondant à des variables avec lesquelles on va chercher à corréler avec un signal positif ou négatif (tourner le moter dans un sens ou moteur à l'arret).


Ici, les données d'entrées sont uniquement les 640*480*3=921600 variables des pixels des images de la vidéo (921600 colonnes/variables par lignes, à partir desquelles on cherche une corrélation avec le signal positif 1 ou 0 de la dernière colonne).


Pour faire fonctionner le tracker, ca ne fonctionnera pas bien, il faudrait faire du "feature engineering" (nom compliqué pour dire rajouter des colones de variables plus proabablement corrélées au signal positif) en rajoutant la luminosité et/ou la date et l'heure, sur des échantillons de vidéos couvrant toutes les saisons sur plusieurs années.


Si vous voulez apprendre les bases sur lesquelles reposent ce code, je recommande le cours "Applied Data Science with Python" de l'université du michigan dans lequelvous apprendrez des bases de python,pandas, et machine learning "no bullshit".


import cv2
import time
import numpy as np
import os
import argparse
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import sklearn.model_selection
import sklearn.metrics
import sklearn.decomposition
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.neural_network import MLPClassifier


def enregistrer_video(out_file,fps,capture_duration):
    # Open the webcam
    cap = cv2.VideoCapture(0)  # Use 0 for default webcam

    
    fourcc = cv2.VideoWriter_fourcc(*'XVID')  # Codec (e.g., XVID)

    frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # Get webcam frame width
    frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # Get webcam frame height


    # Check if webcam opened successfully
    if not cap.isOpened():
        print("Error opening webcam")
        exit()

    # Create the VideoWriter object
    out = cv2.VideoWriter(out_file, fourcc, fps, (frame_width, frame_height))

    # Start time for tracking duration
    start_time = time.time()
    while time.time() - start_time < capture_duration:
        # Capture frame-by-frame
        ret, frame = cap.read()

        # Check if frame captured successfully
        if not ret:
            print("Error capturing frame")
            break

        # Write the frame to the video file
        out.write(frame)
    
        # Display the captured frame (optional)
        cv2.imshow('Webcam Video', frame)
    
        # Check if the user wants to quit (press 'q')
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    # Close resources
    cap.release()
    out.release()
    cv2.destroyAllWindows()

    print(f"1 minute video saved successfully as {out_file}!")

def charger_images_video(video_filename):
  """
  Charge les images vidéo d'un fichier.

  Args:
      video_filename: Le chemin d'accès au fichier vidéo.

  Returns:
      Un tableau NumPy contenant les images vidéo (3D array: frames, rows, cols).
  """

  # Ouvrir la vidéo avec OpenCV
  cap = cv2.VideoCapture(video_filename)

  # Vérifier l'ouverture réussie
  if not cap.isOpened():
      print("Erreur d'ouverture du fichier vidéo:", video_filename)
      return None

  # Liste vide pour stocker les images vidéo
  images_list = []

  # Lire les images vidéo image par image
  while True:
      ret, frame = cap.read()

      # Vérifier la lecture de l'image
      if not ret:
          break

      # Convertir l'image en nuance de gris (optionnel pour la normalisation)
      # frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # Décommenter si nécessaire

      # Ajouter l'image à la liste
      images_list.append(frame)

  # Fermer la capture vidéo
  cap.release()

  return images_list

def flatten_images_video(images_array):
    
    # Initier une liste de resultat
    result=[]
    
    # Iterer sur le numpy array en input
    for k in images_array:
        result.append(k.flatten())
    # convertir la liste np.array
    result=np.asarray(result)

    return result

# Exemple d'utilisation
# Define video parameters
out_file = "webcam_video_1min.mp4"  # Output video filename
fps = 20.0  # Frames per second
capture_duration = 60  # Seconds
#enregistrer_video(out_file,fps,capture_duration)
images_video = charger_images_video("webcam_video_1min.mp4")  # Fonction pour charger les images vidéo
print(images_video)
print(len(images_video))


# Flattening and normalizing
images_video = flatten_images_video(images_video)
print(images_video[0])
print(len(images_video[0]))

# compression/normalisation
scaling_factor = 1.0 / 255.0  # Divide by 255 to normalize between 0 and 1
images_video = images_video * scaling_factor
print(images_video[0])
print(len(images_video[0]))

# checking data shape
#np.set_printoptions(threshold=np.inf)  # Set threshold to infinity
#print(np.array2string(images_video[0], suppress_small=True))
#print(len(images_video[0]))

# Get the array shape
image_shape = images_video.shape

# Print the dimensions
print("Image shape", image_shape)
print("Number of dimensions:", len(image_shape))
print("Image height:", image_shape[0])   
print("Image width:", image_shape[1])
print("Number of color channels:", image_shape[2])

# Total number of elements (height x width x color channels)
total_elements = image_shape[0] * image_shape[1] * image_shape[2]
print("Total elements:", total_elements)E

# création du dataset pour dire "oui" pour tourner à gauche (par exemple)
# ND: c'est à cette étape que les géants de la tech emploient des kenyans sous payés
# dans une forme d'esclavage moderne
# il s'agit de définir, pour chaque image, si on doit activer le moteur vers 
# la gauche (cad définir un signal positif pour que la machine fasse des corrélations
# positives avec cette image)

positives=np.zeros_like(images_video)
#Si les 14 premieres images définissent un signal positif, on fera:
#en réalité il faudra traiter des segments de vidéos positivement en définissant
#chaque image auxquelles on va associer le signal positif
positives[0:14] = 1

class ML():
    @staticmethod
    def ml(X,y,classifier):
        "process machine learning on X data set, y yes/no data with classifier"
        # dataset
        #X = images_video
        #y = positives
        # classifier model training
        clf = ML.dico_classifier[classifier]()
        X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(
            X, y, random_state=0)
        clf.fit(X_train, y_train)
        # predictions
        _predicted = clf.predict(X_test)
        # scores
        _accuracy, _precision, _recall = ML.compute_scores(y_test, _predicted,
                                                        classifier)
        # confusion matrix
        ML.compute_confusion_matrix(y_test, _predicted, _accuracy, classifier)
        # courbes precision-recall
        ML.plot_precision_recall(clf, X_test, y_test, classifier, _predicted)
        # roc
        roc_auc_clf = ML.plot_roc(clf, X_test, y_test, classifier)[0]
        return pd.DataFrame(data=(_accuracy, _precision, _recall, roc_auc_clf),
                            index=['accuracy', 'precision', 'recall', 'AUC'],
                            columns=[classifier])

    @staticmethod
    def compute_scores(y_test, _predicted, classifier):
        "compute machine learning scores"
        _accuracy = sklearn.metrics.accuracy_score(y_test, _predicted)
        _precision = sklearn.metrics.precision_score(y_test, _predicted)
        _recall = sklearn.metrics.recall_score(y_test, _predicted)
        print(str(classifier) + ' Accuracy: {:.2f}'.format(_accuracy))
        print(str(classifier) + ' Precision: {:.2f}'.format(_precision))
        print(str(classifier) + ' Recall: {:.2f}'.format(_recall))
        return (_accuracy, _precision, _recall)

    @staticmethod
    def compute_confusion_matrix(y_test, _predicted, _accuracy, classifier):
        "compute confusion matrix"
        confusion_clf = sklearn.metrics.confusion_matrix(y_test, _predicted)
        df_clf = pd.DataFrame(confusion_clf,
                              index=list(range(0, 2)),
                              columns=list(range(0, 2)))
        plt.figure(figsize=(5.5, 4))
        ax_heatmap=sns.heatmap(df_clf, annot=True, vmin=0, vmax=11, cmap="Blues")
        plt.title(str(classifier) + ' \nAccuracy:{0:.3f}'.format(_accuracy))
        plt.ylabel('True label')
        plt.xlabel('Predicted label')
        return df_clf,ax_heatmap
    @staticmethod
    def plot_precision_recall(clf, X_test, y_test, classifier, _predicted):
        "plot precision recall curve"
        _precision = sklearn.metrics.precision_score(y_test, _predicted)
        _recall = sklearn.metrics.recall_score(y_test, _predicted)
        y_score_clf = clf.predict_proba(X_test)
        y_score_df = pd.DataFrame(data=y_score_clf)
        precision, recall, thresholds = sklearn.metrics.precision_recall_curve(
            y_test, y_score_df[1])
        closest_zero = np.argmin(np.abs(thresholds))
        closest_zero_p = precision[closest_zero]
        closest_zero_r = recall[closest_zero]
        plt.figure()
        plt.xlim([0.0, 1.01])
        plt.ylim([0.0, 1.01])
        result,=plt.plot(precision, recall)
        plt.title(
            str(classifier) +
            ' Precision-Recall Curve \nprecision :{:0.2f}'.format(_precision) +
            ' recall: {:0.2f}'.format(_recall))
        plt.plot(closest_zero_p,
                 closest_zero_r,
                 'o',
                 markersize=12,
                 fillstyle='none',
                 c='r',
                 mew=3)
        plt.xlabel('Precision', fontsize=16)
        plt.ylabel('Recall', fontsize=16)
        plt.show()
        return result
    @staticmethod
    def plot_roc(clf, X_test, y_test, classifier):
        "plot roc curve"
        y_score_clf = clf.predict_proba(X_test)
        y_score_df = pd.DataFrame(data=y_score_clf)
        fpr_clf, tpr_clf, _ = sklearn.metrics.roc_curve(y_test, y_score_df[1])
        roc_auc_clf = sklearn.metrics.auc(fpr_clf, tpr_clf)
        plt.figure()
        plt.xlim([-0.01, 1.00])
        plt.ylim([-0.01, 1.01])
        result,=plt.plot(fpr_clf,
                 tpr_clf,
                 lw=3,
                 label=str(classifier) +
                 ' ROC curve (area = {:0.2f})'.format(roc_auc_clf))
        plt.xlabel('False Positive Rate', fontsize=16)
        plt.ylabel('True Positive Rate', fontsize=16)
        plt.title('ROC curve ' + str(classifier) +
                  ' \nAUC:{0:.3f}'.format(roc_auc_clf),
                  fontsize=16)
        plt.legend(loc='lower right', fontsize=13)
        plt.plot([0, 1], [0, 1], color='navy', lw=3, linestyle='--')
        plt.show()
        return roc_auc_clf,result

    dico_classifier = { 'knn': KNeighborsClassifier,
                        'naiveb': GaussianNB,
                        'randomforest': RandomForestClassifier,
                        'gtree': GradientBoostingClassifier,
                        'neural': MLPClassifier}


    @staticmethod
    def plot_heatmap(dataframe):
        "plot heatmap of accuracy, precision, recall, AUC"
        plt.figure()
        sns.heatmap(dataframe, annot=True, vmin=0, vmax=1, cmap="Blues")
        plt.title('scores des classifiers ')
        plt.ylabel('scores')
        plt.xlabel('modeles')
        plt.show()

#process machine learning for all classifiers in dico_classifier
#and plot a heatmap of their accuracy, precision, recall, AUC
df_result = pd.DataFrame(data=(0, 0, 0, 0),
                                 columns=['init'],
                                 index=['accuracy', 'precision', 'recall', 'AUC'])
for clf in ML.dico_classifier:
    print(clf)
    result_ml = ML.ml(images_video, positives, clf)
    df_result = pd.merge(df_result,
                                 result_ml,
                                 right_index=True,
                                 left_index=True)
df_result.drop('init', axis=1, inplace=True)
ML.plot_heatmap(df_result)
return df_result

Conclusion: Voilà, maintenant que vous savez coder une IA, vous pouvez la critiquer d'autant mieux,et promouvoir les lowtech en connaissance de cause.


Vous noterez que les algorithmes d'ia sont open sources et assez faciles à utiliser en tant que développeur "simple utilisateur".

Et aussi que sans data, l'ia ne sert absolument à rien.


C'est pour cette raison que les géants de la tech veulent toujours plus de données et emploient des gens dans des conditions proches de l'esclavage dans de nombreux pays pour les traiter avant d'entrainer leurs modèles.


Tout comme pour les "données personnelles", la question clé des ia repose sur les données.


Voir l'excellente conf de benjamin bayart " Géopolitique de la data (Benjamin BAYART) " sur youtube ou en vidéo ici.


Vous pouvez aussi faire un tracker lowtech, mais aussi adapter le code pour créer un véhicule autonome lowtech avec 4 datasets/signaux positifs distincts pour entrainer l'activation de "tourner à gauche","accélérer", "tourner à droite", "freiner". C'est ce qu'a fait George Hotz en proclamant qu'il suffisait d'une trentaine d'heures de vidéos de conduite en enregistrant avec des capteurs pour avoir les signaux positifs correspondant aux images enregistrées pour que le machine learning fonctionne.


Evidemment, on espere qu'il n'y aura pas de hack ou que le système n'a pas de controle commande à distance sur ce type d'algorithme.




Commentaires

Draft