Luces de Esgrima con Arduino

Este proyecto consiste en hacer un set de luces de esgrima inalámbricas que puedan ser usadas en entrenamientos. Las luces de esgrima son una asistencia al arbitraje que ayuda a saber cuando un tirador ha tocado al oponente.

Funcionamiento:

Partes:

  • Unidad central
  • Periferal rojo
  • Periferal verde

La espada tiene un botón en la punta, y este botón se conecta a los periferales mediante un pasante (cable con tres terminaciones). Cuando ocurre un tocado, el botón de la punta es presionado, completando el circuito, y dando una señal al periférico. Cuando este detecte que el botón ha sido pulsado, enviará una notificación a la unidad central.

Cuando la unidad central recibe una notificación de tocado, hará sonar una alarma y encenderá las luces correspondientes al deportista que haya realizado el tocado. Si en los 40ms siguientes a un tocado de un tirador, el oponente también hace un tocado, será un tocado doble, y se encenderán las luces de ambos tiradores. Si el tirador hace un tocado en una parte no válida (careta, pista), se encenderá una luz amarilla, y no sonará ninguna alarma. Esta última parte es increíblemente difícil de implementar en un sistema inalámbrico, por lo que el producto desarrollado no la tendrá.

Funcionamiento del sistema desarrollado:

Cuando cualquier unidad se enciende, lleva a cabo una secuencia de inicio, donde enciende todos sus leds, zumbadores, pantallas, etc, para:

  1. Comprobar que todo el material funciona correctamente
  2. Confirmar al usuario que el sistema se ha encendido correctamente

Una vez encendido, el funcionamiento depende de si es una unidad periférica o la central. La unidad central queda a la espera de un mensaje de los periféricos, o de que se pulse un botón. Si recibe mensaje de los periféricos, reacciona adecuadamente:

  • Si el mensaje es de sincronización, responde con la identidad asignada
  • Si el mensaje es de tocado, ilumina las luces correspondientes y notifica al periférico para que haga lo mismo
  • Si se ha pulsado el botón, la acción depende del estado
    • Si no hay partida iniciada, inicia una partida
    • Si hay partida iniciada, la pausa. Si está pausada, la reanuda
    • Si se mantiene durante 3 segundos, reinicia la partida

Mientras esté en una partida, uno de los displays mostrará un reloj con 3 minutos, y los otros dos las puntuaciones de cada tirador. Cada vez que un tirador haga un tocado, su puntuación se actualizará. Si la partida es pausada, los tocados seguirán siendo recibidos, y se encenderán los leds, pero no se actualizará la puntuación. Una partida acaba cuando acabe el tiempo o uno de los tiradores llegue a 5 puntos. Cuando esto ocurra, encenderá los leds y zumbador tres veces para indicarlo, y volverá a un estado de reposo.

Los periféricos tienen un funcionamiento más sencillo. Tras su secuencia de iniciado envían un mensaje a la unidad central identificándose como «broadcast». El objetivo de esta señal es recibir su identidad (verde/rojo). Mientras no reciban una señal, el led se iluminará en amarillo para indicar que no tiene una identidad asignada. Cuando la unidad central responda con su identidad, el led se iluminará de rosa/azul para indicar la identidad rojo/verde. En adelante, se comunicará con central indicando su identidad. Los leds se iluminan de rosa/azul en vez de rojo/verde para poder luego iluminarse de rojo/verde cuando haya un tocado.

Componentes utilizados:

La unidad central usa:

  • led rojo
  • led verde
  • zumbador
  • 3 displays de 7 segmentos
  • Arduino nano
  • botón
  • pila
  • interruptor
  • transceptor NRF24L01 (y condensador)
  • cables, resistencias…

Las unidades periféricas, cada una, usan:

  • led rgb
  • interruptor
  • pila
  • transceptor NRF24L01 (y condensador)
  • Arduino nano
  • 3 conectores banana (o en su defecto un botón)
  • resistencias, cables…

