Bouton unique à code secret

Je n'ai encore rien codé pour ce petit projet (ni même câblé quoi que ce soit) mais je butte déjà sur le principe... Ça commence mal !

Base de réflexion : ESP32, bouton poussoir unique et son anti-rebond, relais (plus transistor etc évidement). Ça je sais faire...

J'aimerai commander un relais (bref une sortie) avec un bouton poussoir mais qui ne réagisse qu'à une séquence prédéfinie d'appuis façon code morse.

Exemple : le code secret est long - court - court - long. Court est un appui de moins de 0,4 s, long un appui entre 0,8 et 1,5 s Exemples de valeurs arbitraires pour le moment, à affiner à l'usage si besoin.
Si j’appuie long - court - court - long le relais se ferme. Si je fais autre chose, ou des durées anormales, rien ne se passe.

Comment verriez-vous la logique du code ? machine à états ? autres ? J'ai du mal à voir par où prendre le problème.

Merci d'avance pour vos idées.

Edit : pour une version 2.0 (soyons fous) un mode « programmation » qui permet d'enregistrer un nouveau code pourrait être une bonne option, mais je n'en suis pas là, mais peut-être que ça peut influer sur le design du code

Bonsoir @ProfesseurMephisto
Un point de départ ici pour votre projet :

Bonne soirée

Bonsoir ProfesseurMephisto

Un truc, est de prendre une bibliothèque comme JC_Button, qui gère le long press, ca peut aider.
L'usage de ce genre de bibliothèque est que l'anti rebond est géré.

PS: Si tu n'as rien contre l'IA, tu lui envoies l'intégralité de ton premier message et tu auras des pistes :wink:

Cordialement
jpbbricole

Si la librairie JC_button gère le longpress, je vais déjà essayer de l'utiliser. On verra ensuite pour la logique de décodage - validation

Pour ce qui est de l'IA, je n'ai rien contre mais la "production" sur les enjeux complexes donne généralement du grand n'importe quoi. Je l’utilise à l'occasion mais pour des contextes très précis.

Merci, dans la todo-list pour tests aussi !

j avais fait un prog du meme style mais sans appui long juste avec 2 touches.
si ca peux t aider pour la logique du programme.
https://wokwi.com/projects/390383679180888065

Je ferais une boucle principal qui calcule le temps d'appuis sur le bouton.
c'est à dire mémorise le temps(millis) au changement d'état lâché-appuis et prise de temps au changement d'état appuis-lâché.
Je déclencherais aussi une prise de temps au relâché, qui réinitialiserais le code secret si aucun appuis pendant un certain temps.

Lors du relâché du coup j'ajouterais le résultat de la prise de temps 0 pour court et 1 pour long.

Pas sûre que ce soit très claire, mais cela me parais simple à faire, si tu as un antirebonds matérielle évidement(sans ca complexifie a peine :slight_smile: ).

Je ne sais pas si je suis le seul, mais tes liens ne mène nul pars quand je clique dessus.

Sur un projet aussi simple, il devrait te pondre un code fonctionnel normalement.

Bonjour terwal

Mon lien ne menai qu'à ChatGPT, je ne voulais pas fournir de solution ChatGPT ne sachant pas si le demandeur en était un adepte.

Mais, voici la proposition ChatGPT.

Bonne journée
jpbbricole

Bonjour @ProfesseurMephisto
C'est tout simple car votre programme dépend de la durée d'un appui sur un seul bouton :

//Exemple librairie simpleBouton.h
//Affichage des durées d'appui
#include "simpleBouton.h"

simpleBouton bouton(7);
//Cablage : pin---BP---GND

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  //Lecture de la durée d'appui AVANT d'actualiser
  uint32_t duree = bouton.dureeEnfonce();
 
  bouton.actualiser();
  
  if (bouton.vientDEtreEnfonce())
  {
	Serial.print("Bouton enfonce");
  }

  if (bouton.vientDEtreRelache())
  {
    Serial.print(" pendant ");
	Serial.print(duree);
	Serial.println(" ms");
  } 
}

