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:
- Comprobar que todo el material funciona correctamente
- 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:
Componente | Precio |
Leds, zumbadores, cables, botones… | 15€ |
Transceptor NRF24L01 x3 | 7.2€ |
Arduino nano x3 | 10.2€ |
Carcasa/impresión 3d | 30€ |
Conectores banana | 8€ |
Pilas 9v | 7€ |
Interruptores | 5€ |
Total | 82,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);
}
}