Costes:

ComponentePrecio
Leds, zumbadores, cables, botones…15€
Transceptor NRF24L01 x37.2€
Arduino nano x310.2€
Carcasa/impresión 3d30€
Conectores banana8€
Pilas 9v7€
Interruptores5€
Total82,4

Estos precios son aproximaciones realizadas para poder tener una idea del coste del producto.

Diseño 3d:

Las carcasas utilizadas para encapsular cada unidad han sido diseñadas usando TinkerCad e impresas en 3d. Esto ha conllevado una serie de dificultades y problemas que se comentarán más adelante. A continuación se incluyen algunas fotos del proceso de diseño de las carcasas.

Una de las dificultades que se presentaron es la holgura. En los primeros diseños no se le dio holgura suficiente a los componentes. Si la especificación decía que necesita 3mm, se le hizo un hueco de 3mm. Debido a las dificultades de la vida real, los componentes no cabían en esos huecos. Para ayudar con esto, se diseñaron una serie de objetos para usar de referencia para los tamaños de las piezas. Se muestran a continuación.

Problemas encontrados:

  • Optimización prematura/complejidad innecesaria. Durante el desarrollo, se intentaron usar herramientas y sistemas más complejas de lo necesario, lo cual ha costado tiempo de desarrollo. Por ejemplo, la comunicación, originalmente, pretendía ser utilizando pipes distintas para comunicar con cada periférico, de manera que la unidad central estuviera escuchando en varios pipes a la vez. Esto introducía mucha complejidad, así que fue sustituido por un sistema más sencillo donde el primer byte de un mensaje identifica al emisor. También se hacía uso de una función syncClockWithCentral, que sincronizaba el reloj de los periféricos con el del central. Esto se hizo así por miedo a que la latencia de las transmisiones pudiera causar imprecisión. Sin embargo, causaba problemas y acababa haciendo que el sistema fuera más complejo, e incluso más impreciso.
  • Fallo de componentes: el proyecto necesita 3 transceptores. Se compraron 3, pero por las fiestas tardaron mucho en llegar, así que se compró un batch de 5 más. El batch de 5 salió defectuoso al completo, y uno de los 3 originales también fallaba. Esto introdujo problemas, pues costó identificar el error, y ha generado gastos adicionales y retrasos inesperados.
  • Problemas de impresión: se ha mencionado en la sección anterior un problema relativo a la holgura. Además de ese, una pieza en concreto se estaba imprimiendo mal. Resultó ser que por haber realizado algunas rotaciones, una parte de su base no hacía contacto con el suelo, lo cual hacía que al imprimir saliera mal. El error era prácticamente imperceptible en la aplicación de modelado, y ha causado mucho retraso a la hora de poder montar el producto final, ya que es una parte crucial.
  • Falta de componentes (y dificultad de reabastecimiento): al ser un proyecto que quiero poder usar, he decidido no usar el material de la universidad. Tener que comprar todos los componentes ha implicado retrasos, especialmente porque, debido al apagón y a las fiestas, la tanda que incluía los transceptores, que son la parte principal del proyecto, tardó casi una semana entera en llegar. Teniendo en cuenta que, dadas las circunstancias, sólo constaba con 2 semanas para realizar el proyecto, no poder comprobar el funcionamiento durante esa primera semana ha sido muy limitante.
  • Falta de experiencia/herramientas al soldar: para empotrar el sistema, dado el tamaño reducido necesario para que sea portátil, ha sido necesario soldar todos los componentes a un arduino nano. La falta de herramientas y experiencia ha causado que este proceso sea especialmente duro. Pelar cables y hacer soldaduras entre más de dos piezas han presentado retos especiales, dada la falta de herramientas/conocimientos/técnicas.

Proceso de desarrollo:

El desarrollo del sistema ha seguido más o menos los siguientes pasos

1. Planificación

