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

(Page créée avec « Cp pressure coefficient without dimension égal to 2 for a rectangular metal plate »)
(Page créée avec « 4/5: obersvation below, maximum angle at square »)
 
(191 révisions intermédiaires par le même utilisateur non affichées)
Ligne 147 : Ligne 147 :
 
Cp  pressure coefficient without dimension égal to 2 for a rectangular metal plate
 
Cp  pressure coefficient without dimension égal to 2 for a rectangular metal plate
  
On a donc :
+
We have:
  
 
Fp=1/2*1,2*1,8*2*v²=2,16*v²
 
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:
+
Chatgpt can give us the abacus of wind speeds in km/h and their conversions in m/2 and the generic name in meteorology
  
Calme : Moins de 1 km/h (Moins de 0.3 m/s)
+
Calm: Less than 1 km/h (Less than 0.3 m/s)
  
Très légère brise : 1-5 km/h (0.3-1.5 m/s)
+
Very light breeze: 1-5 km/h (0.3-1.5 m/s)
  
Légère brise : 6-11 km/h (1.6-3.0 m/s)
+
Light breeze: 6-11 km/h (1.6-3.0 m/s)
  
Petite brise : 12-19 km/h (3.4-5.4 m/s)
+
Gentle breeze: 12-19 km/h (3.4-5.4 m/s)
  
Jolie brise : 20-28 km/h (5.5-7.9 m/s)
+
Moderate breeze: 20-28 km/h (5.5-7.9 m/s)
  
Bonne brise : 29-38 km/h (8.0-10.7 m/s)
+
Fresh breeze: 29-38 km/h (8.0-10.7 m/s)
  
Vent frais : 39-49 km/h (10.8-13.8 m/s)
+
Strong breeze: 39-49 km/h (10.8-13.8 m/s)
  
Vent modéré : 50-61 km/h (13.9-16.9 m/s)
+
Moderate wind: 50-61 km/h (13.9-16.9 m/s)
  
Vent assez fort : 62-74 km/h (17.2-20.6 m/s)
+
Near gale: 62-74 km/h (17.2-20.6 m/s)
  
Fort vent : 75-88 km/h (20.8-24.4 m/s)
+
Gale: 75-88 km/h (20.8-24.4 m/s)
  
Tempête : 89-102 km/h (24.7-28.3 m/s)
+
Strong gale: 89-102 km/h (24.7-28.3 m/s)
  
Violente tempête : 103-117 km/h (28.6-32.5 m/s)
+
Storm: 103-117 km/h (28.6-32.5 m/s)
  
Ouragan : Au moins 118 km/h (Au moins 32.8 m/s)
+
Hurricane: At least 118 km/h (At least 32.8 m/s)
  
Nous avons donc une Force Fp qui peut varier de
+
So we have a Force Fp that can vary from
  
Un ordre de grandeur de 20N pour une legere brise
+
An order of magnitude of 20N for a ligth breeze
  
à
+
to
  
Un ordre de grandeur de 2000N pour une tempete
+
An order of magnitude of 2000N for a storm
  
(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).
+
(NB: the gravity force of 1kg is approximately 10N so the force of 2000N corresponds in order of magnitude to the gravity force of 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.
+
The inertia moments on the axis are of the same order of magnitude (20Nm and 2000Nm) because the dimensions of the module are an order of magnitude of 1 m
  
Si vous souhaitez construire un tracker qui resiste donc à des conditions de tempete, il est conseillé de dimensionner le tracker en conséquence
+
If you want to build a tracker that can resist to storm conditions, it is advised to size the tracker consequently
  
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
+
On the one hand with sufficient ties in the ground, and on the other hand with an adapted frame -see caravan tips at the end of this stage-, and deactivate
  
lors des vents de tempetes ou plus important.)
+
when there are stronger winds.)
  
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.
+
Wind resistance sizing is one of the reasons trackers are expensive et less generalized than standard photovoltaic installations
  
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.
+
The step motors and servomotors that have an adequate torke to resist to important winds are expensive, et that can be understandable for motors designed for step precision
  
(par exemple là: <nowiki>https://www.distrelec.fr/fr/automatisation/moteurs-et-entrainements/moteurs-pas-pas-et-servocommandes/c/cat-L3D_525513</nowiki> )
+
(For example here: <nowiki>https://www.distrelec.fr/fr/automatisation/moteurs-et-entrainements/moteurs-pas-pas-et-servocommandes/c/cat-L3D_525513</nowiki> )
  
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.
+
For hydraulic cylinders, the driving force is generally in orders of magnitude fitting to resist to storms.
  
<s>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).</s>
+
<s>For our low-tech tutorial, we know that core drilling machines have torque in an order of magnitude fitting to resist to storms (1W corresponds to 1 N multiplying 1 meter per second, so a core drilling machine of 2000W should have an adequate torque of more or less 2000Nm).</s>
  
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.
+
After verification on the technical caracteristics of drills, impact screwdriver, and after a manual verification of resistances (brake,clutch) when the engine is not powered, this type of engine doesnt fit.
  
Pour pouvoir dimensionner une résistance à des vents entre la violente tempête et l'ouragan, on va donc procéder différemment:
+
To size a resistance to winds between the storm and hurricane we will proceed differently:
  
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.
+
We find on aliexpress a affordable prices (70€) step motors or endless screw motors with torque in an order of magnitude of 20Nm(200kgcm). We will use thi engine with two reductors 1:10 to get a resisting torque of 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
+
Recall: the torque expressed in Nm is a rotation force produced by the motor that can be computed with the same principle of the inertia moment: the force in newton x the distance to the rotation axis
  
Les transmissions par couroie ou chaine permettent de réduire ce torque/couple, et cela se calcule très simplement:
+
Belt or chain transmission permit to reduce this torque, and that can be calculated very easily
  
 
C1=(R1/R2)*C2
 
C1=(R1/R2)*C2
  
Avec C1 couple sur la poulie 1
+
With C1 torque on pulley 1
  
R1 rayon de la poulie 1
+
R1 pulley 1 radius
  
C2 couple sur la poulie2
+
C2 torque on pulley 2
  
R2 rayon de la poulie 2
+
R2 pulley 2 radius
  
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).
+
The radius is directly proportional to the number of teeth of a gear wheel (a bike cog or a motorcycle cog for example), we can easily calculate the transmission reduction by dividing the number of teeth of the big cog by the number of teeth of the little cog (verify they have the  same teeth standards)
  
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€.
+
Therefore a transmission 80 teeth/10 teeth (80T/10T) will produce a reduction of 1:8, like here on amazon for an electric bike  ("Keenso Kit Chaîne et Pignon 80T 25H 34mm 3 Trous Pignon 10T H Trou Chaîne Pignon 146 Maillons Chaîne") for 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.
+
We will notice that a standard bike transmission have a maximum transmission reduction of 50 teeth/11 teeth so a reduction of 1:5, which is not enough for efficient reduction witouth multiplying pulleys
  
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.
+
It is difficult to find transmissions with reduction of 1:10, but we find some on aliexpress and we will easily adapt a bicycle drive on which we will weld cogs, which will allow us a reduction of 1:100 with only one axis added in addition to the principal axis and the engine axis
  
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 ;))
+
So we will use an engine step motor or an endless screw engine (resistance unpowered test at reception) commanded by a raspberry pi (but the plate lifter has wheels, so we will take the precaution to put away the tracker when there's a storm ;))
  
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.
+
Carvan tips: to size the width of the metal frame, modern norms dont seem to be really made on scientific basis but on commercial basis. We will therefore measure the width of old material (made in the 70s). For example, the frame of this old caravan accredited on its registration document for a 750kg weigth has a metal frame of 5mm width. On can then make a rule of 3 to verify the width of our frame fits.
  
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 />
+
This is rustic style (strenght of material calculs is rigorous and complex), but it allows to have a firest approximation "a visto de nas" as we say in gascon<br />
 
<br />
 
<br />
 
|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
Ligne 249 : Ligne 249 :
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=Test la resistance des moteurs
+
|Step_Title=Engine resistance test
|Step_Content=Voir vidéos
+
|Step_Content=See videos
  
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.
+
The rotative engine ("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" on aliexpress) takes well 20Nm being powered and not being powered.
  
Rappel : torque ou couple = moment d'inertie engendré par le moteur
+
Recall: torque = moment of intertia generated by the motor
  
=force*distance à l'axe du moteur
+
=force*distance to motor axis
  
ici 4kg à 0,5m=9,8*4*0,5=20Nm (en ordre de grandeur)
+
here 4kg at 0,5m=9,8*4*0,5=20Nm (in order of magnitude)
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_resistance_a_vide_VID_20240529_185214.mp4
 
|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
 
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_resistance_sous_tension_VID_20240529_185517.mp4
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=Dimensionner : mesurer les angles et débattements
+
|Step_Title=Sizing: measurement of angles and displacement
|Step_Content=On va d'abord s'interesser à la trajectoire solaire ou solar trajectory en anglais.
+
|Step_Content=We will first look at the solar trajectory
  
On mesure typiquement la position du soleil selon deux systemes de coordonnées:
+
We measure typically the sun position along two coordinate systems:
  
le systeme equatorial avec des coordonnées exprimées en:
+
the equatorial system with coordinates expressed in:
  
ascension droite equivalente à la longitude terrestre mesurée en heures minutes secondes
+
right ascension equivalent to terrestrial longitude measured in hours minutes seconds
  
déclinaison équivalente à la latitude terrestre mesurée en degré minutes secondes
+
declination equivalent to terrestrial latitude measured in degrees minutes seconds
  
le systeme horizontal avec des coordonnées exprimées en:
+
The horizontal system with coordinates expressed in:
  
degré d'azimut
+
azimut degree
  
degré d'altitude ou de hauteur
+
altitude degree or height
  
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.
+
The solar trajectory abacus (for example available here https://www.astrolabe-science.fr/diagramme-solaire-azimut-hauteur ) give us the sun trajectories in a day (generally several typical days of several seasons) expressed in horizontal degrees.
  
Pour lire un graphique de ce type:
+
To read this type of grap
  
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:
+
If we follow the graph inserted in this tutorial and from the link above, when we follow for example the red curb for paris, we can read :
  
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'à de heuteur lorsqu'il se couche (vers l'Ouest sur l'axe horizontal).
+
The 21st of december, when we look at the south, the sun follows a trajectory that starts at -50° azimut (along the East on the horizontal axis) at dawn, then when the sun goes west during the day (we follow the red curb), it takes height until it reaches 17° height (on the vertical axis) at noon (position 0° azimut on the horizontal axis) and then goes down again to height when it sets (along the West on the horizontal axis)
  
Pour paris, on a :
+
For Paris, we have:
  
un degré d'azimut qui varie de -130° à +130° selon l'heure et la saison
+
One degree azimut varying from -130° to +130° according to the hour and the season
  
un degré d'altitude ou de hauteur qui varie de à 64° selon l'heure et la saison
+
One degree altitude or height varying from to 64° according to the hour and the season
  
Pour notre tracker,
+
For our 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.
+
To calculate our horizontal displacement (along the Oz vertical axis if the module is put down on the ground), on don't really have constraints on the plate lifter used since the axis turns 360° without problems.So we can follow the sun from -130° azimut to +130° azimut without problem.
  
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.
+
To calculate our vertical displacement (along the Ox horizontal axis if the module is put on the ground), we have a constraint on the maximal angle.
  
N'ayant pas de décimetre sous la main, on va utiliser pythagore (voir photo):
+
I dont have a decimeter at hand, so I will use pythagore (see photo):
  
 
64cm*150cm*134cm
 
64cm*150cm*134cm
Ligne 310 : Ligne 310 :
 
sinus phi=134/150
 
sinus phi=134/150
  
sinus phi=0,8933
+
sinus phi=134/150
  
 
phi=1,1046 rad
 
phi=1,1046 rad
Ligne 316 : Ligne 316 :
 
phi=1,1046*180/pi=63°
 
phi=1,1046*180/pi=63°
  
On a donc une contrainte pour notre leve plaque qui accepte des angles selon l'axe Ox de à 63°.
+
We have a constrainte for our plate lifter accepting angles along the Ox axis from to 63°
  
Lorsque le tracker est à son angle maximum (63°), on est perpendiculaire au soleil lorsque le soleil est à un angle phi de
+
When the tracker is at its maximum angle (63°), we are perpendicular to the sun when the sun is at an angle phi of
 
phi=180-63-90=27°
 
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.
+
When the sun has an angle lower than 27°, the tracker can not follow and keep being perpendicular to the sun
  
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.
+
<div class="mw-translate-fuzzy">
 +
We see however that the stop is guaranteed by the spring (on the photo we sse the mark on the paint). We can win a bit of amplitude on the stop by drilling and making a notch in the jib).
 +
</div>
  
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)
+
The manual measurement of the displacement between the tube axis on which is fixed the crank handle and the back of the module when the plate lifter leans at its maximum angle gives us 42cm. (see photo)
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_pythagore1.jpg
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_pythagore1.jpg
 
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_pythagore2.jpg
 
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_pythagore2.jpg
Ligne 334 : Ligne 336 :
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=Installer le verin sur l'axe horizontal
+
|Step_Title=Install the hydraulic cylinder on the horizontal axis
|Step_Content=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.
+
|Step_Content=We will fix a jib on the fix part of the plate lifter that turns with the vertical axis, so we can fix a rod on which we will fix the hydraulic cylinder which will be able to turn with the vertical axis so it can adjust the angle on the horizontal axis.
  
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.
+
We begin by fixing the metal jib welding to the fix part regarding the vertical axis. It requires to sand the paint before the welding is done (see photo). We do arc welding here.
  
