onSolved triggers Slave (I2C)

Hey cool geniuses, I have an idea for an escape room project and need your advice on:

  1. if it's possible/feasible, and
  2. what's the best way to go about it

I have one Uno rigged to solve a 5-RFID puzzle. I have a second Uno puzzle, "cut the wires" bomb. My goal is to code the RFID Uno to be the master, and once that is solved, trigger the slave Uno to release the maglock which is concealing the "bomb", start the slow ticking sound, and prepare for that puzzle to be solved.

Below is my attempt at (my first ever) coding using I2C between the two puzzles. Unfortunately, the slave Uno started "ticking" upon start up instead of waiting for the master RFIDs to be solved. Please help!?

MASTER code:

/**
* Master/Slave
* Tea Set/Bomb
*/


// DEFINES
// Provides debugging information over serial connection if defined
#define DEBUG


// LIBRARIES
// Standard SPI library comes bundled with the Arduino IDE
#include <SPI.h>
// Download and install from https://github.com/miguelbalboa/rfid
#include <MFRC522.h>
// Include the required Wire library for I2C<br>
#include<Wire.h>

// CONSTANTS
// The number of RFID readers
const byte numReaders = 5;
// Each reader has a unique Slave Select pin
const byte ssPins[] = {2, 3, 4, 5, 6};
// They'll share the same reset pin
const byte resetPin = 8;
// The sequence of NFC tag IDs required to solve the puzzle
const String correctIDs[] = {"ddc729b0", "ad292eb0", "cdec2eb0", "1df331b0", "ed2e31b0"};


// GLOBALS
// Initialise an array of MFRC522 instances representing each reader
MFRC522 mfrc522[numReaders];
// The tag IDs currently detected by each reader
String currentIDs[numReaders];
byte x = 0;


/**
 * Initialisation
 */
void setup() {

  #ifdef DEBUG
  // Initialise serial communications channel with the PC
  Serial.begin(9600);
  Serial.println(F("Serial communication started"));
  #endif
  
  // We set each reader's select pin as HIGH (i.e. disabled), so 
  // that they don't cause interference on the SPI bus when
  // first initialised
  for (uint8_t i=0; i<numReaders; i++) {
    pinMode(ssPins[i], OUTPUT);
    digitalWrite(ssPins[i], HIGH);
  }
  
  // Initialise the SPI bus
  SPI.begin();


  for (uint8_t i=0; i<numReaders; i++) {
  
    // Initialise the reader
    // Note that SPI pins on the reader must always be connected to certain
    // Arduino pins (on an Uno, MOSI=> pin11, MISO=> pin12, SCK=>pin13)
    // The Slave Select (SS) pin and reset pin can be assigned to any pin
    mfrc522[i].PCD_Init(ssPins[i], resetPin);
    
    // Set the gain to max - not sure this makes any difference...
    // mfrc522[i].PCD_SetAntennaGain(MFRC522::PCD_RxGain::RxGain_max);
    


  #ifdef DEBUG
    // Dump some debug information to the serial monitor
    Serial.print(F("Reader #"));
    Serial.print(i);
    Serial.print(F(" initialised on pin "));
    Serial.print(String(ssPins[i]));
    Serial.print(F(". Antenna strength: "));
    Serial.print(mfrc522[i].PCD_GetAntennaGain());
    Serial.print(F(". Version : "));
    mfrc522[i].PCD_DumpVersionToSerial();
  #endif
    
    // Slight delay before activating next reader
    delay(100);
  }
  
  #ifdef DEBUG
  Serial.println(F("--- END SETUP ---"));
  #endif
}


/**
 * Main loop
 */