Se investigaron posibles proyectos, decidiendo finalmente realizar las luces de esgrima. Se investigó sobre su funcionamiento para confirmar conocimientos previos, y se buscaron fuentes oficiales sobre el reglamento de esgrima y el funcionamiento de estos aparatos. También se desarrolló una idea principal de cómo montar el sistema, y se investigó qué materiales serían necesarios

2. Compra de los componentes

La compra de los componentes, debido a retrasos y fallos, ha acabado siendo algo constante, pero empezó con dos pedidos. Uno con materiales a bulto en amazon, y otro con materiales más específicos a una tienda de electrónica. Desde el principio de esta fase hubo problemas. Según el plan, los pedidos se hubieran realizado el lunes, pero debido al apagón, esto no pudo ser así. Se realizaron los pedidos el martes. Esto resultó ser mala suerte, ya que el pedido a la tienda de electrónica se enviaba mediante correos, así que no pudo llegar hasta el lunes a última hora. Temiendo que no fuera a llegar el lunes, y viendo que quedaba poco tiempo, realicé otro pedido en amazon el lunes para asegurar que al menos los transceptores los tendría, ya que son esenciales para el funcionamiento de la app. Luego resultó que una buena parte de esos componentes salieron defectuosos, así que me vi obligado a ir a una tienda a comprar una tercera tanda de transceptores.

3. Desarrollo inicial

Mientras esperaba a que llegara el pedido con los transceptores aproveché para realizar una prueba de concepto. Monté un sistema sencillo equivalente al central, pero que en vez de recibir las señales mediante radio, usaba un botón para cada tirador. Esto me permitió desarrollar la lógica del central, menos el proceso en sí de enviar y recibir mensajes.

Durante este periodo, también realicé un primer boceto de los modelos 3d necesarios para el proyecto.

4. Troubleshooting

Una vez llegaron los transceptores, pasamos a una fase de desarrollo/troubleshooting. El primer paso fue familiarizarme con las librerías utilizadas, utilizando ejemplos y programas sencillos. Una vez familiarizado busqué modificar los ejemplos utilizados para que pudieran ser utilizados en mi sistema. Este proceso se vio dificultado por los fallos en el hardware.

A la par que esta fase de troubleshooting del código, se llevó a cabo troubleshooting de los modelos 3d utilizados, creando las unidades de medición, y creando ya los diseños finales de cada parte. El frontal de la carcasa central en concreto necesitó especial atención, como se ha indicado en secciones anteriores.

5. Sprint final

Esta última fase consistió en mejorar el código, eliminar funciones problemáticas, asegurar que todo funcionara correctamente, etc, junto con el montaje del sistema a las carcasas. Como se ha comentado en la sección problemas encontrados, este ultimo paso ha presentado un reto sustancial.

Vídeo:

A continuación se incluye un vídeo en el que se puede observar el funcionamiento del sistema.

Código:

El código utilizado se divide en dos archivos: central.ino y perifericos.ino. Se incluye el código a continuación:

Central.ino

#include <TM1637Display.h>
#include <SPI.h>
#include <RF24.h>
#include <nRF24L01.h>

// This is the central unit. It receives signals from the peripherics and signals when they are correct

struct Message {
  char sentBy;
  char type;
};

enum PlayerType {
  GREEN,
  RED,
  DOUBLE
};

#define FANFARE_TIME 3000
#define INTERVAL_DOUBLE 40
#define GAME_LENGTH 180
#define HOLD_LONG 2000

int timer = 0;
int redPoints = 0;
int greenPoints = 0;

bool gameOngoing = false;
bool gamePaused = false;

const int buzzer = 7;
const int red = 4;
const int green = 2;

const int clk = A1;
const int dioRed = 3;
const int dioGreen = 5;
const int dioTimer = 6;

const int mainButton = A0;

unsigned long prevMillis;

TM1637Display displayRed(clk, dioRed);
TM1637Display displayGreen(clk, dioGreen);
TM1637Display displayTimer(clk, dioTimer);