On perce ensuite une tige en metal qu'on vient boulonner à la potence (voir photo).
+
Then we drill a metal rod we will screw to the jib (see 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).
+
We drill et we fix a metal rod we screw to the mobile part that allows to adjust the angle on the horizontal axis, ie the arm that permits to carry the plate or the photovoltaic module (see 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)
+
We then fix the hydraulic cylinder to the two metal rods. The hydraulic cylinder is equiped with fixing that adjust to the angle taken by the rods on which it is fixed (see 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.
+
<div class="mw-translate-fuzzy">
 +
Notice we have a metal rod with a right angle that avoid this fix is totally free. It will stop on a part of the rod, which permits then to calibrate more precisely the hydraulic cylinder amplitude).
 +
</div>
  
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)
+
We then test the course of the hydraulic cylinder. We will pay attention, because the stop of the plate lifter corresponds to a 40cm course for the hyrdaulic cylinder and it can extend up to 50cm. (see photos)
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_potence.jpg
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_potence.jpg
 
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_tige1.jpg
 
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_tige1.jpg
Ligne 356 : Ligne 360 :
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=Augmenter l'angle maximum du lève plaque
+
|Step_Title=Raise the plate lifter maximum angle
|Step_Content=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=After observing the critical elements, we will modify the jib to avoid a stop when the sun has a altitude degree lower than 27°.
  
Pour cela on va :
+
To do so, we will:
  
-laisser passer le ressort en évidant la potence (pour éviter que le ressort fasse butée)
+
-let the spring go by scooping the jib (to avoid the spring be a stop)
  
-augmenter l'angle en abaissant la fixation du ressort en perçant la potence
+
-raise the angle by lowering the fix of the spring drilling the jib
  
-augmenter l'angle en coupant les bords de la potence
+
-raise the angle cutting the edges of the jib
  
Voir photos :
+
See photos:
  
1/2: observation dessus/dessous
+
1/2: observation top/below
  
3/4: demontage potence
+
3/4: jib disassembly
  
4/5 : observation dessous, angle max à l'equerre
+
4/5: obersvation below, maximum angle at square
  
On arrive ainsi à un angle maximum de quasi 90° et on peut donc suivre le soleil sous tous les angles!
+
We reach an maximum angle of almost 90° and we can completely follow the sun!
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_observation_dessus.jpg
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_observation_dessus.jpg
 
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_observation_dessous.jpg
 
|Step_Picture_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_observation_dessous.jpg
Ligne 384 : Ligne 388 :
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=Tester les commandes du verin et du moteur rotatif
+
|Step_Title=Test the hydraulic cylinder and rotation engine commands
|Step_Content=Pour controller le verin, on va utiliser un raspberry pi, l'ordinateur monocarte le plus répandu.
+
|Step_Content=To control the hydraulic cylinder, we will use a rapsberry pi, the most widespread monocard computer. It is equiped with a serie of 40 pins, we can connect devices to, called GPIO controller.
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
+
A first reading of a few tutorials and available libraries to use it a some time taken to test wrong hypothesis to install correctly using the good versions leads me to talk a few possible options:
exposer les options possibles:
 
  
-système d'exploitation :
+
-operating system:
  
*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)
+
*dietpi images (debian) available here: https://dietpi.com/#download  (see my other tutorial for how to activate wifi : https://wiki.lowtechlab.org/wiki/Serveur_orangepi-raspberry_nextcloud_en_photovolta%C3%AFque_autonome)
  
*raspberry pi os (systeme d'exploitation par défaut) images disponibles ici: https://www.raspberrypi.com/software/operating-systems/
+
*rapsberry pi os (default) images available here: https://www.raspberrypi.com/software/operating-systems/
  
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!):
+
Archives and operating systems versions usefull for retrocompatibility (every reader caring for low tech computer is encouraged to keep local copies of these archives and to share them in torrent!):
  
 
<nowiki>*</nowiki>dietpi:
 
<nowiki>*</nowiki>dietpi:
Ligne 409 : Ligne 411 :
 
si vous utilisez wheezy,
 
si vous utilisez wheezy,
  
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:
+
NB: ChatGPT gives us the following rapsberry release dates:
  
#'''Raspberry Pi Model B''' : 29 février 2012
+
#'''Raspberry Pi Model B''' : 29 february 2012
#'''Raspberry Pi Model A''' : 4 février 2013
+
#'''Raspberry Pi Model A''' : 4 february 2013
#'''Raspberry Pi Model B+''' : 14 juillet 2014
+
#'''Raspberry Pi Model B+''' : 14 july 2014
#'''Raspberry Pi Model A+''' : 10 novembre 2014
+
#'''Raspberry Pi Model A+''' : 10 november 2014
#'''Raspberry Pi 2 Model B''' : 2 février 2015
+
#'''Raspberry Pi 2 Model B''' : 2 february 2015
#'''Raspberry Pi Zero''' : 30 novembre 2015
+
#'''Raspberry Pi Zero''' : 30 november 2015
#'''Raspberry Pi 3 Model B''' : 29 février 2016
+
#'''Raspberry Pi 3 Model B''' : 29 february 2016
#'''Raspberry Pi Zero W''' : 28 février 2017
+
#'''Raspberry Pi Zero W''' : 28 february 2017
#'''Raspberry Pi 3 Model B+''' : 14 mars 2018
+
#'''Raspberry Pi 3 Model B+''' : 14 march 2018
#'''Raspberry Pi 3 Model A+''' : 15 novembre 2018
+
#'''Raspberry Pi 3 Model A+''' : 15 november 2018
#'''Raspberry Pi 4 Model B''' : 24 juin 2019
+
#'''Raspberry Pi 4 Model B''' : 24 june 2019
#'''Raspberry Pi 400''' : 2 novembre 2020
+
#'''Raspberry Pi 400''' : 2 november 2020
#'''Raspberry Pi Pico''' : 21 janvier 2021
+
#'''Raspberry Pi Pico''' : 21 january 2021
#'''Raspberry Pi Zero 2 W''' : 28 octobre 2021
+
#'''Raspberry Pi Zero 2 W''' : 28 october 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.
+
We hope this is true (it comes from chatgpt), but you can verify on what's written on the pcb of your card.
  
