Salut à toutes et tous,
je suis débutant sur Arduino. Mon sujet est un laser pour chat avec mouvements aléatoires avec 2 servo et un PIR HC-SR501 afin qu'il détecte le chat lorsque nous ne sommes pas présents. J'ai récupéré le code sur un site de DIY et je souhaiterais l'améliorer afin qu'il corresponde à mes besoins. Le voici actuellement:
void setup() {
y_servo.attach(9); // attaches the y servo on pin 9 to the servo object
x_servo.attach(6); // attaches the x servo on pin 6 to the servo object
pinMode (4, INPUT); // pin 4 is an input (PIR)
pinMode (13, OUTPUT); // pin 13 is an output (laser)
//Place the servos in the center at the beginning
y_servo.write(y_position);
x_servo.write(x_position);
delay(60000);
}
void loop()
{ if (digitalRead(4) == HIGH) // if state of PIR is HIGH
{
digitalWrite (13, HIGH); // switch on the laser
movement_time = random(10, 125);
random_delay = random(min_freeze, max_freeze);
x_new_position = random(min_x + minimal_movement, max_x - minimal_movement);
y_new_position = random(min_y + minimal_movement, max_y - minimal_movement);
if ( (y_new_position > y_old_position) && (abs(y_new_position - y_old_position) < 5 )) {
y_new_position = y_new_position + minimal_movement;
} else if ( (y_new_position < y_old_position) && (abs(y_new_position - y_old_position) < 5 )) {
y_new_position = y_new_position - minimal_movement;
}
if ( (x_new_position > x_old_position) && (abs(x_new_position - x_old_position) < 5 )) {
x_new_position = x_new_position + minimal_movement;
} else if ( (x_new_position < x_old_position) && (abs(x_new_position - x_old_position) < 5 )) {
x_new_position = x_new_position - minimal_movement;
}
x_speed = (x_new_position - x_old_position) / movement_time;
y_speed = (y_new_position - y_old_position) / movement_time;
for (pos = 0; pos < movement_time; pos += 1) {
x_position = x_position + x_speed;
y_position = y_position + y_speed;
x_servo.write(x_position);
y_servo.write(y_position);
delay(10);
}
x_old_position = x_new_position;
y_old_position = y_new_position;
delay(random_delay);
}
else digitalWrite (13, LOW); // switch off the laser
}
Cela fonctionne bien mais tant que le chat est détecté le laser continu.
Je souhaiterais:
que le chat soit détecté, que le laser se mette en mouvement et qu'il s'arrête au bout de 10min par exemple ou un compteur basé sur le nombre déclenchement du PIR.
une fois ces 10min passées (ou le compteur basé sur le PIR), si le chat repasse devant le PIR, le système ne se remette pas en fonction avant 1h (si il repasse devant) pour un nouveau cycle de 10min (ou de compteur de PIR), etc etc.
Ceci afin qu'il ne s'abrutisse pas dessus et qu'il s'en lasse.
J'ai essayé plusieurs petites choses avec des milli(), des flag, des while, j'ai essayé aussi avec un void() qui appelle d'autres boucles mais je n'arrive pas à mes fins.
Voyez vous un moyen de réaliser cela ?
c'est typiquement une définition de programme qui se prête bien à la programmation par machine à états (cf mon tuto éventuellement)
il faut rajouter des états à votre "machine" actuelle qui permettront de mémoriser que vous avez joué et qu'il faut attendre 1h, ainsi qu'un timeout au bout de 10 min
Merci manumanu
Merci J-M-L, j'essaie de mettre en application par rapport à ton tuto très bien détaillé et qui reste cependant assez complexe pour moi, mais je vais y arriver.
J'ai compris la notion de machine à états mais je ne comprend pas si je dois forcément utiliser la librairie OneButton dans mon cas en remplaçant le bouton par le PIR. :
ok, je n'arrive sincèrement pas à poser les bases concernant les différents états de ce projet et en plus gérer le timer, je me mélange complètement les pinceaux. Selon vous, combien d'états machine différents à mettre dans enum comporte ce projet ?
J'aurais dis 3:
reposInitial (état du système en attente de sa première détection après mise sous tension)
jeu (état d'allumage du laser + mouvements)
repos 1h (état du système led éteinte et pas de mouvements avec tempo d'une heure)
Quelles bases voyez vous pour ce projet afin que je puisse travailler le code dans la bonne direction ?
Quant tu paramètre "random()" le mini et maxi sont définis par tes valeurs.
radom(100,300); //100 mini valeur - 300 maxi valeur. = Affichera une valeur aléatoire entre ces deux données.
Je n'avais pas tout posté le code, c'est pour cela que tout n'y est pas.
La partie concernant les mouvements aléatoires fonctionne parfaitement bien (je rappelle que je l'ai récupéré d'un projet posté sur une plateforme DIY)
Je ne peux pas te dire pourquoi c'est programmé comme cela au niveau des random(), de plus, je ne m'y connais pas encore assez.
Le delay ligne 10 je l'ai rajouté pour laisser le temps au PIR de s'initialiser. (dans le projet que j'ai récupéré il n'y avait pas de PIR)
Voici tout le code actuel qui fonctionne très bien mais où il manque les fonctions que j'aimerais ajouter (décrites dans mon premier message):
/*
Laser Tower for the CAT - LA FABRIQUE DIY
Pseudo-randomly moves a servo tower (on X and Y axis) and lights up a laser.
x_servo is attached to pin 6 and moves in the X plan
y_servo is attached to pin 9 and moves in the Y plan
Laser is on pin 13
HOW IT WORKS :
The program randomly choose a new position for the laser inside a square you can define below.
It checks the new position is different from the old one of at least "minimal_movement".
It moves the tower to the new position and stays still for a time between min_freeze and max_freeze
(this aims to reproduce the behaviour of an insect landing somewhere for a bit and then flying off,
that's the variable you need to increase if your cat is fat).
Ans starts the process over and over again.
*/
#include <Servo.h> //include servo library
/* YOU CAN CUSTOM THESE VARIABLES IF YOU WANT TO ALTER THE TOWER BEHAVIOUR */
// X servo angle will stay in [min_x, max_x] range
// Y servo angle will stay in [min_y, max_y] range
// to be ajsuted to the size of your living room
float min_x = 5;
float max_x = 75; //50
float min_y = 5;
float max_y = 75; //35
int min_freeze = 50;
int max_freeze = 2500;
float minimal_movement = 5;
/* YOU SHOULD NOT HAVE TO MODIFY THE CODE BELOW THIS LINE */
// finding center of square for starting point
int random_delay;
float x_position = min_x + (max_x - min_x) / 2;
float y_position = min_y + (max_y - min_y) / 2;
float x_old_position = x_position;
float y_old_position = y_position;
float x_new_position;
float y_new_position;
float x_speed;
float y_speed;
int movement_time;
// Instantiating two servos
Servo x_servo; // déclaration d'une variable de type Servo
Servo y_servo; // déclaration d'une variable de type Servo
int pos = 0;
void setup() {
y_servo.attach(9); // attaches the y servo on pin 9 to the servo object
x_servo.attach(6); // attaches the x servo on pin 6 to the servo object
pinMode (4, INPUT); // pin 4 is an input
pinMode (13, OUTPUT); // pin 13 is an output
//Place the servos in the center at the beginning
y_servo.write(y_position);
x_servo.write(x_position);
delay(60000);
}
void loop()
{ if (digitalRead(4) == HIGH) // if state of PIR is HIGH
{
digitalWrite (13, HIGH); // switch on the laser
movement_time = random(10, 125);
random_delay = random(min_freeze, max_freeze);
x_new_position = random(min_x + minimal_movement, max_x - minimal_movement);
y_new_position = random(min_y + minimal_movement, max_y - minimal_movement);
if ( (y_new_position > y_old_position) && (abs(y_new_position - y_old_position) < 5 )) {
y_new_position = y_new_position + minimal_movement;
} else if ( (y_new_position < y_old_position) && (abs(y_new_position - y_old_position) < 5 )) {
y_new_position = y_new_position - minimal_movement;
}
if ( (x_new_position > x_old_position) && (abs(x_new_position - x_old_position) < 5 )) {
x_new_position = x_new_position + minimal_movement;
} else if ( (x_new_position < x_old_position) && (abs(x_new_position - x_old_position) < 5 )) {
x_new_position = x_new_position - minimal_movement;
}
x_speed = (x_new_position - x_old_position) / movement_time;
y_speed = (y_new_position - y_old_position) / movement_time;
for (pos = 0; pos < movement_time; pos += 1) {
x_position = x_position + x_speed;
y_position = y_position + y_speed;
x_servo.write(x_position);
y_servo.write(y_position);
delay(10);
}
x_old_position = x_new_position;
y_old_position = y_new_position;
delay(random_delay);
}
else digitalWrite (13, LOW); // switch off the laser
}
Psylicib:
ok, je n'arrive sincèrement pas à poser les bases concernant les différents états de ce projet et en plus gérer le timer, je me mélange complètement les pinceaux. Selon vous, combien d'états machine différents à mettre dans enum comporte ce projet ?
J'aurais dis 3:
reposInitial (état du système en attente de sa première détection après mise sous tension)
jeu (état d'allumage du laser + mouvements)
repos 1h (état du système led éteinte et pas de mouvements avec tempo d'une heure)
Quelles bases voyez vous pour ce projet afin que je puisse travailler le code dans la bonne direction ?
Oui c'est un bon départ. Je pense qu'il y a un état en plus si vous voulez permettre au chat de jouer 2 fois 5 minutes par exemple dans un temps court.
voilà à quoi pourrait ressembler la structure du code, j'ai tapé ça ici directement donc je ne sais pas si ça compile mais ça devrait vous donner des idées
const uint32_t periodeJeu = 10 * 60 * 1000ul; // 10 minutes en ms
uint32_t chronoJeu;
const uint32_t periodeRepos = 60 * 60 * 1000ul; // 60 minutes en ms
uint32_t chronoRepos;
const uint32_t periodeLaser = 250ul; // le laser se déplacera 4 fois par seconde par exemple
uint32_t chronoLaser;
uint32_t tempsDeJeu;
enum : byte {ATTENTE, JEU, JEU_EN_PAUSE, REPOS} etat;
// =================================================
// gestion de l'évenement externe
// =================================================
boolean chatPresent() // retourne true si le chat a été detecté
{
boolean detection = false;
// =================================================
// ===>>> ici programmer la détection du chat <<<===
// =================================================
return detection;
}
// =================================================
// LA CONFIGURATION INITIALE
// =================================================
void setup()
{
Serial.begin(115200);
// ==================================================
// ===>>> ici initialiser les pins, le PIR etc <<<===
// ==================================================
etat = ATTENTE;
// on est prêt
Serial.println(F("Lachez le fauve"));
}
// =================================================
// LA MACHINE A ETATS
// =================================================
void loop()
{
// Notre machine à états
switch (etat) {
case ATTENTE: // On est dans un mode ou on attend le chat
if (chatPresent()) { // le chat est detecté
chronoLaser = chronoJeu = millis(); // on note l'heure de départ du jeu
tempsDeJeu = 0; // et on va cumuler les périodes de jeu, pour l'instant c'est juste le début
etat = JEU; // on passe en mode JEU
}
break;
case JEU: // on est en train de jouer
if (millis() - chronoJeu >= periodeJeu) { // le chat a-t-il trop joué ?
chronoRepos = millis(); // si oui, on note l'heure d'arrêt
etat = REPOS; // et on arrête
} else { // sinon le chat a le droit de continuer à jouer
if (chatPresent()) { // on regarde s'il est là
if (millis() - chronoLaser >= periodeLaser) { // si le chat est bien là, on regarde si c'est le moment de déplacer le laser
// c'est le moment de faire bouger le laser quelque part
// algo pour faire bouger le laser UNE SEULE FOIS (ne pas bloquer la machine à états)
// ==================================================
// ===>>> ici programmer le Mouvement du Laser <<<===
// ==================================================
chronoLaser += periodeLaser; // et on mémorise le moment
} else {
// le chat est parti mais on n'a pas écoulé les 10 minutes autorisées
chronoRepos = millis(); // on note l'heure d'arrêt
tempsDeJeu = chronoRepos - chronoJeu; // le temps qu'il a passé à jouer sera décompté à la prochaine partie
etat = JEU_EN_PAUSE // on se met en attente du retour du chat
}
}
}
break;
case JEU_EN_PAUSE:
if (chatPresent()) { // si le chat est revenu, on reprend le jeu
chronoLaser = millis();
if (millis() - chronoRepos >= periodeRepos) {
chronoJeu = chronoLaser; // le chat était parti trop longtemps, on ne tient pas compte de son temps de jeu précédent
} else {
chronoJeu = chronoLaser - tempsDeJeu; // le chat n'a pas attendu assez, on recule la date de démarrage du jeu pour prendre en compte le temps déjà joué
}
etat = JEU;
}
break;
case REPOS:
if (millis() - chronoRepos >= periodeRepos) { // a-t-on a assez attendu
etat = ATTENTE; // si oui, le chat peut revenir jouer quand il veut
}
break;
}
}
Ouahou !! Merci énormément !! C'est même plus les bases là, on est presque déjà en trin de mettre le crépi ;D
Je comprend presque tout. Cependant, quel est le but de:
const uint32_t periodeLaser = 250ul; // le laser se déplacera 4 fois par seconde par exemple
et
if (millis() - chronoLaser >= periodeLaser) { // si le chat est bien là, on regarde si c'est le moment de déplacer le laser
// c'est le moment de faire bouger le laser quelque part*
// algo pour faire bouger le laser UNE SEULE FOIS (ne pas bloquer la machine à états)*
Psylicib:
Cependant, quel est le but de periodeLaser
...
Tout ce qui concerne la variable periodeLaser m'échappe.
Dans votre machine à état vous devez constamment scruter les évènements donc vous ne pouvez pas rester coincé trop longtemps à un endroit. il s'agit donc pour vous d'extraire de l'autre code ce qui calcule 1 changement de position du laser et de l'insérer à cet endroit. la periodeLaser dit simplement combien de fois par seconde on bouge le laser. ici je l'ai mis constante, mais vous pouvez virer "const" et utiliser random pour définir quand on fera 1 mouvement
--> il vous faut donc décortiquer un peu l'autre programme (ou mieux en faire un par vous même qui fait bouger le laser de temps en temps)
Donc voici le code que j'ai adapté pour l'instant en enlevant la partie "jeu en pause" car le chat n'est pas constamment en trin de bouger et d'être détecté par le PIR, j'ai également réduit les tempos de jeu et de repos pour les essais:
/*
*/
#include <Servo.h> //include servo library
const uint32_t periodeJeu = 1 * 30 * 1000ul; // 30 sec en ms
uint32_t chronoJeu;
const uint32_t periodeRepos = 1 * 15 * 1000ul; // 15 sec en ms
uint32_t chronoRepos;
enum : byte {ATTENTE, JEU, REPOS} etat;
// =================================================
// gestion de l'évenement externe
// =================================================
boolean chatPresent() // retourne true si le chat a été detecté
{
boolean detection = false;
if (digitalRead(4) == HIGH) { // if state of PIR is HIGH
detection = true;
}
return detection;
}
// =================================================
// LA CONFIGURATION INITIALE
// =================================================
/* YOU CAN CUSTOM THESE VARIABLES IF YOU WANT TO ALTER THE TOWER BEHAVIOUR */
// X servo angle will stay in [min_x, max_x] range
// Y servo angle will stay in [min_y, max_y] range
// to be ajsuted to the size of your living room
float min_x = 5;
float max_x = 75; //50
float min_y = 5;
float max_y = 75; //35
int min_freeze = 50;
int max_freeze = 2500;
float minimal_movement = 5;
// finding center of square for starting point
int random_delay;
float x_position = min_x + (max_x - min_x) / 2;
float y_position = min_y + (max_y - min_y) / 2;
float x_old_position = x_position;
float y_old_position = y_position;
float x_new_position;
float y_new_position;
float x_speed;
float y_speed;
int movement_time;
// Instantiating two servos
Servo x_servo; // déclaration d'une variable de type Servo
Servo y_servo; // déclaration d'une variable de type Servo
int pos = 0;
void setup()
{
Serial.begin(115200);
y_servo.attach(9); // attaches the y servo on pin 9 to the servo object
x_servo.attach(6); // attaches the x servo on pin 6 to the servo object
pinMode (4, INPUT); // pin 4 is an input
pinMode (13, OUTPUT); // pin 13 is an output
//Place the servos in the center at the beginning
y_servo.write(y_position);
x_servo.write(x_position);
etat = ATTENTE;
// on est prêt
Serial.println(F("Lachez le fauve"));
}
// =================================================
// LA MACHINE A ETATS
// =================================================
void loop()
{
// Notre machine à états
switch (etat) {
case ATTENTE: // On est dans un mode ou on attend le chat
if (chatPresent()) { // le chat est detecté
chronoJeu = millis(); // on note l'heure de départ du jeu
etat = JEU; // on passe en mode JEU
}
break;
case JEU: // on est en train de jouer
if (millis() - chronoJeu >= periodeJeu) { // le chat a-t-il trop joué ?
chronoRepos = millis(); // si oui, on note l'heure d'arrêt
etat = REPOS; // et on arrête
} else { // sinon le chat a le droit de continuer à jouer
digitalWrite (13, HIGH); // switch on the laser
movement_time = random(10, 125);
random_delay = random(min_freeze, max_freeze);
x_new_position = random(min_x + minimal_movement, max_x - minimal_movement);
y_new_position = random(min_y + minimal_movement, max_y - minimal_movement);
if ( (y_new_position > y_old_position) && (abs(y_new_position - y_old_position) < 5 )) {
y_new_position = y_new_position + minimal_movement;
} else if ( (y_new_position < y_old_position) && (abs(y_new_position - y_old_position) < 5 )) {
y_new_position = y_new_position - minimal_movement;
}
if ( (x_new_position > x_old_position) && (abs(x_new_position - x_old_position) < 5 )) {
x_new_position = x_new_position + minimal_movement;
} else if ( (x_new_position < x_old_position) && (abs(x_new_position - x_old_position) < 5 )) {
x_new_position = x_new_position - minimal_movement;
}
x_speed = (x_new_position - x_old_position) / movement_time;
y_speed = (y_new_position - y_old_position) / movement_time;
for (pos = 0; pos < movement_time; pos += 1) {
x_position = x_position + x_speed;
y_position = y_position + y_speed;
x_servo.write(x_position);
y_servo.write(y_position);
delay(10);
}
x_old_position = x_new_position;
y_old_position = y_new_position;
delay(random_delay);
}
break;
case REPOS:
digitalWrite (13, LOW); // switch off the laser
if (millis() - chronoRepos >= periodeRepos) { // a-t-on a assez attendu
etat = ATTENTE; // si oui, le chat peut revenir jouer quand il veut
}
break;
}
}
Avec ce code, j'ai vérifié sur le Monitor:
démarrage, le code passe une seule fois au cas 0 (Attente) puis directement au cas 1 (jeu) pendant 30s puis cas 2 (repos) pendant 15s et cela recommence en boucle entre 1 et 2.
Cela paraît correct sauf que le PIR en réel ne détecte rien donc je ne vois pas pourquoi le code passe directement du cas 0 à 1 (à l'allumage) puis qu'il boucle entre 1 et 2.
La partie:
boolean chatPresent() // retourne true si le chat a été detecté
{
boolean detection = false;
if (digitalRead(4) == HIGH) { // if state of PIR is HIGH
detection = true;
}
return detection;
}
m'a l'air correcte alors comment peut-il renvoyer "detection = true" alors que la sortie du PIR est à 0 ?
oui je n'avais pas remis le delay de 60s durant le setup. J'ai également diminué la sensibilité du PIR car apparemment en faisant des essais sur le bureau, il détectait le laser...
Dés fois le PIR détecte alors qu'il est tourné vers le mur sans aucun mouvements, je n'ai pas encore compris pourquoi.
Je vais faire des essais en réel avec le chat !! Je vous remercie du fond du cœur pour cette précieuse aide.
c'est un peu bizarre car bien que le PIR détecte l'infra-rouge (comme son nom l'indique) - votre laser, s'il est rouge, à une Longueur d'onde entre 635nm et 650nm et l'infrarouge "proche" (au sens du spectre visible) va de 700nm à 2 000 nm environ.. mais les PIR sont généralement calibrés pour du 8 à 14 micromètres... donc ça ne devrait pas être sensible au mouvement de votre laser sur la table...
Bon après plusieurs essais bien différents, effectivement cela ne vient pas du laser.
Essai d'un autre programme où je scrute l'entrée du PIR > idem
Essai en passant sur l'alim jack (bien entendu j'enlève l'USB) > idem
Essai en mettant le PIR contre un mur où il ne peut pas détecter quelque chose > idem
J'ai remarqué qu'en modifiant la sensibilité, le PIR déclenchait différemment:
-Sensibilité au plus bas avec tempo de sortie réglé au mini (environ 0.5s) > le PIR déclenche aléatoirement toutes les 3s à plus d'une minute.
-Sensibilité au milieu > le PIR déclenche aléatoirement toutes les 3s à 10s
-Sensibilité réglé au max > le PIR déclenche encore plus fréquemment toutes les 3 à 6s max
Tout ceci avec le PIR calé dans un coin contre un mur où il n'y a aucun mouvement !! :o
Je crois que le PIR dois avoir un problème...qu'en pensez vous ?
Si le PIR est trop proche d’un mur et que vous n’êtes pas loin votre chaleur sera reflétée par le mur et visible par le PIR (ce n’est pa s une caméra, ça fonctionne par différentiel d’onde « de chaleur » reçue au travers d’un prisme de fresnel)
Vaut mieux le mettre vers un espace ouvert avec vous vraiment derrière, éventuellement avec un carton derrière le PIR pour éviter des ondes réfléchies
Je doute qu'il me voit car je ne bouge pas.
Je viens de faire un essai en le faisant pointer dans une pièce, espace dégagé devant lui.
Aucun mouvement et moi qui ne bouge pas d'un poil et de plus caché derrière un mur > le PIR déclenche tout seul et ceci à différents seuils de sensibilité.
Le chauffage au sol électrique ?
Non là ça commence à être douteux... si vous avez bien attendu 1 minute avant de commencer les mesures et réglez le PIR en position moyenne sur les 2 potentiomètres et regardez ce que ça dit. Sinon il se peut qu’il soit défectueux en effet