RF24 radio(9, 8);          // CE, CSN
const byte broadcast[5] = {'0','0','0','0','0'};
const byte sync_in[5] = {'0','0','0','0','1'};
const byte r_in[5] = {'0','0','0','0','2'};
const byte g_in[5] = {'0','0','0','0','3'};

const PlayerType linkedPeripheralsList[] = {RED, GREEN};
int linkedPeripherals = 0;

void startUpSequence() {
    const uint8_t segments[] = {
      0b00000001, // Segment A
      0b00000010, // Segment B
      0b00000100, // Segment C
      0b00001000, // Segment D
      0b00010000, // Segment E
      0b00100000, // Segment F
      0b01000000, // Segment G
      0b10000000  // Decimal Point
    };
    int blinkState = 1;    // 0 = off, 1 = on
    int i = 0; //segmentIndex

    unsigned long startTime = millis();
    unsigned long lastBlinkTime = 0;
    unsigned long lastSegmentTime = 0;

    int segmentCount = 0;
    int blinkCount = 0;

    unsigned long delta = 0;
  //first case
    digitalWrite(red, blinkState);
    digitalWrite(green, blinkState);
    digitalWrite(buzzer, blinkState);
    blinkState = !blinkState;
    uint8_t pattern = segments[i]; // The pattern for one segment
    uint8_t allDigits[4] = {pattern, pattern, pattern, pattern};
    displayTimer.setSegments(allDigits, 4, 0); 
    displayRed.setSegments(allDigits, 4, 0); 
    displayGreen.setSegments(allDigits, 4, 0);
    segmentCount++;
    i++;
  //repeat cases  
    while (segmentCount<16 || blinkCount<4) {
      unsigned int now = millis();
      if (now-lastBlinkTime>400) {
        lastBlinkTime = now;
        digitalWrite(red, blinkState);
        digitalWrite(green, blinkState);
        digitalWrite(buzzer, blinkState);
        if (!blinkState) blinkCount++; // only count OFFs

        blinkState = !blinkState;
      }
      if (now-lastSegmentTime>100) {
        segmentCount++;
        lastSegmentTime=now;
        pattern = segments[i]; // The pattern for one segment
        uint8_t allDigitsLoop[4] = {pattern, pattern, pattern, pattern};
        displayTimer.setSegments(allDigitsLoop, 4, 0); 
        displayRed.setSegments(allDigitsLoop, 4, 0); 
        displayGreen.setSegments(allDigitsLoop, 4, 0); 
        i++;
        if (i>8) i=0;
      }
    }
  //last cheeky bit
    for (int i=0;i<10;i++){
      int n = i*1111;
      displayTimer.showNumberDec(n); 
      displayRed.showNumberDec(n); 
      displayGreen.showNumberDec(n); 
      delay(50);
    }
  //turn everything off
    displayTimer.clear(); 
    displayRed.clear(); 
    displayGreen.clear(); 
    digitalWrite(red, LOW);
    digitalWrite(green, LOW);
    digitalWrite(buzzer, LOW);
  //set default values  
    displayTimer.showNumberDec(0,true); 
    displayRed.showNumberDec(0,true); 
    displayGreen.showNumberDec(0,true); 
}

void updatePoints(PlayerType player, int newPoints, bool leading0=false) {
  if (player==RED) {
    redPoints = newPoints;
    //update red points screen
    displayRed.clear();
    displayRed.showNumberDec(newPoints, leading0);
  } else if (player==GREEN) {
    greenPoints = newPoints;
    //update green points screen
    displayGreen.clear();
    displayGreen.showNumberDec(newPoints, leading0);
  }
}