Pour vérifier la version de votre raspberry sous raspberry pi os si vous ne savez pas quelle version cest(voir photo):
+
To verify the version of your raspberry under raspberry pi os if you dont know what version it is (see photo):
  
 
<pre>sudo usermod -a G gpio pi
 
<pre>sudo usermod -a G gpio pi
 
pinout</pre>
 
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!).
+
Adjust with the operating system that you think is relevant based on your relevancy filters. You have a list of old os versions of dietpi and raspberry pi os in case new versions would now ork anymore (and i repeat: every reader caring of low tech computer is encouraged to download and keep local copies of these archives and share them in torrent!)
  
Pour l'install, comme d'habitude, telecharger balenaetcher, flasher une clé usb avec l'image téléchargée,
+
To install, as usually, download balenaetcher, flash a usb key with the donwloaded image, boot.
booter.
+
The default login/password on dietpi are root/dietpi and for raspberry pi os pi/raspberry (take care to the default qwerty on raspberry pi os at boot).  
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).
+
To configure keyboard, locales, timezone and wifi, do:
Pour configurer le clavier, les locales, la timezone et le wifi, faire
+
<pre>sudo dietpi-config</pre> under dietpi and <pre>sudo raspi-config</pre> under raspberry pi.
<pre>sudo dietpi-config</pre> sous dietpi et <pre>sudo raspi-config</pre> sous raspberry pi.
 
  
-driver utilisé pour controler le gpio
+
-driver used to control the gpio
  
*RPi.GPIO(dev independant) ou RPi.GPIO2(dev redhat, repo recent)
+
*RPi.GPIO(independant dev) or RPi.GPIO2(redhat dev, recent repo)
  
Essais infructueux avec pip et le depot pypi (erreur de compilations etc.).  
+
Failure with pip and pypi repository (compilation errors etc.).  
Installer en passant en root avec la commande
+
Install as root with command
 
<pre>sudo -s </pre>
 
<pre>sudo -s </pre>
le plus simple est alors d'installer une version déjà compilée avec apt:
+
The simpler is to install a precompiled version with apt:
 
<pre>sudo apt install python3-rpi.gpio</pre>
 
<pre>sudo apt install python3-rpi.gpio</pre>
faire un test pour voir si ca fonctionne bien (toujours en etant utilisateur root):
+
do a test to see if that works well (as root):
 
<pre>python3
 
<pre>python3
 
import RPi.GPIO</pre>
 
import RPi.GPIO</pre>
  
Se reporter à la doc de sourceforge, plus explicite que celle de pypi:
+
See the sourceforge documentation, more explicit than the pypi one:
 
http://sourceforge.net/p/raspberry-gpio-python/wiki/Home/
 
http://sourceforge.net/p/raspberry-gpio-python/wiki/Home/
  
Ligne 464 : Ligne 464 :
 
*gpiozero
 
*gpiozero
  
installé par défaut sous raspberry pi
+
under dietpi do
 
sous dietpi faire
 
 
<pre>sudo apt install python3 python3-venv python3-pip
 
<pre>sudo apt install python3 python3-venv python3-pip
 
python3 -m venv venv
 
python3 -m venv venv
Ligne 472 : Ligne 470 :
 
pip install lgpio gpiozero</pre>
 
pip install lgpio gpiozero</pre>
  
Le premier point à comprendre est la numérotation des fiches:
+
The first point to understand is the numbering of the pins:
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.
+
The pins have all a number that goes from 1 to 40 following an order from bottom to top and from left to right, and each pin has also a gpiio number which is different from the pin number.
  
Pour cela on peut chercher les infos sur internet:  
+
To do so, we can find information on 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
+
or type the command under raspberry pi os (see image)
ou taper les commande suivantes dans raspberry pi os (voir image)
 
  
 
<pre>
 
<pre>
Ligne 484 : Ligne 481 :
 
</pre>
 
</pre>
  
Dans ce tuto, que ce soit avec gpiozero ou RPi.GPIO, on utilisera la numerotation GPIO et pas la numérotation pin
+
In this tutorial, wether for gpiozero or RPi.GPIO, we will use the GPIO numbering and not the pin numbering
  
On branche les GPIO 2 et 4 (alim +5V) aux deux fiches +5V du HBrdige
+
We plug the GPIO 2 and 4 (+5V) to the +5V pins of the HBridge
On branche les GPIO 6 et 7 (Ground, la terre) aux deux fiches GND du HBrdige
+
We plug the GPIO 6 and 7 (Ground) to the GND pin of the HBridge
On branche les GPIO 23 et 16 à l'interrupteur 3 du HBridge  
+
We plug the GPIO 23 and 16 to the switch 3 of the HBridge
Pour tester le "enable" du HBridge, on branche les GPIO 20 et 21 au ENA du HBridge
+
To test the "enable" of the HBridge, we plug the GPIO 20 and 21 to EAN of HBridge
  
On  utilise ensuite les scripts suivants :
+
We then use the following scripts:
  
 
<pre>
 
<pre>
Ligne 507 : Ligne 504 :
 
     try:
 
     try:
 
         print("forwardzero")
 
         print("forwardzero")
         #cas où le hbridge necessite un signal enable
+
         #case where Hbridge needs a enable signal
 
         led21.off()
 
         led21.off()
 
         led20.on()
 
         led20.on()
         #signal à zero sur interupteur 2
+
         #zero signal off on switch 2
 
         led1.off()
 
         led1.off()
 
         led7.off()
 
         led7.off()
         #mettre un signal sur l'interrupteur 1
+
         #positive signal off on switch 1
 
         led23.on()
 
         led23.on()
 
         led16.off()
 
         led16.off()
         #laisser le signal actif le temps de wait
+
         #let signal active during wait time
 
         for k in range(wait):
 
         for k in range(wait):
 
             print(k)
 
             print(k)
 
             time.sleep(1)
 
             time.sleep(1)
         #eteindre le signal sur l'interrupteur
+
         #put signal off on the switch
 
         led23.off()
 
         led23.off()
 
         led20.off()
 
         led20.off()
Ligne 529 : Ligne 526 :
 
     finally:
 
     finally:
 
         print("zero")
 
         print("zero")
         #signal à zero sur interupteur 2
+
         #zero signal off on switch 2
 
         led1.off()
 
         led1.off()
 
         led7.off()
 
         led7.off()
         #signal à zero sur l'interrupteur 1
+
         #signal off on switch 1
 
         led16.off()
 
         led16.off()
 
         led23.off()
 
         led23.off()
         #signal à zero sur la fiche enable
+
         #signal off on enable pin
 
         led20.off()
 
         led20.off()
 
         led21.off()
 
         led21.off()
Ligne 547 : Ligne 544 :
 
     try:
 
     try:
 
         print("backwardzero")
 
         print("backwardzero")
         #signal zero sur interupteur 1
+
         #signal off on switch 1
 
         led16.on()
 
         led16.on()
 
         led23.off()
 
         led23.off()
         #cas où le hbrige necessite un signal sur enable
+
         #case where Hbridge needs a enable signal
 
         led20.off()
 
         led20.off()
 
         led21.on()
 
         led21.on()
         #signal sur interrupteur 2
+
         #signal on switch 2
 
         led1.off()
 
         led1.off()
 
         led7.off()
 
         led7.off()
Ligne 560 : Ligne 557 :
 
             print(k)
 
             print(k)
 
             time.sleep(1)
 
             time.sleep(1)
         #signal off sur interrupteur
+
         #signal off on switches
 
         led16.off()
 
         led16.off()
 
         led21.off()
 
         led21.off()
Ligne 569 : Ligne 566 :
 
     finally:
 
     finally:
 
         print("zero")
 
         print("zero")
         #signal à zero sur interupteur 1
+
         #signal off on switch 1
 
         led16.off()
 
         led16.off()
 
         led23.off()
 
         led23.off()
         #signal à zero sur l'interupteur 2
+
         #signal off on switch 2
 
         led1.off()
 
         led1.off()
 
         led7.off()
 
         led7.off()
         #signal à zero sur la fiche enable
+
         #signal off on enable pin
 
         led20.off()
 
         led20.off()
 
         led21.off()
 
         led21.off()
Ligne 615 : Ligne 612 :
 
         GPIO.output(23,GPIO.LOW)
 
         GPIO.output(23,GPIO.LOW)
 
         GPIO.output(16,GPIO.LOW)
 
         GPIO.output(16,GPIO.LOW)
         #GPIO.cleanup() chez moi, ca n'enleve pas les +3V
+
         #GPIO.cleanup() on my sbc it doesnt remove the +3V
 
def backward(wait):
 
def backward(wait):
 
     GPIO.setmode(GPIO.BCM)
 
     GPIO.setmode(GPIO.BCM)
Ligne 649 : Ligne 646 :
 
         GPIO.output(1,GPIO.LOW)
 
         GPIO.output(1,GPIO.LOW)
 
         GPIO.output(7,GPIO.LOW)
 
         GPIO.output(7,GPIO.LOW)
         #GPIO.cleanup() chez moi ca n'enleve pas les +3V
+
         #GPIO.cleanup() on my sbc it doesnt remove the +3V
forward(2) #rotation horaire motor2
+
forward(2) #rotation horary motor2
backward(2) #rotation antihoraire motor2
+
backward(2) #rotation antihorary motor2
forwardzero(10) #verin extension motor1 111 max
+
forwardzero(10) #hydraulic cylinder extension motor1 111 max
backwardzero(10) #verin retractation motor1 107 max
+
backwardzero(10) #hydraulic cylinder retractation motor1 107 max
 
</pre>
 
</pre>
  
GPIO.BCM permet d'utiliser la numerotation GPIO
+
GPIO.BCM allows to use GPIO numbering
GPIO.HIGH envoie un signal de +3V dans la fiche concernée
+
GPIO.HIGH sends a +3V signal in the concerned pin
GPIO.LOW remet le signal à 0V (GND, la terre)
+
GPIO.LOW sets the signal to 0V (GND)
gpiozero fait la meme chose avec les methodes .on() et .off()
+
gpiozero does the same with  .on() and .off() methods
  