Bonne journée

On peut même adapter la librairie à votre programme mais je ne suis pas certain que cela plaise :wink:

Bonne journée

Le morse c'est un trait et un point :
Rien de plus simple :

//Exemple librairie simpleBouton.h
//Affichage des durées d'appui
#include "simpleBouton.h"
const uint32_t  point = 100;
const uint32_t  trait = 500;

simpleBouton bouton(7);
//Cablage : pin---BP---GND

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  //Lecture de la durée d'appui AVANT d'actualiser
  uint32_t duree = bouton.dureeEnfonce();

  bouton.actualiser();
  if (bouton.vientDEtreRelache()) {
    if (duree <= point) Serial.println ("point") ;
    else if  (duree >= trait) Serial.println ("trait") ;
    Serial.print(duree);
    Serial.println(" ms");
  }
}

Après une fois défini le code secret et le code C++ nécessaire à sa reconnaissance, il n'y a plus qu'à déclencher votre relais ...

Bonne journée

ok, comme c'est la deuxième fois, je me demandais, si je n'avais pas un problème.

@ProfesseurMephisto
Voici un code vite fait :

//Exemple librairie simpleBouton.h
//Affichage des durées d'appui
#include "simpleBouton.h"
const uint32_t  point = 400;
const uint32_t  trait = 1000;
const uint8_t code [] = {1, 0, 0, 1};

simpleBouton bouton(7);
//Cablage : pin---BP---GND

bool identificationCode(uint8_t saisieBouton[]);

void setup()
{
  Serial.begin(115200);
}
void loop()
{
  //Lecture de la durée d'appui AVANT d'actualiser

  uint32_t duree = bouton.dureeEnfonce();
  static uint8_t y = 0;
  static uint8_t saisieBouton[4];


  bouton.actualiser();


  if (bouton.vientDEtreRelache()) {
    (duree <= point) ? saisieBouton[y] = 1 : saisieBouton[y] = 0 ;
    (duree <= point) ? Serial.println("point") : Serial.println("trait") ;
    y++;
    Serial.println(y);
  }
  if ((y == 4) && identificationCode(saisieBouton)) {
    Serial.println ("le code est bon !");
    for (uint8_t z = 0 ; z < 4; z++) saisieBouton[z] = 3;
    // action sur relais ....
  }
  if (y == 4) y = 0;

}

bool identificationCode(uint8_t saisieBouton[]) {
  for (uint8_t x = 0 ; x < 4 ; x++) {
    if (saisieBouton[x] != code [x]) return false;
  }
  return true;
}

Bonne journée.

PS : A vous de doser le temps d'appui pour déterminer si l'appui est long ou court. Dés que le code est trouvé vous n'avez plus qu'à actionner le relais ...
Avec les temps d'appui que j'ai déterminés tout fonctionne très bien sur 328p ou 1284p. Non testé sur ESP32 mais nul doute que ça fonctionnera bien.
edit : vous pouvez supprimer la réinitialisation du tableau saisieBouton qui est absolument inutile :
for (uint8_t z = 0 ; z < 4; z++) saisieBouton[z] = 3;

comme ça je dirais un tableau où je stocke la séquence
aCode = ['L','C','C','L']

et un entier qui m'indique où j'en suis
iCodeStep = 0

ensuite au changement d'état enfoncé, j'enregistre le temps

au changement d'état relaché, je mesure le temps passé, et détermine si je suis en cAppui = 'C' oucAppui = 'L'

je compare cAppui avec aCode[iCodeStep]
si égalité alors iCodeStep++

sinon iCodeStep=0 + delai de sécurité avant nouvel essai

quand iCodeStep = longueur du code c'est que le code est bon

traiter le cas du code bon et remettre iCodeStep à 0

j'ajouterai aussi une verification du temps entre deux appuis sur le bouton. si trop long => reset de la lecture du code

Bonsoir à tous,