void loop() {
  
  // Assume that the puzzle has been solved
  boolean puzzleSolved = true;


  // Assume that the tags have not changed since last reading
  boolean changedValue = false;


  // Loop through each reader
  for (uint8_t i=0; i<numReaders; i++) {


    // Initialise the sensor
    mfrc522[i].PCD_Init();
    
    // String to hold the ID detected by each sensor
    String readRFID = "";
    
    // If the sensor detects a tag and is able to read it
    if(mfrc522[i].PICC_IsNewCardPresent() && mfrc522[i].PICC_ReadCardSerial()) {
      // Extract the ID from the tag
      readRFID = dump_byte_array(mfrc522[i].uid.uidByte, mfrc522[i].uid.size);
    }
    
    // If the current reading is different from the last known reading
    if(readRFID != currentIDs[i]){
      // Set the flag to show that the puzzle state has changed
      changedValue = true;
      // Update the stored value for this sensor
      currentIDs[i] = readRFID;
    }
    
    // If the reading fails to match the correct ID for this sensor 
    if(currentIDs[i] != correctIDs[i]) {
      // The puzzle has not been solved
      puzzleSolved = false;
    }


    // Halt PICC
    mfrc522[i].PICC_HaltA();
    // Stop encryption on PCD
    mfrc522[i].PCD_StopCrypto1(); 
  }


  #ifdef DEBUG
  // If the changedValue flag has been set, at least one sensor has changed
  if(changedValue){
    // Dump to serial the current state of all sensors
    for (uint8_t i=0; i<numReaders; i++) {
      Serial.print(F("Reader #"));
      Serial.print(String(i));
      Serial.print(F(" on Pin #"));
      Serial.print(String((ssPins[i])));
      Serial.print(F(" detected tag: "));
      Serial.println(currentIDs[i]);
    }
    Serial.println(F("---"));
  }
  #endif


  // If the puzzleSolved flag is set, all sensors detected the correct ID
  if(puzzleSolved){
    onSolve();
  }
 
  // Add a short delay before next polling sensors
  //delay(100); 
}


/**
 * Called when correct puzzle solution has been entered
 */
void onSolve(){


  #ifdef DEBUG
  // Print debugging message
  Serial.println(F("Puzzle Solved!"));
  #endif;

	// Trigger slave UNO to begin loop
  // Start the I2C Bus as Master
  Wire.begin();
	Wire.beginTransmission(4);
	Wire.write("x is ");
	Wire.write(x);
	Wire.endTransmission();
	x++;
	delay(500);
  }


/**
 * Helper function to return a string ID from byte array
 */
String dump_byte_array(byte *buffer, byte bufferSize) {
  String read_rfid = "";
  for (byte i=0; i<bufferSize; i++) {
    read_rfid = read_rfid + String(buffer[i], HEX);
  }
  return read_rfid;
}


SLAVE code:

/**
 * "Cut The Wires" defuse bomb puzzle
 *
 * Players must "cut" the correct wires in order(via releasing alligator clips) to stop
 * the ticking clock.  If the clock reaches zero (after 60 minutes), the bomb "explodes"
 * with sound effects. If wires are "cut" out of order, the ticking will get faster.
 */
 
#define DEBUG
#define SD_ChipSelectPin 10
#include <SD.h>
#include <TMRpcm.h>
#include <SPI.h>
#include <Wire.h>



TMRpcm tmrpcm;



// CONSTANTS
const byte numWires = 5;
const int wirePins[numWires] = {2, 3, 4, 5, 6};
// Define the number of steps in the sequence that the player must follow
// This pin will be driven LOW to release a lock when puzzle is solved
const byte lockPin = A1;


// GLOBALS
int lastState[numWires];
// What is the order in which wires need to be cut
// 0 indicates the wire should not be cut
int wiresToCut[numWires] = {0, 1, 2, 3, 4}; // (blue, red, yellow, green)
byte wiresCutCounter = 1;
// Keep track of the current state of the device
enum State {Inactive, Active, Defused, Exploded};
State state = State::Inactive;
//This is the timestamp at which the bomb will detonate
//It is calculated by adding on the specified number of minutes in the game time
// to the value of millis() when the code is initialised.
unsigned long detonationTime;
//The game length (in minutes)
int gameDuration = 60;
byte x = 0;


void Activate() {
  state = State::Active;
  // Set the detonation time to the appropriate time in the future
  detonationTime = millis() + (unsigned long)gameDuration*60*1000;
  Serial.println("Bomb activated!");
}