Update 31.05.24 : vous l'attendiez, voilà l'update du jour avec le hbridge made in europe, ca roule, voir vidéo! :)
+
Update 31.05.24: you were waiting for it, here's today update with the HBridge made in Europe. It works, see video!
Update de l'étalonnage rapidement à reception d'un truc pour mesurer les angles un peu pratique.
+
Update of calibration quickly at recepetion of something to measure angles a bit more handy.
  
Remarquez que les fiches 20 et 21 ne sont pas branchées au hbridge.  
+
Notice the pins 20 and 21 are not plugged to the HBridge.  
Le "pwm" (pulse width modulation) n'est pas utilisé
+
The "PWM" (pulse width modulation) is not used to "activate" (enable in the code) the engine because jumbers on ENA pins of the Hbridge are enough (here we dont need to adjust motor speed)
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).
 
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_apt_install.jpg
 
|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_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_pinout.jpg
Ligne 674 : Ligne 669 :
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=Verin hydraulique pour la rotation d'axe vertical
+
|Step_Title=Hydraulic cylinder for vertical axis rotation
|Step_Content='''<s><big>Fixer les poulies et la transmission 1:100 pour le moteur rotatif</big></s>'''
+
|Step_Content='''<s><big>Fix pulleys and 1:100 transmission for rotation motor</big></s>'''
  
<s>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.</s>
+
<s>We begin by recycling two bycicle drives from second hand bikes we cut with a grinder taking care to keep the metal rod of the frame.</s>
  
 
<s><br />
 
<s><br />
On va venir y souder les pignons 92T (92 dents).</s>
+
we will weld 92T cogs on it.</s>
  
<s>On soude ensuite les pignons 8T (8 dents) dessus.</s>
+
<s>We then weld the 8T cogs on it.</s>
  
<s>On soude un pignon 8T sur un pignon avec une clavette qui s'adapte à l'axe du moteur.</s>
+
<s>We weld a 8T cog on a locked cog adapted to the motor axis.</s>
  
<s>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.</s>
+
<s>We cut a metal rod and we weld a flat metal plate along the axis of the plate lifter on which we will screw the bike metal rods attached to the bike drives and alos a metal rod on which we will fix the motor.</s>
  
<s>On assemble et on boulonne.</s>
+
<s>We assemble and we screw.</s>
  
<s>Update à réceptiond de la 3eme chaine et en attendant de réfléchir à un moyen de régler les tensions de chaine.</s>
+
<s>Update when receiving the 3rd chain and waiting how to fix the chain tensions.</s>
  
 
<s><br />
 
<s><br />
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).</s>
+
Update of 11.6.24: the constraints of the axis of the plate lifter make it difficult to fix a big cog to get a good reduction on the last transmission chain. Even we have several reductions with other pulleys, the plate lifter is not rotating (see video).</s>
  
<s>update du 16.6.24: réception du verin supplémentaire, date livraison estimée : 28 juin-2juillet</s>
+
<s>update of 16.6.24: additional hydraulic cylinder ordered. Estimated delivery 28 juin-2juillet</s>
  
 
<s><br />
 
<s><br />
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)</s>
+
We will make the rotation with two hydraulic cylinders. Update when received at the end of june (not available these next weeks anyway).</s>
  
La tension de chaine etant mauvaise, on remplace les transmissions par un verrin hydraulique permettant de faire la rotation mais sur un angle réduit (environ 70° faute de verrin telescopique à plusieurs brins)
+
The chain tension is bad, we replace the tranmissions with a hydraulic cylinder allowing a rotation but with a reduced angle (about 70° because it is not a 2 or 3 parts telescopic cylinder)
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_IMG_20240605_134410.jpg
 
|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_01=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_IMG_20240605_174848.jpg
Ligne 709 : Ligne 704 :
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=Etalonner les moteurs
+
|Step_Title=Engine calibration
|Step_Content=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)
+
|Step_Content=The updated code is as follow: we define dictionnaries associating each sought angle to an activation time of the engine (each must be manually tested and measured)
  
 
<pre>
 
<pre>
#Etalonnage
+
#Calibration
 
#dict_angle_rotation= sun_degre_azimut:motoractivationtime
 
#dict_angle_rotation= sun_degre_azimut:motoractivationtime
 
dict_angle_verin={1:0,
 
dict_angle_verin={1:0,
Ligne 878 : Ligne 873 :
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=Coder le tracking en "dur"
+
|Step_Title=Hard coding the tracking
|Step_Content=Le code mis à jour est le suivant
+
|Step_Content=The updated code is as follows:
  
 
<pre>
 
<pre>
Ligne 885 : Ligne 880 :
 
import RPi.GPIO as GPIO
 
import RPi.GPIO as GPIO
 
import gpiozero
 
import gpiozero
import ephem
+
 
import datetime
 
 
def forwardzero(wait):
 
def forwardzero(wait):
 
     led16=gpiozero.LED(16) #motor1
 
     led16=gpiozero.LED(16) #motor1
Ligne 896 : Ligne 890 :
 
     try:
 
     try:
 
         print("forwardzero")
 
         print("forwardzero")
         #cas où le hbridge necessite un signal enable
+
         #case where Hbridge needs a enable signal
         led21.on()
+
         led21.off()
         led20.off()
+
         led20.on()
         #signal à zero sur interupteur 2
+
         #zero signal off on switch 2
 
         led1.off()
 
         led1.off()
 
         led7.off()
 
         led7.off()
         #mettre un signal sur l'interrupteur 1
+
         #positive signal off on switch 1
 
         led23.on()
 
         led23.on()
 
         led16.off()
 
         led16.off()
         #laisser le signal actif le temps de wait
+
         #let signal active during wait time
 
         for k in range(wait):
 
         for k in range(wait):
 
             print(k)
 
             print(k)
 
             time.sleep(1)
 
             time.sleep(1)
         #eteindre le signal sur l'interrupteur
+
         #put signal off on the switch
 
         led23.off()
 
         led23.off()
         led21.off()
+
         led20.off()
 
     except KeyboardnInterrupt:
 
     except KeyboardnInterrupt:
 
         print("keyboard interrupt")
 
         print("keyboard interrupt")
Ligne 918 : Ligne 912 :
 
     finally:
 
     finally:
 
         print("zero")
 
         print("zero")
         #signal à zero sur interupteur 2
+
         #zero signal off on switch 2
 
         led1.off()
 
         led1.off()
 
         led7.off()
 
         led7.off()
         #signal à zero sur l'interrupteur 1
+
         #signal off on switch 1
 
         led16.off()
 
         led16.off()
 
         led23.off()
 
         led23.off()
         #signal à zero sur la fiche enable
+
         #signal off on enable pin
 
         led20.off()
 
         led20.off()
 
         led21.off()
 
         led21.off()
Ligne 936 : Ligne 930 :
 
     try:
 
     try:
 
         print("backwardzero")
 
         print("backwardzero")
         #signal zero sur interupteur 1
+
         #signal off on switch 1
 
         led16.on()
 
         led16.on()
 
         led23.off()
 
         led23.off()
         #cas où le hbrige necessite un signal sur enable
+
         #case where Hbridge needs a enable signal
 
         led20.off()
 
         led20.off()
 
         led21.on()
 
         led21.on()
         #signal sur interrupteur 2
+
         #signal on switch 2
 
         led1.off()
 
         led1.off()
 
         led7.off()
 
         led7.off()
Ligne 949 : Ligne 943 :
 
             print(k)
 
             print(k)
 
             time.sleep(1)
 
             time.sleep(1)
         #signal off sur interrupteur
+
         #signal off on switches
 
         led16.off()
 
         led16.off()
 
         led21.off()
 
         led21.off()
Ligne 958 : Ligne 952 :
 
     finally:
 
     finally:
 
         print("zero")
 
         print("zero")
         #signal à zero sur interupteur 1
+
         #signal off on switch 1
 
         led16.off()
 
         led16.off()
 
         led23.off()
 
         led23.off()
         #signal à zero sur l'interupteur 2
+
         #signal off on switch 2
 
         led1.off()
 
         led1.off()
 
         led7.off()
 
         led7.off()
         #signal à zero sur la fiche enable
+
         #signal off on enable pin
 
         led20.off()
 
         led20.off()
 
         led21.off()
 
         led21.off()
Ligne 1 004 : Ligne 998 :
 
         GPIO.output(23,GPIO.LOW)
 
         GPIO.output(23,GPIO.LOW)
 
         GPIO.output(16,GPIO.LOW)
 
         GPIO.output(16,GPIO.LOW)
         #GPIO.cleanup() chez moi, ca n'enleve pas les +3V
+
         #GPIO.cleanup() on my sbc it doesnt remove the +3V
 
def backward(wait):
 
def backward(wait):
 
     GPIO.setmode(GPIO.BCM)
 
     GPIO.setmode(GPIO.BCM)
Ligne 1 012 : Ligne 1 006 :
 
     GPIO.setup(7,GPIO.OUT)  #motor2
 
     GPIO.setup(7,GPIO.OUT)  #motor2
 
     GPIO.setup(16,GPIO.OUT) #motor1
 
     GPIO.setup(16,GPIO.OUT) #motor1
     GPIO.setup(23,GPIO.OUT) #motr1
+
     GPIO.setup(23,GPIO.OUT) #motor1
 
     try:
 
     try:
 
         print("backward")
 
         print("backward")
         GPIO.output(21,GPIO.HIGH)      
+
         GPIO.output(21,GPIO.HIGH)
 
         GPIO.output(20,GPIO.LOW)
 
         GPIO.output(20,GPIO.LOW)
 
         GPIO.output(16,GPIO.LOW)
 
         GPIO.output(16,GPIO.LOW)
Ligne 1 031 : Ligne 1 025 :
 
         print(err)
 
         print(err)
 
     finally:
 
     finally:
         print("zero")      
+
         print("zero")
 
         GPIO.output(20,GPIO.LOW)
 
         GPIO.output(20,GPIO.LOW)
 
         GPIO.output(21,GPIO.LOW)
 
         GPIO.output(21,GPIO.LOW)
Ligne 1 038 : Ligne 1 032 :
 
         GPIO.output(1,GPIO.LOW)
 
         GPIO.output(1,GPIO.LOW)
 
         GPIO.output(7,GPIO.LOW)
 
         GPIO.output(7,GPIO.LOW)
         #GPIO.cleanup() chez moi ca n'enleve pas les +3V
+
         #GPIO.cleanup() on my sbc it doesnt remove the +3V
#forward(41) #rotation horaire motor2
+
 
#backward(90) #rotation antihoraire motor2
 
#forwardzero(10) #verin extension motor1 111 max
 
#backwardzero(2) #verin retractation motor1 107 max
 
#forwardzero(90)
 
 
#Etalonnage
 
#Etalonnage
 
#dict_angle_verin= sun_degre_horizontal:motoractivationtime
 
#dict_angle_verin= sun_degre_horizontal:motoractivationtime
Ligne 1 213 : Ligne 1 203 :
 
     #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
 
     #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
 
     # Europe/Paris is GMT+2
    #tester angles pyephem sur mesures réelles
 
 
     utc_now=datetime.datetime.utcnow()
 
     utc_now=datetime.datetime.utcnow()
 
     #is_dst=datetime.datetime(year=utc_now.year,month=utc_now.month,day=utc_now.day).dst()
 
     #is_dst=datetime.datetime(year=utc_now.year,month=utc_now.month,day=utc_now.day).dst()
Ligne 1 256 : Ligne 1 245 :
 
}}
 
}}
 