Avec vos aides et suggestions (merci beaucoup :pray:) j'ai un code fonctionnel.

Je le copie-colle ci-dessous, j'ai laissé tous les Serial.println() de debuggage

Je suis bien sûr preneur de toute critique de celui-ci...

#include <JC_Button.h>

// Configuration du bouton
const uint8_t BUTTON_PIN = 10;  // PIN10(pullup) -- poussoir + 100nF // -- GND
Button myButton(BUTTON_PIN);   

// Configuration du relais
const uint8_t RELAIS_PIN = 0;   // PIN0 -- R 10kΩ -- base NPN
bool isWorking = false;         // true quand le relais est fermé
unsigned long msEndWorking = 0; // instant de la fin de fonctt du relais

// constantes de durées
const unsigned long MIN_COURT = 30;
const unsigned long MAX_COURT = 400;
const unsigned long MIN_LONG  = 600;
const unsigned long MAX_LONG  = 1200;

const unsigned long MIN_SILENCE = 2000;   // si tu reviens j'annule pas tout
const unsigned long DUREE_RELAIS = 5000;  // le relais reste collé 5 s

// variables pour les clics
unsigned long msPress = 0;  // instant de l'appui
unsigned long msLache = 0;  // instant du relâchement
bool isTyping = false;      // true quand il y a eu un appui récent (< MIN_SILENCE)

// variables pour le code
static uint8_t codeStep = 0;              // rang du clic
static uint8_t saisie[4] = {0, 0, 0, 0};  // 0 pour un clic de durée incorrecte, 1 court et 2 long
static uint8_t secret[4] = {2, 2, 1, 1};  // prévoir la mémorisation et la modification dans la version 2

bool testCode() {
  for (int i = 0; i < 4; i++) {
    if (saisie[i] != secret[i]) {
      return false; 
    }
  }
  return true;
}

void setup() {
  Serial.begin(115200);
  myButton.begin();

  pinMode(RELAIS_PIN, OUTPUT);   
  digitalWrite(RELAIS_PIN, LOW); 


  delay(1000);
  Serial.println("démarrage...");
}

void loop() {
  myButton.read(); // Lecture de l'état du bouton

  if ( myButton.wasPressed() )
  {
    isTyping = true;
    msPress = millis();
  }

  if ( myButton.wasReleased() )
  {
    msLache = millis();
    
    if ((msLache - msPress < MAX_COURT) && (msLache - msPress > MIN_COURT)) {
      // clic court
      Serial.println("CLIC COURT");
      if (codeStep < 4) {saisie[codeStep] = 1;}
    } else {
      if ((msLache - msPress < MAX_LONG) && (msLache - msPress > MIN_LONG)) {
        // clic long
        Serial.println("CLIC LONG");
        if (codeStep < 4) {saisie[codeStep] = 2;}
      } else {
        // mauvais clic, ni court ni long
        Serial.println("MAUVAIS CLIC");
        if (codeStep < 4) {saisie[codeStep] = 0;}
      } 
    }
    codeStep++;

    // tester le code si codeStep = 4
    if (codeStep == 4) {
      if (testCode()) {
        // code OK
        Serial.println("CODE OK");
        Serial.println("Fermeture relais");
        digitalWrite(RELAIS_PIN, HIGH); 
        isWorking = true;
        msEndWorking = millis() + DUREE_RELAIS;
      } else {
        // code pas OK, sale pirate
        isWorking = false; 
        msEndWorking = 0; 
        Serial.println("TIPIAK !!");
      }
    }
  
  }

  if ((isWorking) && (millis() > msEndWorking)) {
    // fin du relais
    digitalWrite(RELAIS_PIN, LOW); 
    isWorking = false;
    msEndWorking = 0;
    Serial.println("Ouverture relais");
  }

  if (isTyping) {
    if (millis() - max(msLache, msPress) > MIN_SILENCE) {
      // silence trop long, t'es pas revenue, j'annule tout
      isTyping = false;
      memset(saisie, 0, sizeof(saisie));
      msLache = 0;
      msPress = 0;
      codeStep = 0;
      
      Serial.println("dodo............");
    }
  }
}