void touchScored(PlayerType scoringPlayer){ //should add delays to make shit legible
  Serial.println("Point scored");
  if (gameOngoing) {
    if (scoringPlayer == GREEN) {
      updatePoints(GREEN, greenPoints+1);
      if (greenPoints >= 5) { //this could/should be done within updateScore
        gameOver(GREEN);
      }
    } else if (scoringPlayer == RED) {
      updatePoints(RED, redPoints+1);
      if (redPoints >= 5) { //this could/should be done within updateScore
        gameOver(RED);
      }
    } else if (scoringPlayer == DOUBLE) {
      Serial.println("Double touch!!!");
      updatePoints(GREEN, greenPoints+1);
      updatePoints(RED, redPoints+1);
      if (greenPoints >= 5 && redPoints >= 5) {
        updatePoints(GREEN, greenPoints-1);
        updatePoints(RED, redPoints-1);
      } else if (greenPoints >= 5) {
        gameOver(GREEN);
      } else if (redPoints >= 5) { 
        gameOver(RED);
      }
    }
  }
  fanfare(scoringPlayer);
}

void fanfare(PlayerType player) {
  Serial.println("fanfare called");
  if (player==GREEN) {
    digitalWrite(green, HIGH);
  } else if (player==RED) {
    digitalWrite(red, HIGH);
  } else { //DOUBLE
    digitalWrite(red, HIGH);
    digitalWrite(green, HIGH);
  }
  digitalWrite(buzzer, HIGH);
  delay(FANFARE_TIME);
  digitalWrite(red, LOW);
  digitalWrite(green, LOW);
  digitalWrite(buzzer, LOW);
}

bool buttonPressed(int button) {
  return digitalRead(button)==LOW;
}

void startGame(){
  Serial.println("Game Starting");
  updateTimer(GAME_LENGTH);
  updatePoints(RED, 0);
  updatePoints(GREEN, 0);
  gameOngoing = true;
  prevMillis = 0; //maybe should be in update timer, since after all it is bound to the timer
}

void stopGame(){
  Serial.println("Stopping Game");
  updateTimer(GAME_LENGTH);
  updatePoints(RED, 0, true);
  updatePoints(GREEN, 0, true);
  gameOngoing = false;
}

void updateTimer(int newTime) {
  timer = newTime;
  //update timer screen

  const int mins = newTime / 60;
  const int secs = newTime % 60;
  int finalTime = mins*100+secs;
    uint8_t digits[4];

  digits[0] = 0x00;
  digits[1] = displayTimer.encodeDigit(mins); // Blank if 0
  digits[2] = displayTimer.encodeDigit(secs / 10);
  digits[3] = displayTimer.encodeDigit(secs % 10);
  digits[1] |= 0b10000000; // Add colon (dots bit)

  displayTimer.clear();
  displayTimer.setSegments(digits);

  //displayTimer.clear();
  //displayTimer.showNumberDecEx(mins*100+secs, 0b01000000, true); 
}



void gameOver(PlayerType winner) { //DOUBLE used for game over because of time
  //do some cool beeping and light display
  for (int i=0;i<3;i++) {
    digitalWrite(red, HIGH);
    digitalWrite(green, HIGH);
    digitalWrite(buzzer, HIGH);
    delay(400);
    digitalWrite(red, LOW);
    digitalWrite(green, LOW);
    digitalWrite(buzzer, LOW); 
    delay(200);

  }
  Serial.println("Game Over");
  stopGame();
}

Message readAndProcessMessage() {
  Message msg;
  char txt_received[3] = "";
  radio.read(&txt_received, sizeof(txt_received));
  msg.sentBy = txt_received[0];
  msg.type = txt_received[1];
  //logs
    char sentBy = txt_received[0];
    char type = txt_received[1];
    Serial.print("\nReceived a message: '"); Serial.print(txt_received);
    Serial.print("'. From "); Serial.print(sentBy);
    Serial.print(" of type "); Serial.println(type);
  return msg;
}

bool isDouble(char p1, unsigned long touchTime) {
  uint8_t pipe;
  while (millis()-touchTime<INTERVAL_DOUBLE){
    if (radio.available()) {
      Serial.println("Received another message. Very likely it's a double");
      unsigned long recieved_at = millis();
      //read the message
      Message msg = readAndProcessMessage();
      const char p2 = msg.sentBy;
      if ((p1=='r' && p2=='g') || (p1=='g' && p2=='r')) {  //if the message isn't from one of the players it's lost
        return true;
      }
    }
  }
  return false;
}