{{Tuto Step
 
{{Tuto Step
|Step_Title=Coder le tracking avec une IA
+
|Step_Title=AI Coding the tracking
|Step_Content=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)
+
|Step_Content=We will now code a lowtech AI for the educational part (Machine Learning here, as massively used since about fifteen years in many industries, ie no AI in the chatgpt sense as it is usually understood in 2024). We could say lowtech AI is AI with results not being magic, where data and code are open source, not stolen, where data belong to us, by us and for us, and on which we are in control of play (this last point is essential but the experience to step out of the line drives me to pessimism on this matter because testing if there is interference in an machine learning result is difficult to detect)
  
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).
+
We plug a webcam, we record the images as input data, we process the image to make a digit table corresponding to variables with which on wee seek to correlate with a positive or negative signal (turn the engine in one direction, or stop)
  
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).
+
Here, the input data are only the 640*480*3=921600 variables of pixels of the images in the video (921600 colunns/variables per line, from which we seek to correlate with a positive signal 1 or 0 of the last column).
  
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.
+
To have the tracker work, this method will not fits well, it would require to do "feature engineering" (complicated name to say add columns of variables more probably correlated to the positive signal) by adding luminosity and/or date and time, on video samples covering all seasons on many years.
  
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".
+
If you want to learn the basics of AI on which this code relies, I recommand the course "Applied Data Science with Python" of michigan university in which you will learn python, pandas and machine learning basics "no bullshit".
  
 
<pre>
 
<pre>
Ligne 1 290 : Ligne 1 279 :
 
     cap = cv2.VideoCapture(0)  # Use 0 for default webcam
 
     cap = cv2.VideoCapture(0)  # Use 0 for default webcam
  
fourcc = cv2.VideoWriter_fourcc(*'XVID')  # Codec (e.g., XVID)
+
    fourcc = cv2.VideoWriter_fourcc(*'XVID')  # Codec (e.g., XVID)
  
frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))  # Get webcam frame width
+
    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
 
     frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))  # Get webcam frame height
  
# Check if webcam opened successfully
+
    # Check if webcam opened successfully
 
     if not cap.isOpened():
 
     if not cap.isOpened():
 
         print("Error opening webcam")
 
         print("Error opening webcam")
 
         exit()
 
         exit()
  
# Create the VideoWriter object
+
    # Create the VideoWriter object
 
     out = cv2.VideoWriter(out_file, fourcc, fps, (frame_width, frame_height))
 
     out = cv2.VideoWriter(out_file, fourcc, fps, (frame_width, frame_height))
  
# Start time for tracking duration
+
  # Start time for tracking duration
 
     start_time = time.time()
 
     start_time = time.time()
 
     while time.time() - start_time < capture_duration:
 
     while time.time() - start_time < capture_duration:
Ligne 1 309 : Ligne 1 298 :
 
         ret, frame = cap.read()
 
         ret, frame = cap.read()
  
# Check if frame captured successfully
+
      # Check if frame captured successfully
 
         if not ret:
 
         if not ret:
 
             print("Error capturing frame")
 
             print("Error capturing frame")
 
             break
 
             break
  
# Write the frame to the video file
+
        # Write the frame to the video file
 
         out.write(frame)
 
         out.write(frame)
 
      
 
      
Ligne 1 324 : Ligne 1 313 :
 
             break
 
             break
  
# Close resources
+
        # Close resources
 
     cap.release()
 
     cap.release()
 
     out.release()
 
     out.release()
 
     cv2.destroyAllWindows()
 
     cv2.destroyAllWindows()
  
print(f"1 minute video saved successfully as {out_file}!")
+
    print(f"1 minute video saved successfully as {out_file}!")
  
 
def charger_images_video(video_filename):
 
def charger_images_video(video_filename):
 
   """
 
   """
   Charge les images vidéo d'un fichier.
+
   Load video images from a file
  
Args:
+
  Args:
 
       video_filename: Le chemin d'accès au fichier vidéo.
 
       video_filename: Le chemin d'accès au fichier vidéo.
  
Returns:
+
  Returns:
       Un tableau NumPy contenant les images vidéo (3D array: frames, rows, cols).
+
       a numpy table with video images (3D array: frames, rows, cols).
 
   """
 
   """
  
# Ouvrir la vidéo avec OpenCV
+
  # Ouvrir la vidéo avec OpenCV
 
   cap = cv2.VideoCapture(video_filename)
 
   cap = cv2.VideoCapture(video_filename)
  
# Vérifier l'ouverture réussie
+
  # Vérifier l'ouverture réussie
 
   if not cap.isOpened():
 
   if not cap.isOpened():
 
       print("Erreur d'ouverture du fichier vidéo:", video_filename)
 
       print("Erreur d'ouverture du fichier vidéo:", video_filename)
 
       return None
 
       return None
  
# Liste vide pour stocker les images vidéo
+
  # Liste vide pour stocker les images vidéo
 
   images_list = []
 
   images_list = []
  
# Lire les images vidéo image par image
+
  # Lire les images vidéo image par image
 
   while True:
 
   while True:
 
       ret, frame = cap.read()
 
       ret, frame = cap.read()
  
# Vérifier la lecture de l'image
+
      # Vérifier la lecture de l'image
 
       if not ret:
 
       if not ret:
 
           break
 
           break
  
# Convertir l'image en nuance de gris (optionnel pour la normalisation)
+
         
 +
          # Convertir l'image en nuance de gris (optionnel pour la normalisation)
 
       # frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # Décommenter si nécessaire
 
       # frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # Décommenter si nécessaire
  
# Ajouter l'image à la liste
+
      # Ajouter l'image à la liste
 
       images_list.append(frame)
 
       images_list.append(frame)
  
# Fermer la capture vidéo
+
      # Fermer la capture vidéo
 
   cap.release()
 
   cap.release()
  
return images_list
+
  return images_list
  
 
def flatten_images_video(images_array):
 
def flatten_images_video(images_array):
Ligne 1 383 : Ligne 1 373 :
 
     result=np.asarray(result)
 
     result=np.asarray(result)
  
return result
+
    return result
  
 
# Exemple d'utilisation
 
# Exemple d'utilisation
Ligne 1 423 : Ligne 1 413 :
 
# Total number of elements (height x width x color channels)
 
# Total number of elements (height x width x color channels)
 
total_elements = image_shape[0] * image_shape[1] * image_shape[2]
 
total_elements = image_shape[0] * image_shape[1] * image_shape[2]
print("Total elements:", total_elements)E
+
print("Total elements:", total_elements)
  
# création du dataset pour dire "oui" pour tourner à gauche (par exemple)
+
# dataset creation to say "yes" to turn left (for example)
# ND: c'est à cette étape que les géants de la tech emploient des kenyans sous payés
+
# NB: it's at this stage that tech giants employ kenyans at very low wages
# dans une forme d'esclavage moderne
+
# in a form of modern slavery
# il s'agit de définir, pour chaque image, si on doit activer le moteur vers
+
# it's about defining, for each image, if we must activate the motor
# la gauche (cad définir un signal positif pour que la machine fasse des corrélations
+
# on the loeft (ie defining a positive signal for the machine to correlate positively with this image)
# positives avec cette image)
 
  
 
positives=np.zeros_like(images_video)
 
positives=np.zeros_like(images_video)
#Si les 14 premieres images définissent un signal positif, on fera:
+
#If first 14 images define a positive signal, we will do:
#en réalité il faudra traiter des segments de vidéos positivement en définissant
+
#in reality it would require to process video intervals positively defining
#chaque image auxquelles on va associer le signal positif
+
#for each image those where we associate a positive signal  
 
positives[0:14] = 1
 
positives[0:14] = 1
  
Ligne 1 465 : Ligne 1 454 :
 
                             columns=[classifier])
 
                             columns=[classifier])
  
@staticmethod
+
    @staticmethod
 
     def compute_scores(y_test, _predicted, classifier):
 
     def compute_scores(y_test, _predicted, classifier):
 
         "compute machine learning scores"
 
         "compute machine learning scores"
Ligne 1 476 : Ligne 1 465 :
 
         return (_accuracy, _precision, _recall)
 
         return (_accuracy, _precision, _recall)
  