Bonjour @ProfesseurMephisto,

1/ Je ne connais pas du tout la librairie JC_Button.h
Mais il semble qu'elle ne gère pas l'anti-rebond correctement :
const uint8_t BUTTON_PIN = 10; // PIN10(pullup) -- poussoir + 100nF // -- GND
Vous ne devriez pas avoir de condensateur anti-rebond, de plus personnellement je mets plutôt une valeur proche de 10nF (100nF étant le maximum). C'est la librairie ou le condensateur pour l'anti-rebond. Si la librairie est ben faite le condensateur est totalement inutile. Bon après le fait d'utiliser les deux ne doit pas poser de problème ...

2/ Une librairie qui gère des boutons poussoirs ne devrait pas vous obliger à utiliser la fonction millis(), elle fait le job de manière transparente pour vous.

3/ la librairie de @bricoleau est particulièrement bien faite est extrêmement efficace. C'est celle-ci que j'utilise systématiquement. De plus là où @bricoleau est génial c'est qu'il l'a faite de sorte qu'elle soit simple et facilement compréhensible :wink:

Après si votre code vous convient, c'est votre choix et de surcroît si il fonctionne bien, il n'y a rien à y redire ...

Enfin pourquoi déclarez-vous les variables globales de code static ?

Bonne journée.

PS : je n'ai pas testé votre code.

Merci pour les commentaires !

Je ne connaissais aucune des deux avant de commencer ce projet :wink:

Normalement si, par défaut et on peut ajuster en paramètre la durée de l'anti-rebond logiciel.
J'ai laissé le condensateur plus par habitude que par nécessité, et 100 nF parce que des expériences précédentes avec des boutons d'assez mauvaise qualité faisait que 10 nF laissait passer parfois quelques rebonds. C'est surdimensionné, je vais fouiller les fonds de tiroir pour diminuer.

Peut-être, mais je n'ai pas trouvé comment avec JC_button.h. Reste que par plaisir, je vais quand même regarder l'autre lib et si quelqu'un à mieux à proposer avec JC_button, je suis preneur de conseils et d'explications.

Je ne suis encore vraiment pas au point avec le C++ et je code en copie-collant des bouts trouvés de-ci de-là. Si j'ai bien compris (pas sûr du tout),staticest nécessaire pour les interruptions. Mais à y réfléchir, c'est pour les déclarations locales, ici pour les globales c'est moins clair. Là aussi, je suis preneur d'une explication.

Bonjour ProfesseurMephisto

Avec pressedFor()
et l'exemple.

Cordialement
jpbbricole

Ah oui, je l'avais vu mais je ne savais pas comment faire l'encadrement des durées minimales et maximales des clics longs et courts.

Franchement je ne vois pas trop quoi dire, c'est bien.
Pour pinailler ou pourrait se dire que sur exemple aussi simple, n'est-il pas possible de simplifier et si toutes les variables sont vraiment nécessaire et si l'ajout d'une variable ne peut pas simplifier le code?

Par exemple remplacer tous les calcules de temps de pression par une variable pour aérer la lecture du code ?
Pourquoi ne pas utiliser la propriété lastChange, plutôt que millis pour msLache
Du coup cela peut simplifier le test d'inactivité?

je ne suis pas persuadé que la variable isTyping soit absolument nécessaire ?

j'ai fait un wokwi pour voir ce que cela pourrait donner certains changements qui sont fort discutable :slight_smile: .

C'est le fait que @ProfesseurMephisto utilise de manière préventive un condensateur qui te fait dire ça ?

Pourquoi cela est-il absolument nécessaire, de ne pas avoir de condensateur dans ce cas?
Moi je crois de mémoire que je mets systématiquement un 100nF, je n'ai pas calculé le temps, tu pense que c'est trop ?