void Deactivate() {
  state = State::Inactive;
}


void Detonate() {
  state = State::Exploded;
}

void receiveEvent(int howMany)
{
  while(1 < Wire.available())
  {
    char c = Wire.read();
    Serial.print(c);
  }
  int x = Wire.read();
  Serial.println(x);
}




void setup() {
  pinMode(lockPin, OUTPUT);
  digitalWrite(lockPin, HIGH);
  tmrpcm.speakerPin = 9;
  Serial.begin(9600);

  if (!SD.begin(SD_ChipSelectPin)) {
    Serial.println("SD fail");
  }


  // Initialise wire pins
  for(int i=0; i<numWires; i++) {
    pinMode(wirePins[i], INPUT_PULLUP);
    lastState[i] = digitalRead(wirePins[i]);
  }
  
  // Set the detonation time to the appropriate time in the future
  detonationTime = millis() + (unsigned long)gameDuration*60*1000;


  // Print the initial state of the wires
  for(int i=0; i<numWires; i++) {
    Serial.println(F("Wire "));
    Serial.print(i);
    Serial.println(digitalRead(wirePins[i])? " Unconnected" : " Connected");
  }


  // Arm the bomb!
  Wire.begin(4);
  Wire.onReceive(receiveEvent);
  if (x = 5) {
    Activate();
    tmrpcm.volume(5);
    tmrpcm.play("slow.wav");
  }
};



void loop() {
  // "poor man's" debouncing
  delay(25);
  digitalWrite(lockPin, LOW);
  // First, see if any of the wires have been recently cut
  for(int i=0; i<numWires; i++) {
    // If the previous reading was LOW, but now it's HIGH, that means this wire must have been cut
    if(digitalRead(wirePins[i]) == HIGH && lastState[i] == LOW) {
      Serial.println("Wire ");
      Serial.print(i);
      Serial.println(" cut");
      lastState[i] = HIGH;


      // Was this the correct wire to cut?
      if(wiresCutCounter == wiresToCut[i]) {
        // Go on to the next counter
        wiresCutCounter++;
      }
      // Incorrect wire cut
      else {
        // Play faster ticking sound effect
        tmrpcm.setVolume(5);
        tmrpcm.play("fast.wav");
        Serial.println("  WRONG WIRE");
        break;
      }
    }
    // If the previous reading was LOW, but now it's HIGH, that means this wire must just have been cut
    else if(digitalRead(wirePins[i]) == LOW && lastState[i] == HIGH) {
      Serial.println(" Wire ");
      Serial.println(i);
      Serial.println( " reconnected");
      lastState[i] = LOW;
      break;
    }
  }


  // Now, test the current state of all wires against the solution state
  //First, assume that the correct wires have all been cut
  bool allWiresCut = true;
  // Then, loop over the wires array
  for(int i=0; i<numWires; i++) {
    // Every wire that has a number > 0 in the wiresToCut array should be cut (at some point), after which they will read HIGH,
    // So if any of them still read LOW, that means that there is at least one wire still to be cut
    if (wiresToCut[i] !=0 && lastState[i] == LOW) {
      allWiresCut = false;
      break;
    }
  }


  // What to do next depends on the current state of the device
  if(state == State::Active) {
    // Retrieve the current timestamp
    unsigned long currentTime = millis();
    if(currentTime > detonationTime) {
      Detonate();
      Serial.println("BOOM!");
      tmrpcm.setVolume(5);    // play explosion sound effect
      tmrpcm.play("expl.wav");
    }
    else if(numWires <= wiresCutCounter) {
      Deactivate();
      Serial.println("Bomb defused!");
      tmrpcm.setVolume(5);   // Play Sousa music
      tmrpcm.play("Sous.wav");
    }
  }
}

oops! That is an assignment, not a comparison (==) so your if() statement is always true

Also, in your master code, you only need Wire.begin() in setup(), not every time. It would probably be much better to send just the value of x as 1 byte rather than a string. Look at the i2c read/write examples in the IDE>

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.