@staticmethod
+
    @staticmethod
 
     def compute_confusion_matrix(y_test, _predicted, _accuracy, classifier):
 
     def compute_confusion_matrix(y_test, _predicted, _accuracy, classifier):
 
         "compute confusion matrix"
 
         "compute confusion matrix"
Ligne 1 545 : Ligne 1 534 :
 
         return roc_auc_clf,result
 
         return roc_auc_clf,result
  
dico_classifier = { 'knn': KNeighborsClassifier,
+
        dico_classifier = { 'knn': KNeighborsClassifier,
 
                         'naiveb': GaussianNB,
 
                         'naiveb': GaussianNB,
 
                         'randomforest': RandomForestClassifier,
 
                         'randomforest': RandomForestClassifier,
Ligne 1 551 : Ligne 1 540 :
 
                         'neural': MLPClassifier}
 
                         'neural': MLPClassifier}
  
@staticmethod
+
    @staticmethod
 
     def plot_heatmap(dataframe):
 
     def plot_heatmap(dataframe):
 
         "plot heatmap of accuracy, precision, recall, AUC"
 
         "plot heatmap of accuracy, precision, recall, AUC"
Ligne 1 577 : Ligne 1 566 :
 
return df_result
 
return df_result
 
</pre>
 
</pre>
 +
  
 
Conclusion:
 
Conclusion:
Voilà, maintenant que vous savez coder une IA, vous pouvez la critiquer d'autant mieux,et promouvoir les lowtech en connaissance de cause.
+
Here, now you know how to code using AI, you can critize it better et promote low tech adequately
  
Vous noterez que les algorithmes d'ia sont open sources et assez faciles à utiliser en tant que développeur "simple utilisateur".
+
You will notice AI algorithms are open source and easy to use as simple "dev user".
  
Et aussi que sans data, l'ia ne sert absolument à rien.
+
And also that without data, AI is absolutely useless.
  
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.
+
That's the reason why big tech giants want more and more data and employ peolple in slavery conditions in many countries to process these data before training their models.
  
Tout comme pour les "données personnelles", la question clé des ia repose sur les données.
+
Just like for "personal data", the key question here with AI relies on data.
  
Voir l'excellente conf de benjamin bayart " Géopolitique de la data (Benjamin BAYART) " sur youtube ou en vidéo ici.
+
See the excellent conference of Benjamin Bayart "Geopolitique de la data (Benjamin BAYART)" on youtube or in video in this tutorial.
  
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.
+
You can make a lowtech tracker, but also adapt this code to create an autonomous vehicle lowtech with 4 distinct datasets/positive signals to train activation of "turn left", "accelerate", "brake", "turn right". This is what [https://fr.wikipedia.org/wiki/George_Hotz#Conduite_autonome George Hotz] did claiming it required only 30 hours of videos of driving and recorded sensors to have the needed positives signals correlated with the recorded images to have autonomous driving work.
  
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.
+
Of course, we strongly hope there won't be hack or that the system doesnt have a distant command and control on this type of algorithm.
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_G_opolitique_de_la_data_Benjamin_BAYART_.mp4
 
|Step_Picture_00=Dimensionner_et_faire_un_tracker_solaire_photovolat_que_low_tech_G_opolitique_de_la_data_Benjamin_BAYART_.mp4
 
}}
 
}}

Version actuelle datée du 3 août 2024 à 19:29

Tutorial de avatarAurelpere | Catégories : Énergie

Matériaux

tracker:

BRD plate lifter: 150€

hydraulic cylinder "Actionneur linéaire 12V DC , 1320LBS(6000N) 20 pouces (500mm) moteur électrique"  : 69€ on aliexpress, available on amazon a bit more expensive

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

2 chain cogs 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 cogs "Pignon pour scooter électrique 8T 9T 11T 13T 25H 410 420, pour moteur à courant continu 25H JOMotor MY1020 BM1109 MY1016Z MY1018"

10€

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

4€

Engine "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

Photovoltaic module Voltech 2mx1m 375W: 200€

Control:

raspberry: environ 100€

Outils

facom measuring tape: 20€

sovietic mass kit: 60€

spirit level: 5€

bracket: 6€

grinder: 50€

drill: 50€

welding machine: 100€

Étape 1 - Sizing : moment of inertia measurement

We set two axis for our module:

one axis Oz perpendicular to the module plane (vertical when the module is flat) and one axis Ox paralelle to the module plane (horizontal when the module is flat)

Caractéristics of the module:

mass: m=21,2 kg

Length: L=1,8m

Width: 1m

Wikipedia theory tells us intertia moment along Oz axis is :

Jdelta=1/12*m*L

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

Nb: we suppose the module is a bar because here the rotation axis is the axis paralell to the plane of the rectangle formed by the module and not the perpendicular axis

We now experimentally verify:

Center the module on the plate lifter and put it horizontally

verify there is no wind

check the level on which the mast of the plate lifter is put

fix a horizontal lanmark at the bottom of the module

set a 500g mass at the extremity of the module

measure the distance between the position at equilibrium and the position with the mass

We have:

Jdelta=d*F

with d distance in meter to the axis of rotation

F force applied to the solid (here mg with m solid mass in kg and g gravitational constant)

We then have

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

We measure the rotation: in our case, the distance of rotation at the extremity of the module varies between 3 and 10cm. The important variations

are due to frictions on the axis that can force or slide more freely

The order of magnitude of the inertia momen is verified

For the moment of inertia along axis Ox, it is more difficult to verify experimentally, because the axise of the plate lifter doesnt allow

to have a position at equilibrium with the module mass (which will finish on the stop under its own weight)

Therefore, we will accept the theory:

The theory says

Jdelta=1/12*m(b²+c²) with m mass of the module, b lenght of the short side of the module and c length of the long side of the module

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

This theoretical result is strongly lower than the weigth of the module, so we will size based on the necessary force to lift the weigth of the module (the axis being submitted to the weight):

F=mg=21,2*9,8

In order of magnitude 200Nm (1m of lever arm in order of magnitude)

We have now the caracteristics to size the engines of our module along two rotation axis

But the trackers have a consequent wind exposure

If we want to take into account the resistance to the wind, we must measure the force applied to the module according to wind speed:

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

With ​ρ air density equal to 1,2 kg/m3 in order of magnitude for standard temperature and pressure conditions

v wind speed in m/s

S object surface in m²

Cp pressure coefficient without dimension égal to 2 for a rectangular metal plate

We have:

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

Chatgpt can give us the abacus of wind speeds in km/h and their conversions in m/2 and the generic name in meteorology

Calm: Less than 1 km/h (Less than 0.3 m/s)

Very light breeze: 1-5 km/h (0.3-1.5 m/s)

Light breeze: 6-11 km/h (1.6-3.0 m/s)

Gentle breeze: 12-19 km/h (3.4-5.4 m/s)

Moderate breeze: 20-28 km/h (5.5-7.9 m/s)

Fresh breeze: 29-38 km/h (8.0-10.7 m/s)

Strong breeze: 39-49 km/h (10.8-13.8 m/s)

Moderate wind: 50-61 km/h (13.9-16.9 m/s)

Near gale: 62-74 km/h (17.2-20.6 m/s)

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

Strong gale: 89-102 km/h (24.7-28.3 m/s)

Storm: 103-117 km/h (28.6-32.5 m/s)

Hurricane: At least 118 km/h (At least 32.8 m/s)

So we have a Force Fp that can vary from

An order of magnitude of 20N for a ligth breeze

to

An order of magnitude of 2000N for a storm

(NB: the gravity force of 1kg is approximately 10N so the force of 2000N corresponds in order of magnitude to the gravity force of 200kg).

The inertia moments on the axis are of the same order of magnitude (20Nm and 2000Nm) because the dimensions of the module are an order of magnitude of 1 m

If you want to build a tracker that can resist to storm conditions, it is advised to size the tracker consequently

On the one hand with sufficient ties in the ground, and on the other hand with an adapted frame -see caravan tips at the end of this stage-, and deactivate

when there are stronger winds.)

Wind resistance sizing is one of the reasons trackers are expensive et less generalized than standard photovoltaic installations

The step motors and servomotors that have an adequate torke to resist to important winds are expensive, et that can be understandable for motors designed for step precision