const byte* getAddress(char sentBy) {
  switch(sentBy) {
    case 'b': return broadcast;
    case 'r': return r_in;
    case 'g': return g_in;
    default: Serial.print("Unknown sender: ");Serial.println(sentBy);
  }
}

void syncWith(char sentBy) {
  Serial.print(sentBy);Serial.println(" wants to sync with central");

  radio.stopListening();
  radio.openWritingPipe(getAddress(sentBy));

  //send red or green as r or g

  const char msg = linkedPeripherals==0 ? 'r':'g';
  if (radio.write(&msg, sizeof(msg))) {
    Serial.print("Sync with ");Serial.print(sentBy);Serial.println(" successful");
    linkedPeripherals += 1;
    if (linkedPeripherals>=2) linkedPeripherals=0;
  } else {
    Serial.print("Sync with ");Serial.print(sentBy);Serial.println(" failed. Could not send reply with configuration.");
  }
  radio.startListening();
}

void touchBy(char player, unsigned long touchTime) {
  //player says a touch happened
  if (player!='r' && player!='g'){Serial.print("Unknown player "); Serial.println(player); return;}
  radio.stopListening();
  //wait until touchTime+40ms. Then check if green sent a message. if they did, check for double
  //while(millis()<touchTime+INTERVAL_DOUBLE);
  if (isDouble(player, touchTime)){
    //Send message to oponent as well
    bool validTouch = true;
    unsigned long until = FANFARE_TIME;
    radio.openWritingPipe(r_in);
    radio.write(&validTouch, sizeof(validTouch));
    radio.write(&until, sizeof(until));
    radio.openWritingPipe(g_in);
    radio.write(&validTouch, sizeof(validTouch));
    radio.write(&until, sizeof(until));
    touchScored(DOUBLE);
  } else {
    bool validTouch = true;
    unsigned long until = FANFARE_TIME;
    radio.openWritingPipe(player=='r'?r_in:g_in);
    radio.write(&validTouch, sizeof(validTouch));
    radio.write(&until, sizeof(until));
    touchScored(player=='r'?RED:GREEN);
  }
  radio.startListening();
}

void pauseGame() {
  gameOngoing = !gameOngoing;
  gamePaused = !gamePaused;
}

////////////////////////////////////////////////////////////////////////////////////////////////////


void setup() {
  //pin and component initialization
  Serial.begin(9600);

  pinMode(mainButton, INPUT_PULLUP);

  pinMode(buzzer, OUTPUT);
  pinMode(red, OUTPUT);
  pinMode(green, OUTPUT);


  displayRed.setBrightness(5); //0-7
  displayGreen.setBrightness(5); //0-7
  displayTimer.setBrightness(5); //0-7

  if (!radio.begin()) {
    Serial.println("Radio hardware not responding!");
    while(1);
  }
  radio.setDataRate(RF24_2MBPS);
  radio.setPALevel(RF24_PA_MAX);
  radio.enableDynamicPayloads();
  radio.setRetries(4, 5); //retries every 4*25µs=1ms up to 5 times, at hardware level. 

  radio.openWritingPipe(broadcast);
  radio.openReadingPipe(1, sync_in); // sync
  
  radio.startListening();


  prevMillis = 0;
  
  digitalWrite(red, LOW);
  digitalWrite(green, LOW);
  
  startUpSequence();

  Serial.print("\nSetup complete at "); Serial.println(millis());
}