(For example here: https://www.distrelec.fr/fr/automatisation/moteurs-et-entrainements/moteurs-pas-pas-et-servocommandes/c/cat-L3D_525513 )

For hydraulic cylinders, the driving force is generally in orders of magnitude fitting to resist to storms.

For our low-tech tutorial, we know that core drilling machines have torque in an order of magnitude fitting to resist to storms (1W corresponds to 1 N multiplying 1 meter per second, so a core drilling machine of 2000W should have an adequate torque of more or less 2000Nm).

After verification on the technical caracteristics of drills, impact screwdriver, and after a manual verification of resistances (brake,clutch) when the engine is not powered, this type of engine doesnt fit.

To size a resistance to winds between the storm and hurricane we will proceed differently:

We find on aliexpress a affordable prices (70€) step motors or endless screw motors with torque in an order of magnitude of 20Nm(200kgcm). We will use thi engine with two reductors 1:10 to get a resisting torque of 2000Nm.

Recall: the torque expressed in Nm is a rotation force produced by the motor that can be computed with the same principle of the inertia moment: the force in newton x the distance to the rotation axis

Belt or chain transmission permit to reduce this torque, and that can be calculated very easily

C1=(R1/R2)*C2

With C1 torque on pulley 1

R1 pulley 1 radius

C2 torque on pulley 2

R2 pulley 2 radius

The radius is directly proportional to the number of teeth of a gear wheel (a bike cog or a motorcycle cog for example), we can easily calculate the transmission reduction by dividing the number of teeth of the big cog by the number of teeth of the little cog (verify they have the same teeth standards)

Therefore a transmission 80 teeth/10 teeth (80T/10T) will produce a reduction of 1:8, like here on amazon for an electric bike ("Keenso Kit Chaîne et Pignon 80T 25H 34mm 3 Trous Pignon 10T H Trou Chaîne Pignon 146 Maillons Chaîne") for 30€.

We will notice that a standard bike transmission have a maximum transmission reduction of 50 teeth/11 teeth so a reduction of 1:5, which is not enough for efficient reduction witouth multiplying pulleys

It is difficult to find transmissions with reduction of 1:10, but we find some on aliexpress and we will easily adapt a bicycle drive on which we will weld cogs, which will allow us a reduction of 1:100 with only one axis added in addition to the principal axis and the engine axis

So we will use an engine step motor or an endless screw engine (resistance unpowered test at reception) commanded by a raspberry pi (but the plate lifter has wheels, so we will take the precaution to put away the tracker when there's a storm ;))

Carvan tips: to size the width of the metal frame, modern norms dont seem to be really made on scientific basis but on commercial basis. We will therefore measure the width of old material (made in the 70s). For example, the frame of this old caravan accredited on its registration document for a 750kg weigth has a metal frame of 5mm width. On can then make a rule of 3 to verify the width of our frame fits.

This is rustic style (strenght of material calculs is rigorous and complex), but it allows to have a firest approximation "a visto de nas" as we say in gascon

Étape 3 - Sizing: measurement of angles and displacement

We will first look at the solar trajectory

We measure typically the sun position along two coordinate systems:

the equatorial system with coordinates expressed in:

right ascension equivalent to terrestrial longitude measured in hours minutes seconds

declination equivalent to terrestrial latitude measured in degrees minutes seconds

The horizontal system with coordinates expressed in:

azimut degree

altitude degree or height

The solar trajectory abacus (for example available here https://www.astrolabe-science.fr/diagramme-solaire-azimut-hauteur ) give us the sun trajectories in a day (generally several typical days of several seasons) expressed in horizontal degrees.

To read this type of grap

If we follow the graph inserted in this tutorial and from the link above, when we follow for example the red curb for paris, we can read :

The 21st of december, when we look at the south, the sun follows a trajectory that starts at -50° azimut (along the East on the horizontal axis) at dawn, then when the sun goes west during the day (we follow the red curb), it takes height until it reaches 17° height (on the vertical axis) at noon (position 0° azimut on the horizontal axis) and then goes down again to 0° height when it sets (along the West on the horizontal axis)

For Paris, we have:

One degree azimut varying from -130° to +130° according to the hour and the season

One degree altitude or height varying from 0° to 64° according to the hour and the season

For our tracker,

To calculate our horizontal displacement (along the Oz vertical axis if the module is put down on the ground), on don't really have constraints on the plate lifter used since the axis turns 360° without problems.So we can follow the sun from -130° azimut to +130° azimut without problem.

To calculate our vertical displacement (along the Ox horizontal axis if the module is put on the ground), we have a constraint on the maximal angle.

I dont have a decimeter at hand, so I will use pythagore (see photo):

64cm*150cm*134cm

socatoa:

sinus phi=opposé/hypothenus

sinus phi=134/150

sinus phi=134/150

phi=1,1046 rad

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

We have a constrainte for our plate lifter accepting angles along the Ox axis from 0° to 63°

When the tracker is at its maximum angle (63°), we are perpendicular to the sun when the sun is at an angle phi of phi=180-63-90=27°

When the sun has an angle lower than 27°, the tracker can not follow and keep being perpendicular to the sun

We see however that the stop is guaranteed by the spring (on the photo we sse the mark on the paint). We can win a bit of amplitude on the stop by drilling and making a notch in the jib).

The manual measurement of the displacement between the tube axis on which is fixed the crank handle and the back of the module when the plate lifter leans at its maximum angle gives us 42cm. (see photo)

Étape 4 - Install the hydraulic cylinder on the horizontal axis

We will fix a jib on the fix part of the plate lifter that turns with the vertical axis, so we can fix a rod on which we will fix the hydraulic cylinder which will be able to turn with the vertical axis so it can adjust the angle on the horizontal axis.

We begin by fixing the metal jib welding to the fix part regarding the vertical axis. It requires to sand the paint before the welding is done (see photo). We do arc welding here.

Then we drill a metal rod we will screw to the jib (see photo).

We drill et we fix a metal rod we screw to the mobile part that allows to adjust the angle on the horizontal axis, ie the arm that permits to carry the plate or the photovoltaic module (see photo).

We then fix the hydraulic cylinder to the two metal rods. The hydraulic cylinder is equiped with fixing that adjust to the angle taken by the rods on which it is fixed (see photo)

Notice we have a metal rod with a right angle that avoid this fix is totally free. It will stop on a part of the rod, which permits then to calibrate more precisely the hydraulic cylinder amplitude).

We then test the course of the hydraulic cylinder. We will pay attention, because the stop of the plate lifter corresponds to a 40cm course for the hyrdaulic cylinder and it can extend up to 50cm. (see photos)

Étape 5 - Raise the plate lifter maximum angle

After observing the critical elements, we will modify the jib to avoid a stop when the sun has a altitude degree lower than 27°.

To do so, we will:

-let the spring go by scooping the jib (to avoid the spring be a stop)

-raise the angle by lowering the fix of the spring drilling the jib

-raise the angle cutting the edges of the jib

See photos:

1/2: observation top/below

3/4: jib disassembly

4/5: obersvation below, maximum angle at square

We reach an maximum angle of almost 90° and we can completely follow the sun!

Étape 6 - Test the hydraulic cylinder and rotation engine commands

To control the hydraulic cylinder, we will use a rapsberry pi, the most widespread monocard computer. It is equiped with a serie of 40 pins, we can connect devices to, called GPIO controller.

A first reading of a few tutorials and available libraries to use it a some time taken to test wrong hypothesis to install correctly using the good versions leads me to talk a few possible options:

-operating system:

Archives and operating systems versions usefull for retrocompatibility (every reader caring for low tech computer is encouraged to keep local copies of these archives and to share them in torrent!):

*dietpi:

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

*raspberry pi os:

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

si vous utilisez wheezy,

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

NB: ChatGPT gives us the following rapsberry release dates:

  1. Raspberry Pi Model B : 29 february 2012
  2. Raspberry Pi Model A : 4 february 2013
  3. Raspberry Pi Model B+ : 14 july 2014
  4. Raspberry Pi Model A+ : 10 november 2014
  5. Raspberry Pi 2 Model B : 2 february 2015
  6. Raspberry Pi Zero : 30 november 2015
  7. Raspberry Pi 3 Model B : 29 february 2016
  8. Raspberry Pi Zero W : 28 february 2017
  9. Raspberry Pi 3 Model B+ : 14 march 2018
  10. Raspberry Pi 3 Model A+ : 15 november 2018
  11. Raspberry Pi 4 Model B : 24 june 2019
  12. Raspberry Pi 400 : 2 november 2020
  13. Raspberry Pi Pico : 21 january 2021
  14. Raspberry Pi Zero 2 W : 28 october 2021

We hope this is true (it comes from chatgpt), but you can verify on what's written on the pcb of your card.

To verify the version of your raspberry under raspberry pi os if you dont know what version it is (see photo):

sudo usermod -a G gpio pi
pinout

Adjust with the operating system that you think is relevant based on your relevancy filters. You have a list of old os versions of dietpi and raspberry pi os in case new versions would now ork anymore (and i repeat: every reader caring of low tech computer is encouraged to download and keep local copies of these archives and share them in torrent!)

To install, as usually, download balenaetcher, flash a usb key with the donwloaded image, boot. The default login/password on dietpi are root/dietpi and for raspberry pi os pi/raspberry (take care to the default qwerty on raspberry pi os at boot). To configure keyboard, locales, timezone and wifi, do:

sudo dietpi-config
under dietpi and
sudo raspi-config
under raspberry pi.

-driver used to control the gpio

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

Failure with pip and pypi repository (compilation errors etc.). Install as root with command

sudo -s 

The simpler is to install a precompiled version with apt:

sudo apt install python3-rpi.gpio

do a test to see if that works well (as root):

python3
import RPi.GPIO

See the sourceforge documentation, more explicit than the pypi one: http://sourceforge.net/p/raspberry-gpio-python/wiki/Home/


  • gpiozero

under dietpi do

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

The first point to understand is the numbering of the pins: The pins have all a number that goes from 1 to 40 following an order from bottom to top and from left to right, and each pin has also a gpiio number which is different from the pin number.

To do so, we can find information on internet: https://wiki.lowtechlab.org/wiki/Serveur_orangepi-raspberry_nextcloud_en_photovolta%C3%AFque_autonome or type the command under raspberry pi os (see image)

sudo usermod -aG gpio votre_user
pinout

In this tutorial, wether for gpiozero or RPi.GPIO, we will use the GPIO numbering and not the pin numbering

We plug the GPIO 2 and 4 (+5V) to the +5V pins of the HBridge We plug the GPIO 6 and 7 (Ground) to the GND pin of the HBridge We plug the GPIO 23 and 16 to the switch 3 of the HBridge To test the "enable" of the HBridge, we plug the GPIO 20 and 21 to EAN of HBridge

We then use the following scripts:

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")
        #case where Hbridge needs a enable signal
        led21.off()
        led20.on()
        #zero signal off on switch 2
        led1.off()
        led7.off()
        #positive signal off on switch 1
        led23.on()
        led16.off()
        #let signal active during wait time
        for k in range(wait):
            print(k)
            time.sleep(1)
        #put signal off on the switch
        led23.off()
        led20.off()
    except KeyboardnInterrupt:
        print("keyboard interrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        #zero signal off on switch 2
        led1.off()
        led7.off()
        #signal off on switch 1
        led16.off()
        led23.off()
        #signal off on enable pin
        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 off on switch 1
        led16.on()
        led23.off()
        #case where Hbridge needs a enable signal
        led20.off()
        led21.on()
        #signal on switch 2
        led1.off()
        led7.off()
        #wait
        for k in range(wait):
            print(k)
            time.sleep(1)
        #signal off on switches
        led16.off()
        led21.off()
    except KeyboardInterrupt:
        print("keyboardinterrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        #signal off on switch 1
        led16.off()
        led23.off()
        #signal off on switch 2
        led1.off()
        led7.off()
        #signal off on enable pin
        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() on my sbc it doesnt remove the +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() on my sbc it doesnt remove the +3V
forward(2) #rotation horary motor2
backward(2) #rotation antihorary motor2
forwardzero(10) #hydraulic cylinder extension motor1 111 max
backwardzero(10) #hydraulic cylinder retractation motor1 107 max

GPIO.BCM allows to use GPIO numbering GPIO.HIGH sends a +3V signal in the concerned pin GPIO.LOW sets the signal to 0V (GND) gpiozero does the same with .on() and .off() methods

Update 31.05.24: you were waiting for it, here's today update with the HBridge made in Europe. It works, see video! Update of calibration quickly at recepetion of something to measure angles a bit more handy.

Notice the pins 20 and 21 are not plugged to the HBridge. The "PWM" (pulse width modulation) is not used to "activate" (enable in the code) the engine because jumbers on ENA pins of the Hbridge are enough (here we dont need to adjust motor speed)


Étape 7 - Hydraulic cylinder for vertical axis rotation

Fix pulleys and 1:100 transmission for rotation motor

We begin by recycling two bycicle drives from second hand bikes we cut with a grinder taking care to keep the metal rod of the frame.


we will weld 92T cogs on it.

We then weld the 8T cogs on it.

We weld a 8T cog on a locked cog adapted to the motor axis.

We cut a metal rod and we weld a flat metal plate along the axis of the plate lifter on which we will screw the bike metal rods attached to the bike drives and alos a metal rod on which we will fix the motor.

We assemble and we screw.

Update when receiving the 3rd chain and waiting how to fix the chain tensions.


Update of 11.6.24: the constraints of the axis of the plate lifter make it difficult to fix a big cog to get a good reduction on the last transmission chain. Even we have several reductions with other pulleys, the plate lifter is not rotating (see video).

update of 16.6.24: additional hydraulic cylinder ordered. Estimated delivery 28 juin-2juillet


We will make the rotation with two hydraulic cylinders. Update when received at the end of june (not available these next weeks anyway).

The chain tension is bad, we replace the tranmissions with a hydraulic cylinder allowing a rotation but with a reduced angle (about 70° because it is not a 2 or 3 parts telescopic cylinder)

Étape 8 - Engine calibration

The updated code is as follow: we define dictionnaries associating each sought angle to an activation time of the engine (each must be manually tested and measured)

#Calibration
#dict_angle_rotation= sun_degre_azimut: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={
                0:41,
                1:42,
                2:43,
                3:44,
                4:45,
                5:46,
                6:47,
                7:48,
                8:49,
                9:50,
                10:52,
                11:53,
                12:54,
                13:55,
                14:56,
                15:57,
                16:58,
                17:59,
                18:60,
                19:61,
                20:63,
                21:64,
                22:65,
                23:66,
                24:67,
                25:68,
                26:69,
                27:70,
                28:71,
                29:72,
                30:73,
                31:75,
                32:76,
                33:77,
                34:78,
                35:79,
                36:82,
                37:82,
                38:82,
                39:82,
                40:82,
                41:82,
                42:82,
                43:82,
                44:82,
                45:82,
                46:82,
                47:82,
                -1:39,
                -2:37,
                -3:35,
                -4:33,
                -5:32,
                -6:30,
                -7:29,
                -8:27,
                -9:26,
                -10:25,
                -11:23,
                -12:22,
                -13:19,
                -14:18,
                -15:17,
                -16:16,
                -17:15,
                -18:14,
                -19:6,
                -20:2,
                -21:1,
                -22:0,
                }




Étape 9 - Hard coding the tracking

The updated code is as follows:

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")
        #case where Hbridge needs a enable signal
        led21.off()
        led20.on()
        #zero signal off on switch 2
        led1.off()
        led7.off()
        #positive signal off on switch 1
        led23.on()
        led16.off()
        #let signal active during wait time
        for k in range(wait):
            print(k)
            time.sleep(1)
        #put signal off on the switch
        led23.off()
        led20.off()
    except KeyboardnInterrupt:
        print("keyboard interrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        #zero signal off on switch 2
        led1.off()
        led7.off()
        #signal off on switch 1
        led16.off()
        led23.off()
        #signal off on enable pin
        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 off on switch 1
        led16.on()
        led23.off()
        #case where Hbridge needs a enable signal
        led20.off()
        led21.on()
        #signal on switch 2
        led1.off()
        led7.off()
        #wait
        for k in range(wait):
            print(k)
            time.sleep(1)
        #signal off on switches
        led16.off()
        led21.off()
    except KeyboardInterrupt:
        print("keyboardinterrupt")
    except Exception as err:
        print(err)
    finally:
        print("zero")
        #signal off on switch 1
        led16.off()
        led23.off()
        #signal off on switch 2
        led1.off()
        led7.off()
        #signal off on enable pin
        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() on my sbc it doesnt remove the +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() on my sbc it doesnt remove the +3V

#Etalonnage
#dict_angle_verin= sun_degre_horizontal:motoractivationtime

#dict_angle_rotation= sun_degre_azimut: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={
                0:41,
                1:42,
                2:43,
                3:44,
                4:45,
                5:46,
                6:47,
                7:48,
                8:49,
                9:50,
                10:52,
                11:53,
                12:54,
                13:55,
                14:56,
                15:57,
                16:58,
                17:59,
                18:60,
                19:61,
                20:63,
                21:64,
                22:65,
                23:66,
                24:67,
                25:68,
                26:69,
                27:70,
                28:71,
                29:72,
                30:73,
                31:75,
                32:76,
                33:77,
                34:78,
                35:79,
                36:82,
                37:82,
                38:82,
                39:82,
                40:82,
                41:82,
                42:82,
                43:82,
                44:82,
                45:82,
                46:82,
                47:82,
                -1:39,
                -2:37,
                -3:35,
                -4:33,
                -5:32,
                -6:30,
                -7:29,
                -8:27,
                -9:26,
                -10:25,
                -11:23,
                -12:22,
                -13:19,
                -14:18,
                -15:17,
                -16:16,
                -17:15,
                -18:14,
                -19:6,
                -20:2,
                -21:1,
                -22:0,
                }
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
    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)
    sun_degre_azimut=min(sun_degre_azimut,87)
    sun_degre_horizontal=max(sun_degre_horizontal,-22)
    sun_degre_horizontal=min(36,sun_degre_horizontal)
    backwardzero(dict_angle_verin[sun_degre_horizontal])
    tracker_degré_horizontal=sun_degre_horizontal
    backward(82)
    forward(dict_angle_rotation[sun_degre_azimut-tracker_degré_azimut])
    tracker_degré_azimut=sun_degre_azimut
#test Agen
lat=44.2
lon=0.6
backward(4)
forward(4) #placer le tracker direction sud angle horizontal
backwardzero(4)
forwardzero(41)
#time.sleep(100)
while True:
    track(datetime.datetime.now(),lat,lon)
    time.sleep(20*60) #activer le tracking toutes les 20 minutes

Étape 10 - AI Coding the tracking

We will now code a lowtech AI for the educational part (Machine Learning here, as massively used since about fifteen years in many industries, ie no AI in the chatgpt sense as it is usually understood in 2024). We could say lowtech AI is AI with results not being magic, where data and code are open source, not stolen, where data belong to us, by us and for us, and on which we are in control of play (this last point is essential but the experience to step out of the line drives me to pessimism on this matter because testing if there is interference in an machine learning result is difficult to detect)

We plug a webcam, we record the images as input data, we process the image to make a digit table corresponding to variables with which on wee seek to correlate with a positive or negative signal (turn the engine in one direction, or stop)

Here, the input data are only the 640*480*3=921600 variables of pixels of the images in the video (921600 colunns/variables per line, from which we seek to correlate with a positive signal 1 or 0 of the last column).

To have the tracker work, this method will not fits well, it would require to do "feature engineering" (complicated name to say add columns of variables more probably correlated to the positive signal) by adding luminosity and/or date and time, on video samples covering all seasons on many years.

If you want to learn the basics of AI on which this code relies, I recommand the course "Applied Data Science with Python" of michigan university in which you will learn python, pandas and machine learning basics "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):
  """
  Load video images from a file

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

   Returns:
      a numpy table with video images (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)

# dataset creation to say "yes" to turn left (for example)
# NB: it's at this stage that tech giants employ kenyans at very low wages
# in a form of modern slavery
# it's about defining, for each image, if we must activate the motor 
# on the loeft (ie defining a positive signal for the machine to correlate positively with this image)

positives=np.zeros_like(images_video)
#If first 14 images define a positive signal, we will do:
#in reality it would require to process video intervals positively defining 
#for each image those where we associate a positive signal 
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: Here, now you know how to code using AI, you can critize it better et promote low tech adequately

You will notice AI algorithms are open source and easy to use as simple "dev user".

And also that without data, AI is absolutely useless.

That's the reason why big tech giants want more and more data and employ peolple in slavery conditions in many countries to process these data before training their models.

Just like for "personal data", the key question here with AI relies on data.

See the excellent conference of Benjamin Bayart "Geopolitique de la data (Benjamin BAYART)" on youtube or in video in this tutorial.

You can make a lowtech tracker, but also adapt this code to create an autonomous vehicle lowtech with 4 distinct datasets/positive signals to train activation of "turn left", "accelerate", "brake", "turn right". This is what George Hotz did claiming it required only 30 hours of videos of driving and recorded sensors to have the needed positives signals correlated with the recorded images to have autonomous driving work.

Of course, we strongly hope there won't be hack or that the system doesnt have a distant command and control on this type of algorithm.




Commentaires

Published