void loop() {
  //Serial.println(digitalRead(mainButton));
  digitalWrite(red,LOW);
  digitalWrite(green,LOW);
  if (radio.available()) {
    unsigned long received_at = millis();
    const Message msg = readAndProcessMessage();
    Serial.println(msg.type);

    switch(msg.type) {
      case 'S': syncWith(msg.sentBy);break;
      case 'T': touchBy(msg.sentBy, received_at);break;
      default:Serial.print("Unknown msg type: ");Serial.println(msg.type);
    }
  }
  //we wait until a game starts

  if (buttonPressed(mainButton)) {
    Serial.println("Main Button Pressed");
    unsigned long startTime = millis();
    while (buttonPressed(mainButton)){delay(10); if (millis()-startTime >= 3000) break;};
  
    if (millis()-startTime >= HOLD_LONG) stopGame();
    else if ((gameOngoing && !gamePaused) || (!gameOngoing && gamePaused)) {pauseGame();}
    else startGame();
    delay(500);
  }
  
  if (gameOngoing) {
    if (millis() - prevMillis >= 1000) {
      updateTimer(timer-1);
      if (timer<=0) {
        gameOver(DOUBLE); //to not add a TIME to PlayerType, we'll reuse double here.
      }
      prevMillis = millis();
    } 
  }
  
}


Perifericos.ino

#include <SPI.h>
#include <RF24.h>
#include <nRF24L01.h>

// This is the central unit. It receives signals from the peripherics and signals when they are correct

enum PlayerType {
  GREEN,
  RED,
  DOUBLE
};


const int button = A0;

const int buzzer = 11;
const int red = 6;
const int green = 3;
const int blue = 5;

unsigned long prevMillis;

PlayerType identity = DOUBLE;

RF24 radio(9, 8); // CE, CSN
const byte broadcast[5] = {'0','0','0','0','0'};
const byte sync_in[5] = {'0','0','0','0','1'};
const byte r_in[5] = {'0','0','0','0','2'};
const byte g_in[5] = {'0','0','0','0','3'};

//setup/sync
  void startUpSequence() {
    analogWrite(red, 255);
    delay(500);
    analogWrite(red, 0);

    analogWrite(green, 255);
    delay(500);
    analogWrite(green, 0);

    analogWrite(blue, 255);
    delay(500);
    analogWrite(blue, 0);
  }

  bool connectToCentral() {
    Serial.println("\nAttempting to connect with central unit");

    //send a msg. if no ack, wait and retry
    //when ack, wait for response
    //configure according to response
    radio.openReadingPipe(1, broadcast);
    radio.openWritingPipe(sync_in);

    radio.stopListening();
    const char txt_sent[] = "bS";
    int failed_attempts = 0;
    int failed_attempts_cap = 5;
    while (!radio.write(&txt_sent, sizeof(txt_sent))) {
      failed_attempts++;
      if (failed_attempts % failed_attempts_cap == 0) {
        Serial.print("Tried to send '"); Serial.print(txt_sent); Serial.print("' but didn't get a response. ");
        Serial.print("Total attempts: "); Serial.println(failed_attempts);
      }
      if (failed_attempts>10*failed_attempts_cap) {
        failed_attempts_cap = failed_attempts_cap*2;
      }
      delay(1000); 
    };
    if (failed_attempts>0) Serial.println("Recieved an acknowledment!");

    //Found central, wait for config response
    radio.startListening();
    unsigned long startTime = millis();
    while (!radio.available()) {
      if (millis()-startTime>5000) { //5 sec
        Serial.println("Did not receive central's answer. Will attempt to reconnect in 5 seconds.");
        delay(5000); //10 sec
        return false; //return to be left recursive. Apparently doesn't matter. Should use a retry loop instead
      }
      delay(10);
    }

    char txt_received; // char txt_received = ''; //to read just 1 char presumably
    radio.read(&txt_received, sizeof(txt_received)); 
    Serial.print("Config msg received. ");

    if (txt_received =='r') { //maybe generate a random number to be sent alongside the msg, to handle identity and not set both peripherals at once accidentally.
      Serial.println("I am the red peripheral. Will be using r_in");
      identity = RED;
      setColor(150,15,15);
      radio.openReadingPipe(1, r_in);
    } else if (txt_received =='g') {
      Serial.println("I am the green peripheral. Will be using g_in");
      identity = GREEN;
      setColor(10,60,10);
      radio.openReadingPipe(1, g_in);
    } else {
      Serial.print("The answer received during sync was invalid. Msg was: ");
      Serial.print(txt_received);
      Serial.println("Retrying in 10 seconds");
      delay(10000);
      return false;
    }
    radio.startListening(); //most likely useless
    return true;
  }
//other

bool waitUntilAvailableOrTimeout(unsigned long maxWaitTime) {
  unsigned long startTime = millis();
  while (!radio.available()) {
    if (millis() - startTime > maxWaitTime) return true;
    delay(10);
  }
  return false;
}

void touchScored(unsigned long startTime){
  Serial.println("Touch registered, sending signal");
//send signal
  radio.stopListening();
  char msg[3];
  msg[0] = (identity == RED) ? 'r':'g';
  msg[1] = 'T';
  msg[2] = '\0';
  unsigned long start = millis();
  bool shouldPrint = true;
  int attempts=0;
  while (!radio.write(&msg, sizeof(msg))){ //we don't consider the possibility of failure since we've established a connection
    if (shouldPrint) {
      Serial.println("Error sending touch notification to central. Retrying during the next 40ms");
      shouldPrint=false;
    }
    attempts++;
  }
  Serial.println(attempts);
  Serial.println(attempts);
//receive answer
  radio.startListening();
  bool validTouch; 
  if (waitUntilAvailableOrTimeout(200)) {Serial.println("Answer to touch signal not received. Leds/buzzer will not activate.");return;}
  radio.read(&validTouch, sizeof(validTouch));

  if (validTouch) {
    setColor(identity==RED?255:0,identity==GREEN?255:0,0);
    digitalWrite(buzzer, HIGH);
  }
  waitUntilAvailableOrTimeout(1000);
  unsigned long waitFor; 
  if (radio.available()) {
    radio.read(&waitFor, sizeof(waitFor)); 
  } else { //timed out
    Serial.println("Time to wait not received. Defaulting to 1 second.");
    waitFor = 1000;
  }
  Serial.print("Waiting with leds ON for "); Serial.println(waitFor);
  unsigned long startTime_tS = millis();
  while (millis()-startTime_tS<waitFor);
  if (identity==RED) {
    setColor(150,15,15);
  } else if (identity==GREEN) {
    setColor(10,60,10);
  }
  digitalWrite(buzzer, LOW);
}

void setColor(int redV, int greenV, int blueV) {
  analogWrite(red, redV);
  analogWrite(green, greenV);
  analogWrite(blue, blueV);
}

bool buttonPressed(int button) {
  return digitalRead(button)==LOW;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void setup() {
  //pin and component initialization
  Serial.begin(9600);

  if (!radio.begin()) {
    Serial.println("Radio hardware not responding!");
    while(1);
  }
  radio.setDataRate(RF24_2MBPS);
  radio.setPALevel(RF24_PA_MAX);
  radio.enableDynamicPayloads();
  radio.setRetries(4, 5); //retries every 4*25µs=1ms up to 5 times, at hardware level. 

  pinMode(button, INPUT_PULLUP);

  pinMode(buzzer, OUTPUT);
  pinMode(red, OUTPUT);
  pinMode(green, OUTPUT);
  pinMode(blue, OUTPUT);
  
  startUpSequence();
  setColor(255,128,0);

  while (!connectToCentral());
}

void loop() {
  if (buttonPressed(button)) {
    Serial.println("Button Pressed");
    unsigned long startTime = millis();
    while (buttonPressed(button)){if (millis()-startTime>=2) break;}; //wait until button is released. Consider using delay(10) inside the loop to reduce polling and debouncing issues

    Serial.print("Button pressed for at least: ");
    Serial.println(millis()-startTime);

    if (millis()-startTime >= 2) touchScored(startTime);
  }
}

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *