Arduino Electromagnet Release Method & Remote Control

Selfbondage software and other kinky developments

Moderators: Riddle, Shannon SteelSlave

Post Reply
Te rieh
*
Posts: 11
Joined: 07 Dec 2021, 06:02

Arduino Electromagnet Release Method & Remote Control

Post by Te rieh »

Good morning.

I would like to share the release method that I'm using lately in my sessions.

As I can not use the freezer (there are more people living in the same place), I did a DIY release method with some spare parts that I got at home.

The list of items that you are going to need if you do want to build one:


I drilled the necessary holes in the box:
Image
The frontal with the rotatory encoder, the On/off Switch and the LCD Screen


Image
At the top, I have drilled two holes that give access to Arduino's USB and power jack (easier to upload sketches and also it gives the possibility of playing without the need of a battery)


Image
On one side I drilled the hole for the electromagnet that will keep the keys until countdown ends and in the rear side, the magnet to secure the box in any metallic lamp.


Image
I like to secure all the parts with hot glue when I know for sure that everything works.


In case anyone is going to build one, here there are the schematics with the connections:
Image
Image


This is the code you have to upload to your Arduino:

Code: Select all

#include <Wire.h>
#include <SoftwareSerial.h>
#include <LiquidCrystal_I2C.h>
 
#define outputA 5     // rotatory encoder output A.
#define outputB 7     // rotatory encoder output A.
#define buttonPin 6   // rotatory encoder push button.
#define magnetPin 3   // magnet output pin.
#define customRX 8    // rx port to comunicate with aditional devices without using the Arduino's serial port (USB or pins 0 and 1).
#define customTX 9    // tx port to comunicate with aditional devices without using the Arduino's serial port (USB or pins 0 and 1).
 
 
// Variables used in the program
int long remaining = 1;              // time in minutes left to end the session. The minimum allowed is 1 minute. The maximum is 999 minutes.
int long remainingInSec = 1;         // time in seconds left to end the session. The minimum allowed is 60 seconds. The maximum is 59.940 seconds.
long countdownHour = 0;              // needed for time operations, it will express the remaining hours.
long countdownMinute = 0;            // needed for time operations, it will express the remaining minutes.
long countdownSec = 0;               // needed for time operations, it will express the remaining seconds.
long delayedCountdownMinute = 0;     // needed for time operations, it will express the remaining minutes.
long delayedCountdownSec = 0;        // needed for time operations, it will express the remaining seconds.
int cursorPosition = 1;              // defines where in the screen we are. It can be a position inside one screen or a whole screen.
bool session = false;                // if true, the session has started (countdown, waiting for external events, etc...). If false we are in the setup yet.
bool preSession = false;             // if true, we are in the delayed start countdown. We can go back yet to change any setting.
bool aleatory = false;               // defines if we are going to play with a random duration of time between the max and the min or with a fixed amount of time.
bool visible = true;                 // if true, the countdown and a progress bar will be shown on the screen. If false, the remaining time will be hidden.
int short maximum = 1;               // variable holding the maximum amount of time that we do want the session to last.
int short tempmaximum = 1;           // temporal variable to perform time operations before changing maximum value.
int short minimum = 1;               // variable holding the minimum amount of time that we do want the session to last if we setup a random duration.
int short tempminimum = 1;           // temporal variable to perform time operations before changing minimum value.
int power = 255;                     // the value that we are going to analogWrite to the pin that controls the electromagnet.
int tempPower = 0;                   // temporal variable to perform math operations before changing power value.
int short delayedStart = 1;          // amount of minutes between the setup ending and session starting. It will prevent that users can see random time calculated and leave the session.
int delayedStartInSec = 60;          // amount of seconds between the setup ending and session starting. It will prevent that users can see random time calculated and leave the session.
int short tempDelayedStart = 1;      // temporal variable to perform math operations before changing delayed start value.
unsigned long previousMillis = 0;    // it stores the moment the screen blinked before. It will allow showing the selected value without using code blocking delays.
unsigned long preSessionMillis = 0;  // it stores the last time that we performed a countdown update during the pre-session.
unsigned long sessionMillis = 0;     // it stores the last time that we performed a countdown update during the session.
const int short blinkInterval = 700; // ms between screen blinking. The lower the value is, the faster the screen will blink.
int blinkState = LOW;                // stores current blinking state to toggle between showing the value or hidding it.
int backlightState = LOW;            // stores current lcd backlight state to toggle between on and off, so the user will see that the countdown is working even in hidden mode.
int long b = 1;                      // variable that we are going to use to compare if the user has inputted a bigger minimum than maximum.
int long a = 0;                      // variable that we are going to use to compare if the user has inputted a bigger minimum than maximum.
 
 
// Button timing variables
int debounce = 50;                 // ms debounce period to prevent flickering when pressing or releasing the button.
int DCgap = 500;                   // max ms between clicks for a double click event.
int holdTime = 1200;               // ms hold period: how long to wait for press+hold event.
int longHoldTime = 3000;           // ms long hold period: how long to wait for press+hold event.
 
 
// Button variables
boolean buttonVal = HIGH;          // value read from button.
boolean buttonLast = HIGH;         // buffered value of the button's previous state.
boolean DCwaiting = false;         // whether we're waiting for a double click (down).
boolean DConUp = false;            // whether to register a double click on next release, or whether to wait and click.
boolean singleOK = true;           // whether it's OK to do a single click.
long downTime = -1;                // time the button was pressed down.
long upTime = -1;                  // time the button was released.
boolean ignoreUp = false;          // whether to ignore the button release because the click+hold was triggered.
boolean waitForUp = false;         // when held, whether to wait for the up event.
boolean holdEventPast = false;     // whether or not the hold event happened already.
boolean longHoldEventPast = false; // whether or not the long hold event happened already.
 
 
// Rotatory encoder variables
int aState;         // current state of the rotatory encoder.
int aLastState;     // last state of the rotatory encoder, needed to compare if it has changed.
int counter = 0;    // counts the number of rotations performed.
 
 
// We do declare the Liquid Crystal Display that we are going to use, with the I2C address and the number of columns and rows.
// Find out your I2C address here: https://create.arduino.cc/projecthub/abdularbi17/how-to-scan-i2c-address-in-arduino-eaadda
LiquidCrystal_I2C lcd(0x27, 16, 2);
 
 
// Now we are going to create the special characters that we do want to show on the LCD screen.
// At first we do create the characters that form part of the progress bar.
 
byte one[8] = {
  B10000,
  B10000,
  B10000,
  B10000,
  B10000,
  B10000,
  B10000,
  B10000
};
 
byte two[8] = {
  B11000,
  B11000,
  B11000,
  B11000,
  B11000,
  B11000,
  B11000,
  B11000
};
 
byte three[8] = {
  B11100,
  B11100,
  B11100,
  B11100,
  B11100,
  B11100,
  B11100,
  B11100
};
 
byte four[8] = {
  B11110,
  B11110,
  B11110,
  B11110,
  B11110,
  B11110,
  B11110,
  B11110
};
 
byte five[8] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111
};
 
 
// Next we are going to create the characters that are going to be shown like the word 'to' and a padlock opened and closed
byte openPadlock[8] = {
  B01100,
  B10010,
  B00001,
  B00001,
  B11111,
  B11011,
  B11011,
  B11111
};
 
 
byte ToChar[8] = {
  B01110,
  B00100,
  B00100,
  B00000,
  B01110,
  B01010,
  B01110,
  B00000
};
 
 
byte closedPadlock[8] = {
  B01110,
  B10001,
  B10001,
  B10001,
  B11111,
  B11011,
  B11011,
  B11111
};
 
 
SoftwareSerial customSerial(customRX, customTX);
 
 
void setup()
{
  pinMode (outputA, INPUT);
  pinMode (outputB, INPUT);
  pinMode (buttonPin, INPUT_PULLUP);
 
  lcd.init();
  lcd.createChar(0, ToChar);
  lcd.createChar(1, one);
  lcd.createChar(2, two);
  lcd.createChar(3, three);
  lcd.createChar(4, four);
  lcd.createChar(5, five);
  lcd.createChar(6, openPadlock);
  lcd.createChar(7, closedPadlock);
 
 
  // We do start the backlight of the lcd screen
  lcd.backlight();
 
  // We do write the messages of the first screen
  lcd.setCursor(0, 0);
  lcd.print("MAX=");
  lcd.setCursor(6, 0);
  lcd.print(maximum);
  lcd.setCursor(0, 1);
  lcd.print("MIN=");
  lcd.setCursor(6, 1);
  lcd.print(minimum);
  lcd.setCursor(8, 0);
  lcd.print("RANDOM=");
  lcd.setCursor(15, 0);
  lcd.print("N");
  lcd.setCursor(8, 1);
  lcd.print("HIDDEN=");
  lcd.setCursor(15, 1);
  lcd.print("N");
 
  Serial.begin (9600);
  customSerial.begin(9600);
 
 
  aLastState = digitalRead(outputA);   //Leemos el valor incial
}
 
 
void loop()
{
 
  unsigned long currentMillis = millis();
 
  if (currentMillis - preSessionMillis >= 1000) {
    preSessionMillis = millis();
    if ((preSession == true) && (cursorPosition == 8)) {
      delayedStartInSec --;
      Serial.print("P");
      Serial.println(delayedStartInSec);
      customSerial.print("P");
      customSerial.println(delayedStartInSec);
      if (delayedStartInSec > 0) {
        delayedCountdownMinute = (delayedStartInSec / 60) % 60;
        delayedCountdownSec = delayedStartInSec % 60;
 
        if (delayedCountdownMinute < 10) {
          lcd.setCursor(11, 0);
          lcd.print("0");
          lcd.setCursor(12, 0);
          lcd.print(delayedCountdownMinute);
        } else {
          lcd.setCursor(11, 0);
          lcd.print(delayedCountdownMinute);
        }
 
        if (delayedCountdownSec < 10) {
          lcd.setCursor(14, 0);
          lcd.print("0");
          lcd.setCursor(15, 0);
          lcd.print(delayedCountdownSec);
        } else {
          lcd.setCursor(14, 0);
          lcd.print(delayedCountdownSec);
        }
        updateProgressBar(delayedStartInSec, delayedStart * 60, 1);
      } else {
        cursorPosition = 0;
        preSession = false;
        lcd.noBacklight();
        lcd.setCursor(0, 0);
        lcd.print("STARTING SESSION");
        lcd.setCursor(0, 1);
        lcd.print("                ");
        delay(500);
        lcd.backlight();
        delay(500);
        lcd.noBacklight();
        delay(500);
        lcd.backlight();
        delay(500);
        lcd.setCursor(8, 1);
        lcd.write(6);
        lcd.noBacklight();
        delay(500);
        lcd.backlight();
        delay(500);
        lcd.setCursor(8, 1);
        lcd.write(7);
        delay(1500);
        if (aleatory == true) {
          if (maximum > minimum) {
            remaining = random(minimum, maximum + 1);
          } else {
            remaining = random(maximum, minimum + 1);
          }
        } else {
          if (maximum > minimum) {
            remaining = maximum;
          } else {
            remaining = minimum;
          }
        }
 
        Serial.print("D");
        Serial.println(remaining * 60);
        customSerial.print("D");
        customSerial.println(remaining * 60);
        remainingInSec = remaining * 60;
 
        if (visible == true) {
          lcd.setCursor(0, 0);
          lcd.print("FREE IN         ");
          lcd.setCursor(0, 1);
          lcd.print("                ");
 
          countdownHour = remainingInSec / 3600;
          countdownMinute = ((remainingInSec / 60) % 60);
          countdownSec = remainingInSec % 60;
          lcd.setCursor(8, 0);
          if (countdownHour < 10) {
            lcd.print("0");
          }
          lcd.print(countdownHour);
          lcd.print(":");
          if (countdownMinute < 10) {
            lcd.print("0");
          }
          lcd.print(countdownMinute);
          lcd.print(":");
          if (countdownSec < 10) {
            lcd.print("0");
          }
          lcd.print(countdownSec);
          updateProgressBar(remainingInSec, remaining * 60, 1);
 
        } else {
          lcd.setCursor(0, 0);
          lcd.print("   SESSION IN   ");
          lcd.setCursor(1, 1);
          lcd.print("   PROGRESS   ");
          lcd.setCursor(0, 1);
          lcd.write(7);
          lcd.setCursor(15, 1);
          lcd.write(7);
        }
 
        cursorPosition = 9;
        session = true;
      }
    }
  }
 
 
 
  if (currentMillis - sessionMillis >= 1000) {
    sessionMillis = millis();
    if ((session == true) && (cursorPosition == 9)) {
      remainingInSec --;
      if (remainingInSec > 0) {
        Serial.print("S");
        Serial.println(remainingInSec);
       customSerial.print("S");
        customSerial.println(remainingInSec);
        if (visible == true) {
          countdownHour = remainingInSec / 3600;
          countdownMinute = ((remainingInSec / 60) % 60);
          countdownSec = remainingInSec % 60;
          lcd.setCursor(8, 0);
          if (countdownHour < 10) {
            lcd.print("0");
          }
          lcd.print(countdownHour);
          lcd.print(":");
          if (countdownMinute < 10) {
            lcd.print("0");
          }
          lcd.print(countdownMinute);
          lcd.print(":");
          if (countdownSec < 10) {
            lcd.print("0");
          }
          lcd.print(countdownSec);
          updateProgressBar(remainingInSec, remaining * 60, 1);
 
        } else {
          if (backlightState == LOW) {
            backlightState = HIGH;
            lcd.backlight();
          } else {
            backlightState = LOW;
            lcd.noBacklight();
          }
        }
      } else {
        cursorPosition = 10;
        session = false;
        lcd.setCursor(0, 0);
        lcd.print("SESSION ENDED :)");
        lcd.setCursor(0, 1);
        lcd.print("                ");
        lcd.setCursor(7, 1);
        lcd.write(6);
        analogWrite(magnetPin, 0);
        lcd.backlight();
        Serial.print("E");
        customSerial.println("E");
      }
    }
  }
 
 
  if (Serial.available() > 0) {
    int received1 = Serial.read();
    if (received1 == 'C') {
      shortKeyPress();
    } else if (received1 == 'D') {
      doubleKeyPress();
    } else if (received1 == 'L') {
      longKeyPress();
    } else if (received1 == '0') {
      externalEndSession();
    } else if (received1 == '1') {
      externalTimeAddition(1);
    } else if (received1 == '2') {
      externalTimeAddition(2);
    } else if (received1 == '3') {
      externalTimeAddition(3);
    } else if (received1 == '4') {
      externalTimeAddition(4);
    } else if (received1 == '5') {
      externalTimeAddition(5);
    } else if (received1 == '6') {
      externalTimeAddition(6);
    } else if (received1 == '7') {
      externalTimeAddition(7);
    } else if (received1 == '8') {
      externalTimeAddition(8);
    } else if (received1 == '9') {
      externalTimeAddition(9);
    }
  }
 
  if (customSerial.available() > 0) {
    int received2 = customSerial.read();
    if (received2 == 'C') {
      shortKeyPress();
    } else if (received2 == 'D') {
      doubleKeyPress();
    } else if (received2 == 'L') {
      longKeyPress();
    } else if (received2 == '0') {
      externalEndSession();
    } else if (received2 == '1') {
      externalTimeAddition(1);
    } else if (received2 == '2') {
      externalTimeAddition(2);
    } else if (received2 == '3') {
      externalTimeAddition(3);
    } else if (received2 == '4') {
      externalTimeAddition(4);
    } else if (received2 == '5') {
      externalTimeAddition(5);
    } else if (received2 == '6') {
      externalTimeAddition(6);
    } else if (received2 == '7') {
      externalTimeAddition(7);
    } else if (received2 == '8') {
      externalTimeAddition(8);
    } else if (received2 == '9') {
      externalTimeAddition(9);
    }
  }
 
 
  if (currentMillis - previousMillis >= blinkInterval) {
    previousMillis = currentMillis;
 
    if (blinkState == LOW) {
      blinkState = HIGH;
      if (cursorPosition == 1) {
        lcd.setCursor(0, 0);
        lcd.print("MAX=");
        lcd.setCursor(0, 1);
        lcd.print("MIN=");
        lcd.setCursor(8, 0);
        lcd.print("RANDOM=");
        lcd.setCursor(8, 1);
        lcd.print("HIDDEN=");
      }
 
      if (cursorPosition == 2) {
        lcd.setCursor(0, 0);
        lcd.print("MAX=");
        lcd.setCursor(0, 1);
        lcd.print("MIN=");
        lcd.setCursor(8, 0);
        lcd.print("RANDOM=");
        lcd.setCursor(8, 1);
        lcd.print("HIDDEN=");
      }
 
      if (cursorPosition == 3) {
        lcd.setCursor(0, 0);
        lcd.print("MAX=");
        lcd.setCursor(0, 1);
        lcd.print("MIN=");
        lcd.setCursor(8, 0);
        lcd.print("RANDOM=");
        lcd.setCursor(8, 1);
        lcd.print("HIDDEN=");
      }
 
      if (cursorPosition == 4) {
        lcd.setCursor(0, 0);
        lcd.print("MAX=");
        lcd.setCursor(0, 1);
        lcd.print("MIN=");
        lcd.setCursor(8, 0);
        lcd.print("RANDOM=");
        lcd.setCursor(8, 1);
        lcd.print("HIDDEN=");
      }
    } else {
      blinkState = LOW;
      if (cursorPosition == 1) {
        lcd.setCursor(0, 0);
        lcd.print("    ");
        lcd.setCursor(0, 1);
        lcd.print("MIN=");
        lcd.setCursor(8, 0);
        lcd.print("RANDOM=");
        lcd.setCursor(8, 1);
        lcd.print("HIDDEN=");
      }
 
      if (cursorPosition == 2) {
        lcd.setCursor(0, 0);
        lcd.print("MAX=");
        lcd.setCursor(0, 1);
        lcd.print("    ");
        lcd.setCursor(8, 0);
        lcd.print("RANDOM=");
        lcd.setCursor(8, 1);
        lcd.print("HIDDEN=");
      }
 
      if (cursorPosition == 3) {
        lcd.setCursor(0, 0);
        lcd.print("MAX=");
        lcd.setCursor(0, 1);
        lcd.print("MIN=");
        lcd.setCursor(8, 0);
        lcd.print("       ");
        lcd.setCursor(8, 1);
        lcd.print("HIDDEN=");
      }
 
      if (cursorPosition == 4) {
        lcd.setCursor(0, 0);
        lcd.print("MAX=");
        lcd.setCursor(0, 1);
        lcd.print("MIN=");
        lcd.setCursor(8, 0);
        lcd.print("RANDOM=");
        lcd.setCursor(8, 1);
        lcd.print("       ");
      }
    }
  }
 
  aState = digitalRead(outputA);
  if (aState != aLastState) {
    if (digitalRead(outputB) != aState) {
      if (cursorPosition == 4) {
        lcd.setCursor(15, 1);
        lcd.print("Y");
        visible = false;
      }
      if (cursorPosition == 3) {
        lcd.setCursor(15, 0);
        lcd.print("Y");
        aleatory = true;
      }
      if (cursorPosition == 1) {
        counter ++;
        tempmaximum = maximum + counter;
        if (tempmaximum < 1) {
          tempmaximum = 1;
          counter = 1;
        }
        if (tempmaximum > 999) {
          tempmaximum = 999;
          counter = 999;
        }
        if (tempmaximum < 10) {
          lcd.setCursor(4, 0);
          lcd.print("  ");
          lcd.setCursor(6, 0);
          lcd.print(tempmaximum);
        }
        if ((tempmaximum > 9) &&  (tempmaximum < 100)) {
          lcd.setCursor(4, 0);
          lcd.print(" ");
          lcd.setCursor(5, 0);
          lcd.print(tempmaximum);
        }
        if (tempmaximum > 99) {
          lcd.setCursor(4, 0);
          lcd.print(tempmaximum);
        }
      }
 
      if (cursorPosition == 2) {
        counter ++;
        tempminimum = counter + minimum;
        if (tempminimum < 1) {
          tempminimum = 1;
          counter = 1;
        }
        if (tempminimum > 999) {
          tempminimum = 999;
          counter = 999;
        }
        if (tempminimum < 10) {
          lcd.setCursor(4, 1);
          lcd.print("  ");
          lcd.setCursor(6, 1);
          lcd.print(tempminimum);
        }
        if ((tempminimum > 9) &&  (tempminimum < 100)) {
          lcd.setCursor(4, 1);
          lcd.print(" ");
          lcd.setCursor(5, 1);
          lcd.print(tempminimum);
        }
        if (tempminimum > 99) {
          lcd.setCursor(4, 1);
          lcd.print(tempminimum);
        }
      }
      if (cursorPosition == 6) {
        counter ++;
        tempDelayedStart = counter + delayedStart;
        if (tempDelayedStart < 1) {
          tempDelayedStart = 1;
          counter = 1;
        }
        if (tempDelayedStart > 30) {
          tempDelayedStart = 30;
          counter = 30;
        }
        if (tempDelayedStart < 10) {
          lcd.setCursor(13, 1);
          lcd.print("  ");
          lcd.setCursor(14, 1);
          lcd.print(tempDelayedStart);
        }
        if ((tempDelayedStart > 9) &&  (tempDelayedStart < 100)) {
          lcd.setCursor(13, 1);
          lcd.print("  ");
          lcd.setCursor(13, 1);
          lcd.print(tempDelayedStart);
        }
      }
 
      if (cursorPosition == 7) {
        counter = counter + 4;
        tempPower = power + counter;
 
        if (tempPower > 254) {
          tempPower = 255;
          counter = 0;
        }
 
        updateProgressBar(tempPower, 255, 1);
        analogWrite(magnetPin, tempPower);
      }
 
      if (cursorPosition == 8) {
        counter = counter + 5;
        delayedStartInSec = delayedStartInSec + counter;
 
        if (delayedStartInSec > 600) {
          delayedStartInSec = 600;
        }
        if (delayedStartInSec > delayedStart * 60) {
          delayedStart = delayedStartInSec / 60;
        }
        updateProgressBar(delayedStartInSec, delayedStart * 60, 1);
        counter = 0;
      }
 
 
    } else {
      if (cursorPosition == 4) {
        lcd.setCursor(15, 1);
        lcd.print("N");
        visible = true;
      }
      if (cursorPosition == 3) {
        lcd.setCursor(15, 0);
        lcd.print("N");
        aleatory = false;
      }
      if (cursorPosition == 1) {
        counter --;
        tempmaximum = maximum + counter;
        if (tempmaximum < 1) {
          tempmaximum = 1;
          counter = 1;
        }
        if (tempmaximum > 999) {
          tempmaximum = 999;
          counter = 999;
        }
        if (tempmaximum < 10) {
          lcd.setCursor(4, 0);
          lcd.print("  ");
          lcd.setCursor(6, 0);
          lcd.print(tempmaximum);
        }
        if ((tempmaximum > 9) &&  (tempmaximum < 100)) {
          lcd.setCursor(4, 0);
          lcd.print(" ");
          lcd.setCursor(5, 0);
          lcd.print(tempmaximum);
        }
        if (tempmaximum > 99) {
          lcd.setCursor(4, 0);
          lcd.print(tempmaximum);
        }
      }
      if (cursorPosition == 2) {
        counter --;
        tempminimum = minimum + counter;
        if (tempminimum < 1) {
          tempminimum = 1;
          counter = 1;
        }
        if (tempminimum > 999) {
          tempminimum = 999;
          counter = 999;
        }
        if (tempminimum < 10) {
          lcd.setCursor(4, 1);
          lcd.print("  ");
          lcd.setCursor(6, 1);
          lcd.print(tempminimum);
        }
        if ((tempminimum > 9) &&  (tempminimum < 100)) {
          lcd.setCursor(4, 1);
          lcd.print(" ");
          lcd.setCursor(5, 1);
          lcd.print(tempminimum);
        }
        if (tempminimum > 99) {
          lcd.setCursor(4, 1);
          lcd.print(tempminimum);
        }
      }
 
      if (cursorPosition == 6) {
        counter --;
        tempDelayedStart = delayedStart + counter;
        if (tempDelayedStart < 1) {
          tempDelayedStart = 1;
          counter = 1;
        }
        if (tempDelayedStart > 30) {
          tempDelayedStart = 30;
          counter = 30;
        }
        if (tempDelayedStart < 10) {
          lcd.setCursor(13, 1);
          lcd.print("  ");
          lcd.setCursor(14, 1);
          lcd.print(tempDelayedStart);
        }
        if ((tempDelayedStart > 9) &&  (tempDelayedStart < 100)) {
          lcd.setCursor(13, 1);
          lcd.print("  ");
          lcd.setCursor(13, 1);
          lcd.print(tempDelayedStart);
        }
      }
 
      if (cursorPosition == 7) {
        counter = counter - 4;
        tempPower = power + counter;
 
        if (tempPower < 1) {
          tempPower = 1;
          counter = 0;
        }
 
        updateProgressBar(tempPower, 255, 1);
        analogWrite(magnetPin, tempPower);
      }
    }
 
  }
  aLastState = aState; // We do store the last value
 
 
  // Get button event and act accordingly
  int c = checkButton();
  if (c == 1) {
    shortKeyPress();
  }
  if (c == 2) {
    doubleKeyPress();
  }
  if (c == 3) {
    longKeyPress();
  }
}
 
 
 
 
// called when button is kept pressed for less than 1 seconds
void shortKeyPress() {
 
  if (cursorPosition == 5) {
    cursorPosition = 1;
  }
 
  if (cursorPosition < 6 && cursorPosition > 0) {
    cursorPosition ++;
    counter = 0;
  } else if (cursorPosition == 6) {
    cursorPosition = 0;
    delayedStart = tempDelayedStart;
    delayedStartInSec = delayedStart * 60;
    counter = 0;
    lcd.setCursor(0, 0);
    lcd.print("MAGNET  STRENGTH");
    lcd.setCursor(0, 1);
    lcd.print("                 ");
    updateProgressBar(power, 255, 1);
    cursorPosition = 7;
 
    analogWrite(magnetPin, power);
 
  } else if (cursorPosition == 7) {
    cursorPosition = 0;
    lcd.setCursor(0, 1);
    lcd.print("                 ");
    lcd.setCursor(0, 0);
    lcd.print("START IN:       ");
    lcd.setCursor(13, 0);
    lcd.print(":");
 
    delayedStart = tempDelayedStart;
    delayedStartInSec = delayedStart * 60;
 
    delayedCountdownMinute = (delayedStart % 60);
    delayedCountdownSec = (delayedStartInSec) % 60;
 
    lcd.setCursor(11, 0);
    if (delayedCountdownMinute < 10) {
      lcd.print("0");
    }
 
    lcd.print(delayedCountdownMinute);
 
    lcd.setCursor(14, 0);
    if (delayedCountdownSec < 10) {
      lcd.print("0");
    }
    lcd.print(delayedCountdownSec);
    updateProgressBar(delayedStart * 60, delayedStart * 60, 1);
    cursorPosition = 8;
    delay(1000);
    preSession = true;
 
  } else if (cursorPosition == 10) {
    //We have to reset all the variables to their default values
    remaining = 1;
    remainingInSec = 1;
    countdownHour = 0;
    countdownMinute = 0;
    countdownSec = 0;
    delayedCountdownMinute = 0;
    delayedCountdownSec = 0;
 
    session = false;
    preSession = false;
    aleatory = false;
    visible = true;
    maximum = 1;
    tempmaximum = 1;
    minimum = 1;
    tempminimum = 1;
    power = 255;
    tempPower = 0;
    delayedStart = 1;
    delayedStartInSec = 60;
    tempDelayedStart = 1;
    b = 1;
    a = 0;
 
    // We do start the backlight of the lcd screen
    lcd.backlight();
 
    // We do write the messages of the first screen
    lcd.setCursor(0, 0);
    lcd.print("MAX=            ");
    lcd.setCursor(6, 0);
    lcd.print(maximum);
    lcd.setCursor(0, 1);
    lcd.print("MIN=            ");
    lcd.setCursor(6, 1);
    lcd.print(minimum);
    lcd.setCursor(8, 0);
    lcd.print("RANDOM=");
    lcd.setCursor(15, 0);
    lcd.print("N");
    lcd.setCursor(8, 1);
    lcd.print("HIDDEN=");
    lcd.setCursor(15, 1);
    lcd.print("N");
    cursorPosition = 1;
  }
 
  maximum = tempmaximum;
  minimum = tempminimum;
 
}
 
 
 
 
// called when button is kept pressed twice in less than 500ms
void doubleKeyPress() {
 
  if ((cursorPosition < 5) && (cursorPosition > 0)) {
 
    maximum = tempmaximum;
    minimum = tempminimum;
 
    cursorPosition = 0;
 
    if (maximum > minimum) {
      b = maximum;
      a = minimum;
    } else {
      a = maximum;
      b = minimum;
    }
    if ((aleatory == true) && (a != b)) {
      lcd.setCursor(0, 0);
      lcd.print("                 ");
      lcd.setCursor(0, 1);
      lcd.print("                 ");
      if (a < 10) {
        lcd.setCursor(0, 0);
        lcd.print("  ");
        lcd.setCursor(2, 0);
        lcd.print(a);
      }
      if ((a > 9) &&  (a < 100)) {
        lcd.setCursor(0, 0);
        lcd.print(" ");
        lcd.setCursor(1, 0);
        lcd.print(a);
      }
      if (a > 99) {
        lcd.setCursor(0, 0);
        lcd.print(a);
      }
 
      lcd.setCursor(3, 0);
      lcd.write(0);
 
      if (b < 10) {
        lcd.setCursor(4, 0);
        lcd.print(b);
        lcd.setCursor(6, 0);
        lcd.print("min");
      }
      if ((b > 9) &&  (b < 100)) {
        lcd.setCursor(4, 0);
        lcd.print(b);
        lcd.setCursor(6, 0);
        lcd.print("m.");
      }
      if (b > 99) {
        lcd.setCursor(4, 0);
        lcd.print(b);
        lcd.setCursor(7, 0);
        lcd.print("'");
      }
 
 
    } else {
      if (maximum > minimum) {
        b = maximum;
        a = minimum;
      } else {
        a = maximum;
        b = minimum;
      }
      lcd.setCursor(0, 0);
      lcd.print("                 ");
      if (b < 10) {
        lcd.setCursor(0, 0);
        lcd.print("  ");
        lcd.setCursor(2, 0);
        lcd.print(b);
      }
      if ((b > 9) &&  (b < 100)) {
        lcd.setCursor(0, 0);
        lcd.print(" ");
        lcd.setCursor(1, 0);
        lcd.print(b);
      }
      if (b > 99) {
        lcd.setCursor(0, 0);
        lcd.print(b);
      }
      lcd.setCursor(4, 0);
      lcd.print("min.");
    }
    lcd.setCursor(0, 1);
    lcd.print("START DELAY =        ");
 
    lcd.setCursor(14, 1);
    lcd.print(delayedStart);
 
    lcd.setCursor(15, 1);
    lcd.print("'");
 
 
    if (visible == true) {
      lcd.setCursor(9, 0);
      lcd.print("VISIBLE");
    } else {
      lcd.setCursor(10, 0);
      lcd.print("HIDDEN");
    }
    cursorPosition = 6;
 
  } else if (cursorPosition == 6) {
    cursorPosition = 0;
    counter = 0;
    lcd.setCursor(0, 0);
    lcd.print("MAGNET  STRENGTH");
    lcd.setCursor(0, 1);
    lcd.print("                 ");
    updateProgressBar(power, 255, 1);
    cursorPosition = 7;
    analogWrite(magnetPin, power);
 
  } else if (cursorPosition == 7) {
    cursorPosition = 0;
    lcd.setCursor(0, 1);
    lcd.print("                 ");
   lcd.setCursor(0, 0);
    lcd.print("START IN:       ");
    lcd.setCursor(13, 0);
    lcd.print(":");
 
    delayedStart = tempDelayedStart;
    delayedStartInSec = delayedStart * 60;
 
    delayedCountdownMinute = (delayedStart % 60);
    delayedCountdownSec = (delayedStartInSec) % 60;
 
    lcd.setCursor(11, 0);
    if (delayedCountdownMinute < 10) {
      lcd.print("0");
    }
 
    lcd.print(delayedCountdownMinute);
 
    lcd.setCursor(14, 0);
    if (delayedCountdownSec < 10) {
      lcd.print("0");
    }
    lcd.print(delayedCountdownSec);
    updateProgressBar(delayedStart * 60, delayedStart * 60, 1);
    cursorPosition = 8;
    delay(1000);
    preSession = true;
 
  } else if (cursorPosition == 10) {
    //We have to reset all the variables to their default values
    remaining = 1;
    remainingInSec = 1;
    countdownHour = 0;
    countdownMinute = 0;
    countdownSec = 0;
    delayedCountdownMinute = 0;
    delayedCountdownSec = 0;
 
    session = false;
    preSession = false;
    aleatory = false;
    visible = true;
    maximum = 1;
    tempmaximum = 1;
    minimum = 1;
    tempminimum = 1;
    power = 255;
    tempPower = 0;
    delayedStart = 1;
    delayedStartInSec = 60;
    tempDelayedStart = 1;
    b = 1;
    a = 0;
 
    // We do start the backlight of the lcd screen
    lcd.backlight();
 
    // We do write the messages of the first screen
    lcd.setCursor(0, 0);
    lcd.print("MAX=            ");
    lcd.setCursor(6, 0);
    lcd.print(maximum);
    lcd.setCursor(0, 1);
    lcd.print("MIN=            ");
    lcd.setCursor(6, 1);
    lcd.print(minimum);
    lcd.setCursor(8, 0);
    lcd.print("RANDOM=");
    lcd.setCursor(15, 0);
    lcd.print("N");
    lcd.setCursor(8, 1);
    lcd.print("HIDDEN=");
    lcd.setCursor(15, 1);
    lcd.print("N");
    cursorPosition = 1;
  }
}
 
 
void longKeyPress() {
 
  if (cursorPosition > 5 && cursorPosition != 10 && session == false) {
    preSession = false;
    cursorPosition = 0;
    analogWrite(magnetPin, 0);
    delay(200);
    lcd.setCursor(0, 0);
    lcd.print("MAX=    ");
    lcd.setCursor(0, 1);
    lcd.print("MIN=    ");
    lcd.setCursor(8, 0);
    lcd.print("RANDOM= ");
 
    if (aleatory == true) {
      lcd.setCursor(15, 0);
      lcd.print("Y");
    } else {
      lcd.setCursor(15, 0);
      lcd.print("N");
    }
 
    lcd.setCursor(8, 1);
    lcd.print("HIDDEN= ");
 
    if (visible == true) {
      lcd.setCursor(15, 1);
      lcd.print("N");
    } else {
      lcd.setCursor(15, 1);
      lcd.print("Y");
    }
 
    if (minimum < 10) {
      lcd.setCursor(6, 1);
      lcd.print(minimum);
    }
    if ((minimum > 9) &&  (minimum < 100)) {
      lcd.setCursor(5, 1);
      lcd.print(minimum);
    }
    if (minimum > 99) {
      lcd.setCursor(4, 1);
      lcd.print(minimum);
    }
 
    if (maximum < 10) {
      lcd.setCursor(6, 0);
      lcd.print(maximum);
    }
    if ((maximum > 9) &&  (maximum < 100)) {
      lcd.setCursor(5, 0);
      lcd.print(maximum);
    }
    if (maximum > 99) {
      lcd.setCursor(4, 0);
      lcd.print(maximum);
    }
    cursorPosition = 1;
  } else if (cursorPosition == 10) {
 
    //We have to reset all the variables to their default values
    remaining = 1;
    remainingInSec = 1;
    countdownHour = 0;
    countdownMinute = 0;
    countdownSec = 0;
    delayedCountdownMinute = 0;
    delayedCountdownSec = 0;
 
    session = false;
    preSession = false;
    aleatory = false;
    visible = true;
    maximum = 1;
    tempmaximum = 1;
    minimum = 1;
    tempminimum = 1;
    power = 255;
    tempPower = 0;
    delayedStart = 1;
    delayedStartInSec = 60;
    tempDelayedStart = 1;
    b = 1;
    a = 0;
 
    // We do start the backlight of the lcd screen
    lcd.backlight();
 
    // We do write the messages of the first screen
    lcd.setCursor(0, 0);
    lcd.print("MAX=");
    lcd.setCursor(6, 0);
    lcd.print(maximum);
    lcd.setCursor(0, 1);
    lcd.print("MIN=");
    lcd.setCursor(6, 1);
    lcd.print(minimum);
    lcd.setCursor(8, 0);
    lcd.print("RANDOM=");
    lcd.setCursor(15, 0);
    lcd.print("N");
    lcd.setCursor(8, 1);
    lcd.print("HIDDEN=");
    lcd.setCursor(15, 1);
    lcd.print("N");
    cursorPosition = 1;
  }
}
 
 
 
 
 
 
void updateProgressBar(unsigned long count, unsigned long totalCount, int lineToPrintOn) {
  double factor = totalCount / 80.0;
  int percent = (count + 1) / factor;
  int number = percent / 5;
  int remainder = percent % 5;
  if (number > 0)
  {
    for (int j = 0; j < number; j++)
    {
      lcd.setCursor(j, lineToPrintOn);
      lcd.write(5);
    }
  }
  lcd.setCursor(number, lineToPrintOn);
  if (remainder == 0) {
    lcd.print(" ");
  } else {
    lcd.write(remainder);
  }
  if (number < 16) //
  {
    for (int j = number + 1; j <= 16; j++)
    {
      lcd.setCursor(j, lineToPrintOn);
      lcd.print(" ");
    }
  }
}
 
 
int checkButton() {
  int event = 0;
  buttonVal = digitalRead(buttonPin);
  // Button pressed down
  if (buttonVal == LOW && buttonLast == HIGH && (millis() - upTime) > debounce)
  {
    downTime = millis();
    ignoreUp = false;
    waitForUp = false;
    singleOK = true;
    holdEventPast = false;
    longHoldEventPast = false;
    if ((millis() - upTime) < DCgap && DConUp == false && DCwaiting == true)  DConUp = true;
    else  DConUp = false;
    DCwaiting = false;
  }
  // Button released
  else if (buttonVal == HIGH && buttonLast == LOW && (millis() - downTime) > debounce)
  {
    if (not ignoreUp)
    {
      upTime = millis();
      if (DConUp == false) DCwaiting = true;
      else
      {
        event = 2;
        DConUp = false;
        DCwaiting = false;
        singleOK = false;
      }
    }
  }
  // Test for normal click event: DCgap expired
  if ( buttonVal == HIGH && (millis() - upTime) >= DCgap && DCwaiting == true && DConUp == false && singleOK == true && event != 2)
  {
    event = 1;
    DCwaiting = false;
  }
  // Test for hold
  if (buttonVal == LOW && (millis() - downTime) >= holdTime) {
    // Trigger "normal" hold
    if (not holdEventPast)
    {
      event = 3;
      waitForUp = true;
      ignoreUp = true;
      DConUp = false;
      DCwaiting = false;
      //downTime = millis();
      holdEventPast = true;
    }
    // Trigger "long" hold
    if ((millis() - downTime) >= longHoldTime)
    {
      if (not longHoldEventPast)
      {
        event = 4;
        longHoldEventPast = true;
      }
    }
  }
  buttonLast = buttonVal;
  return event;
}
 
 
 
void externalEndSession() {
  if ((session == true) && (cursorPosition == 9)) {
    cursorPosition = 0; // We do stop the countdown temporary
    remaining = 0;
    analogWrite(magnetPin, 0);
    lcd.backlight();
    lcd.setCursor(0, 0);
    lcd.print(" SESSION ENDING ");
    lcd.setCursor(15, 1);
    lcd.write(6);
    lcd.setCursor(0, 1);
    lcd.write(6);
    lcd.setCursor(1, 1);
    lcd.print("   RECEIVED   ");
    Serial.print("E");
    customSerial.print("E");
  }
  cursorPosition = 10; //We do pass to session ended state
}
 
 
void externalTimeAddition(int timeAdded) {
  if ((session == true) && (cursorPosition == 9)) {
    cursorPosition = 0; // We do stop the countdown temporary
    remainingInSec = remainingInSec + (timeAdded * 60);
    if (remainingInSec > 59940) {
      remainingInSec = 59940;
    }
    if (remainingInSec > remaining * 60) {
      remaining = round(remainingInSec / 60);
    }
    // Depending on if we were playing with hidden screen or not, we will change the way the time addition message is shown
    if (visible == false) {
      // We do start the backlight of the lcd screen to ensure that user can see the message
      lcd.backlight();
      lcd.setCursor(0, 0);
      lcd.print(" TIME  ADDITION ");
      lcd.setCursor(0, 1);
      lcd.print("RECEIVED! +     ");
      lcd.setCursor(10, 1);
      lcd.print(timeAdded);
      lcd.setCursor(12, 1);
      lcd.print("min.");
      delay(5000);
      lcd.setCursor(0, 0);
      lcd.print("   SESSION IN   ");
      lcd.setCursor(1, 1);
      lcd.print("   PROGRESS   ");
      lcd.setCursor(0, 1);
      lcd.write(7);
      lcd.setCursor(15, 1);
      lcd.write(7);
    } else {
      lcd.backlight();
      lcd.setCursor(0, 0);
      lcd.print(" TIME  ADDITION ");
      lcd.setCursor(0, 1);
      lcd.print("RECEIVED! +     ");
      lcd.setCursor(10, 1);
      lcd.print(timeAdded);
      lcd.setCursor(12, 1);
      lcd.print("min.");
      delay(1000);
      lcd.noBacklight();
      delay(1000);
      lcd.backlight();
      delay(1000);
      lcd.noBacklight();
      delay(1000);
      lcd.backlight();
      delay(1000);
      lcd.noBacklight();
      delay(1000);
      lcd.backlight();
      lcd.setCursor(0, 0);
      lcd.print("FREE IN         ");
      lcd.setCursor(0, 1);
      lcd.print("                ");
 
      countdownHour = remainingInSec / 3600;
      countdownMinute = ((remainingInSec / 60) % 60);
      countdownSec = remainingInSec % 60;
      lcd.setCursor(8, 0);
      if (countdownHour < 10) {
        lcd.print("0");
      }
      lcd.print(countdownHour);
      lcd.print(":");
      if (countdownMinute < 10) {
        lcd.print("0");
      }
      lcd.print(countdownMinute);
      lcd.print(":");
      if (countdownSec < 10) {
        lcd.print("0");
      }
      lcd.print(countdownSec);
      updateProgressBar(remainingInSec, remaining * 60, 1);
    }
    cursorPosition = 9; //We do resume the countdown
  }
}

The idea of this release method is to set some options like the desired duration of the session (maximum time in minutes, minimum time, fixed time or random duration, possibility of seeing how many time left during the session, duration of the pre-session countdown before knowing how much time the controller has randomly calculated and possibility of adjusting the electromagnet strength to save battery and heat).


Once you turn on the device, you'll get in your LCD screen 4 possible variables to config:


Image
You can move to the next input clicking once the button of the rotatory encoder.


Moving the wheel of the rotatory encoder clockwise, to increase the time values. Moving it anti-clockwise, you decrease the value.
The maximum allowed is 999 minutes for each one. It doesn't matter if you input a minimum bigger than the maximum, the program will arrange the numbers in the next step.
Image


The Random and the Hidden inputs do only allow Yes or No values.
RANDOM=Y means that the countdown will last an amount of time between the maximum and the minimum.
RANDOM=N means that the countdown will last as much as the bigger time input that you have selected. If MAX=20 and MIN=23, the session will last 23 minutes.
HIDDEN=N means that you will have the total amount of time in the LCD screen with a decreasing progress bar
HIDDEN=Y means that you will have no idea of how much time is left before the session ends.


Doing single clicks, you can change the input value, but to move to the next screen, you have to perform a double click.
Image
In the second screen, you can see the configuration that you've set for your session in the upper row.
If you want to change some value, you can go back bydoing a long click on the embedded button.

It works like this:
  • Single click = Change input field in first screen
  • Single click = Go to next screen in every screen except first one
  • Double click = Go to next screen
  • Long Press = Go to first screen


In the bottom row, there's an input to set the total amount of time that you want to prepare yourself before the final countdown starts.
It is based on the same basis as some of boundanna's programs: if you are going to do a random duration session with this pre-session countdown, it gives you time to fix the box to an unreachable place and you can start cuffing yourself without knowing how much time the controller has calculated.
If you are going to play with a fixed time or with the hidden screen, then just set this value to 1 minute. The max allowed is 30 minutes. It is independent of the main countdown, so if you set 20 minutes of the session and 30 minutes of pre-session, the keys will not fall until 50 minutes later.


Once you have confirmed that the session settings are the ones that you want and the pre-session countdown duration, click or double click to advance to the next screen:
Image

Here you can set the power of attraction of the magnet using the rotatory encoder. Set it to the minimum necessary to hold the keys. It will avoid unnecessary heat and will make your battery last longer.
Turn the wheel counterclockwise to get a zero attraction value and test that the keys fall always to a point where you can reach them.
It is a good idea to attach the keys to a little chain to gain some extra weight. Search the main site looking for ideas about keys attached to a string and other release methods.

When you have set the magnet desired strength, click (or double click) the button to start the pre-session countdown.
Image

During the pre-session countdown, you can still go to the first screen and cancel the session by doing a long press on the button.
You can also increase the pre-session duration by turning the rotatory encoder clockwise to a maximum of 30 minutes.


When the pre-session countdown ends, the real countdown will start.
Image

Once the timer gets to zero, the session will end, the electromagnet will turn off and you'll be free

I use to hang my box in one metal lamp like this:
Image
Image

The Arduino code is prepared to work with external programs or devices.

During the pre-session countdown, it prints via serial port the remaining seconds in this format: P240 (It means Pre-session and 240 seconds = 4 minutes).
When the pre-session ends, it prints the duration that the final countdown is going to be in this format D4020 (It means Duration and the number of seconds). The duration will be printed wherever if you are playing with a random timer or a fixed one.
During the session, there will be printed the total amount of seconds left in this format: S4020 (I guess that you get the idea).
When the session ends, an E will be printed in the serial port.

I have coded it including a second Serial Port using Arduinos Pins 8 and 9. So every Serial Print will be sent to both serials (the one that works via USB cable and RX/TX pins (0 and 1) and also with the custom serial via pins 8 and 9).

That means that you can include external devices as ESP32 or BT dongles to interact with the Arduino.

If you sent an integer from 1 to 9 during the session countdown, the remaining time will be increased in that number of minutes.

If you sent a zero (0) the session will end.

If you are going to connect an ESP32 (or ESP8266) remember voltage differences between ESP(3,3V) and Arduino Pins(5V). You can send data straight from the ESP to the Arduino (using TX pin at 3,3V on the ESP and receiving this data over the original TX pin in the Arduino (D0 at 5V) or via the specially created for the occasion D8 (CustomSerial RX)).

If you want to read in the ESP the data sent by the Arduino, then you are going to need a level converter to deal with voltage differences.


It continues in the second post…
Last edited by Te rieh on 08 Dec 2021, 02:22, edited 2 times in total.
Te rieh
*
Posts: 11
Joined: 07 Dec 2021, 06:02

Re: Arduino Electromagnet Release Method

Post by Te rieh »

A little demonstration of how it would be to play adding an ESP32 would be this one:
Image

We can run a WebServer that controls the ESP32. It can activate relays, add time to the session or finish it.
Image

You can setup up to 8 relays to be connected by the ESP just by editing two lines in the sketch:
Image
The toggle buttons alternate from turning on or off the relays.
The pushbuttons do have two ways of being used:
A single click will turn on the relay for 200ms.
Keeping them pushed will keep the relay activated until you stop pressing the button.

Even if you don't want to build my Arduino's Box, you can set up a session with just the ESP and the electromagnet. Just login into the webserver, connect the electromagnet to the GPIO of the first relay, turn it on with the toggle button and tell to your Online Master that ending the session means turning that GPIO off.

The Webserver is password protected:
Image

It also runs a DDNS client, so you can arrange a meeting via any video chatting program with any contact (even here in BoundAnna) just giving her/him the link to the video chatting app, the domain of your DDNS service, the user and the password of the website.

I would recommend using duckdns, as it is free. Using it, it wouldn't mind if your public IP changes.
Just make sure that you open port 8888 and that you redirect it to the static IP address that you have assigned to the ESP.

This is the sketch to upload to your ESP32:

Code: Select all


// Import required libraries
//
#ifdef ESP32
#include <WiFi.h>
#include "AsyncTCP.h"          // Get it here: https://github.com/me-no-dev/AsyncTCP
#include "HTTPClient.h"
// Define the DDNS domain and token
#include <EasyDDNS.h> // You will need to use this library in order to run a DDNS client inside your ESP32
const char* DDNSService = "duckdns";
const char* DDNSDomain = ""; // Put your duckdns domain
const char* DDNSToken = ""; Put your duckdns token
#else
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include "ESP8266HTTPClient.h"
#endif

#include <ESPAsyncWebServer.h> // Get it here: https://github.com/me-no-dev/ESPAsyncWebServer
//#include <EasyDDNS.h> // You will need to use this library in order to run a DDNS client inside your ESP32

// Set to true to define Relay as Normally Open (NO)
#define RELAY_NO    false
// Set number of relays, Max allowed will be 8 (I have hardcoded it, maybe in future versions it will be flexible)
#define NUM_RELAYS  2

// Assign each GPIO to a relay. If you have set 8 relays, then you must declare 8 pins.
//int relayGPIOs[NUM_RELAYS] = {2, 4, 5, 18, 13, 12, 14, 27}; // Example with 8 relays, adjust to your board and your number of relays
int relayGPIOs[NUM_RELAYS] = {2, 0};

// Assign the duration of pulse when clicking in a pushing device
const int devicePushDuration = 200; // Time in ms that the teasing devices are going to be connected when the push buttons are clicked

// Define the user and the password for those that you do want to give access to the Session Control Panel
const char* accessUserName = "Master"; // Change it to whatever you want to put
const char* accessPassword = "Anna"; // Change it to whatever you want



// Network constants
const char * ssid = "";                   // Change to yours
const char * password = "";   // Change to yours
IPAddress local_IP(192, 168, 1, 121);                   // Use one free inside your network
IPAddress gateway(192, 168, 1, 1);                     // Your router's IP
IPAddress subnet(255, 255, 255, 0);                    // Your subnet mask
IPAddress primaryDNS(8, 8, 8, 8);                      // Optional
IPAddress secondaryDNS(8, 8, 4, 4);                    // Optional


const char* PARAM_INPUT_1 = "relay";
const char* PARAM_INPUT_2 = "state";

// Create AsyncWebServer object on desired port
AsyncWebServer server(8888);


const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
    input:checked+.slider {background-color: #2196F3}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
    .button {
        padding: 10px 20px;
        font-size: 24px;
        text-align: center;
        outline: none;
        color: #fff;
        background-color: #2f4468;
        border: none;
        border-radius: 5px;
        box-shadow: 0 6px #999;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
      } 
      .button2 {
        padding: 10px 20px;
        font-size: 28px;
        text-align: center;
        outline: none;
        color: #fff;
        background-color: #68372f;
        border: none;
        border-radius: 5px;
        box-shadow: 0 6px #999;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
      }  
      .button3 {
        padding: 10px 20px;
        font-size: 28px;
        text-align: center;
        outline: none;
        color: #fff;
        background-color: #2f6837;
        border: none;
        border-radius: 5px;
        box-shadow: 0 6px #999;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
      }  
  </style>
</head>
<body>
  <h2>SB Session Control Panel</h2>
  %BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?relay="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?relay="+element.id+"&state=0", true); }
  xhr.send();
}</script>


<script>
   function push(xx) {
     var xxhr = new XMLHttpRequest();
     xxhr.open("GET", "/" + xx, true);
     xxhr.send();
}</script>


<script>
   function clicked(xxx) {
     var xxxhr = new XMLHttpRequest();
     xxxhr.open("GET", "/" + xxx, true);
     xxxhr.send();
     /////////////setTimeout(function(){ window.open("/","_self"); }, 1000);
}</script>


<script>
   function leave(xxxx) {
     var xxxxhr = new XMLHttpRequest();
     xxxxhr.open("GET", "/" + xxxx, true);
     xxxxhr.send();
     setTimeout(function(){ window.open("/","_self"); }, 1000);
}</script>

<script>
function logoutButton() {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/logout", true);
  xhr.send();
  setTimeout(function(){ window.open("/logged-out","_self"); }, 1000);
}</script>

<script>
function added() {
  setTimeout(function(){ window.open("/add","_self"); }, 1000);
}</script>

<script>
function finish() {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/finish", true);
  xhr.send();
  setTimeout(function(){ window.open("/ending","_self"); }, 1000);
}</script>

<script>
function ask() {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/asking", true);
  xhr.send();
  setTimeout(function(){ window.open("/question","_self"); }, 1000);
}</script>

</body>
</html>
)rawliteral";



const char logout_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <p>Logged out or <a href="/">return to homepage</a>.</p>
  <p><strong>Note:</strong> close all web browser tabs to complete the logout process.</p>
</body>
</html>
)rawliteral";



const char ended_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <h2><strong><i>End Session Command</i> sent to your slave's device.</strong></h2>
  <br></br><br></br>
  <h3><a href="/">Click to go back to <i>Session Control Panel</i></a></h3>
</body>
</html>
)rawliteral";



const char ask_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
      .button2 {
        padding: 10px 20px;
        font-size: 28px;
        text-align: center;
        outline: none;
        color: #000;
        background-color: #feffd6;
        border: none;
        border-radius: 5px;
        box-shadow: 0 6px #999;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
      }  
      .button3 {
        padding: 10px 20px;
        font-size: 28px;
        text-align: center;
        outline: none;
        color: #fff;
        background-color: #2f6837;
        border: none;
        border-radius: 5px;
        box-shadow: 0 6px #999;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
      }  
  </style>
</head>
<body>
  <h2><strong>DO YOU WANT TO END REMOTE SB SESSION AND FREE YOUR SLAVE?</strong></h2>
  <br></br>
  <button class="button2"><a href="/">NO, BACK TO CONTROL PANEL</a></button><br></br><br></br>
  <button class="button3" onmouseup="finish();" ontouchend="finish();">END REMOTE SB SESSION</button><br></br><br></br>

  <script>
function finish() {
  setTimeout(function(){ window.open("/ending","_self"); }, 1000);
}</script>
</body>
</html>
)rawliteral";







const char add_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
      .button2 {
        padding: 10px 20px;
        font-size: 28px;
        text-align: center;
        outline: none;
        color: #000;
        background-color: #feffd6;
        border: none;
        border-radius: 5px;
        box-shadow: 0 6px #999;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
      }  
      .button3 {
        padding: 10px 20px;
        font-size: 28px;
        text-align: center;
        outline: none;
        color: #fff;
        background-color: #2f6837;
        border: none;
        border-radius: 5px;
        box-shadow: 0 6px #999;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
      }  
  </style>
</head>
<body>
  <h2><strong>DO YOU WANT TO ADD TIME TO THE SESSION?</strong></h2>
  <br></br>
  <button class="button2"><a href="/">NO, BACK TO CONTROL PANEL</a></button><br></br><br></br>
  <button class="button3" onmouseup="addtime();" ontouchend="addtime();">ADD 5 MINUTES TO COUNTDOWN</button><br></br><br></br>

  <script>
function addtime() {
  setTimeout(function(){ window.open("/adding","_self"); }, 1000);
}</script>
</body>
</html>
)rawliteral";




// Replaces placeholder with button section in your web page
String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons ="";
    buttons+= "<h4>Click to add 5 minutes to the countdown</h4><button class=\" button2\" onclick=\"added();\">ADD 5 Minutes To Countdown</button><br></br>";
    buttons+= "<h4>TOGGLE = On / Off</h4>";
    buttons+= "<h4>CLICK = Activate for " + String(devicePushDuration) + "ms.</h4>";
    buttons+= "<h4>Device On while button is pressed</h4>";
    
    for(int i=1; i<=NUM_RELAYS; i++){
      String relayStateValue = relayState(i);
      buttons+= "<label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"" + String(i) + "\" "+ relayStateValue +"><span class=\"slider\"></span></label>";
      buttons+= "<button class=\" button\" ontouchstart=\"push('onTeasing" + String(i) + "');\" onmousedown=\"push('onTeasing" + String(i) + "');\" onclick=\"clicked('onClick" + String(i) + "');\" onmouseup=\"click('offTeasing" + String(i) + "');\" ontouchend=\"leave('offTeasing" + String(i) + "');\">PUSH DEVICE " + String(i) + "</button><br></br><br></br>";
    }
    
   

    buttons+= "<h4>Click to set your slave free</h4><button class=\" button3\" onmouseup=\"ask();\" ontouchend=\"ask();\">END REMOTE SB SESSION</button><br></br><br></br>";
    buttons+= "<h4>Click to logout</h4><button onclick=\"logoutButton()\">Logout</button>";
    return buttons;
   
  }
  return String();
}

String relayState(int numRelay){
  if(RELAY_NO){
    if(digitalRead(relayGPIOs[numRelay-1])){
      return "";
    }
    else {
      return "checked";
    }
  }
  else {
    if(digitalRead(relayGPIOs[numRelay-1])){
      return "checked";
    }
    else {
      return "";
    }
  }
  return "";
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(9600);

  // Set all relays to off when the program starts - if set to Normally Open (NO), the relay is off when you set the relay to HIGH
  for(int i=1; i<=NUM_RELAYS; i++){
    pinMode(relayGPIOs[i-1], OUTPUT);
    if(RELAY_NO){
      digitalWrite(relayGPIOs[i-1], HIGH);
    }
    else{
      digitalWrite(relayGPIOs[i-1], LOW);
    }
  }

  WiFi.mode(WIFI_STA);
  // Configures static IP address
  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("STA Failed to configure");
  }
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP8266 Local IP Address
  Serial.println(WiFi.localIP());
  
  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
    request->send_P(200, "text/html", index_html, processor);
  });
  

  // Send a GET request to <ESP_IP>/update?relay=<inputMessage>&state=<inputMessage2>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
    String inputMessage;
    String inputParam;
    String inputMessage2;
    String inputParam2;
    // GET input1 value on <ESP_IP>/update?relay=<inputMessage>
    if (request->hasParam(PARAM_INPUT_1) & request->hasParam(PARAM_INPUT_2)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value();
      inputParam = PARAM_INPUT_1;
      inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
      inputParam2 = PARAM_INPUT_2;
      if(RELAY_NO){
        Serial.print("NO ");
        digitalWrite(relayGPIOs[inputMessage.toInt()-1], !inputMessage2.toInt());
      }
      else{
        Serial.print("NC ");
        digitalWrite(relayGPIOs[inputMessage.toInt()-1], inputMessage2.toInt());
      }
    }
    else {
      inputMessage = "No message sent";
      inputParam = "none";
    }
    Serial.println(inputMessage + inputMessage2);
    request->send(200, "text/plain", "OK");
  });


  // Receive an HTTP GET request
    server.on("/onClick1", HTTP_GET, [] (AsyncWebServerRequest * request) {
      if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
      if(RELAY_NO){
       digitalWrite(relayGPIOs[0], LOW);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[0], HIGH);
      }
      else{
        digitalWrite(relayGPIOs[0], HIGH);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[0], LOW);
      }   
  });

  // Receive an HTTP GET request
    server.on("/onClick2", HTTP_GET, [] (AsyncWebServerRequest * request) {
      if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
    if(RELAY_NO){
       digitalWrite(relayGPIOs[1], LOW);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[1], HIGH);
      }
      else{
        digitalWrite(relayGPIOs[1], HIGH);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[1], LOW);
      }   
  });

   // Receive an HTTP GET request
    server.on("/onClick3", HTTP_GET, [] (AsyncWebServerRequest * request) {
     if(!request->authenticate(accessUserName, accessPassword))
     return request->requestAuthentication();
    if(RELAY_NO){
       digitalWrite(relayGPIOs[2], LOW);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[2], HIGH);
      }
      else{
        digitalWrite(relayGPIOs[2], HIGH);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[2], LOW);
      }   
  });

  // Receive an HTTP GET request
    server.on("/onClick4", HTTP_GET, [] (AsyncWebServerRequest * request) {
      if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
    if(RELAY_NO){
       digitalWrite(relayGPIOs[3], LOW);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[3], HIGH);
      }
      else{
        digitalWrite(relayGPIOs[3], HIGH);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[3], LOW);
      }   
  });

  // Receive an HTTP GET request
    server.on("/onClick5", HTTP_GET, [] (AsyncWebServerRequest * request) {
      if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[4], LOW);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[4], HIGH);
      }
      else{
        digitalWrite(relayGPIOs[4], HIGH);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[4], LOW);
      }   
  });

    // Receive an HTTP GET request
    server.on("/onClick6", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
    return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[5], LOW);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[5], HIGH);
      }
      else{
        digitalWrite(relayGPIOs[5], HIGH);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[5], LOW);
      }   
  });

    // Receive an HTTP GET request
    server.on("/onClick7", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
    return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[6], LOW);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[6], HIGH);
      }
      else{
        digitalWrite(relayGPIOs[6], HIGH);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[6], LOW);
      }   
  });

    // Receive an HTTP GET request
    server.on("/onClick8", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
    return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[7], LOW);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[7], HIGH);
      }
      else{
        digitalWrite(relayGPIOs[7], HIGH);
       request->send(200, "text/plain", "ok");
       delay(devicePushDuration);
       digitalWrite(relayGPIOs[7], LOW);
      }   
  });


   // Receive an HTTP GET request
  server.on("/onTeasing1", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
    if(RELAY_NO){
       digitalWrite(relayGPIOs[0], LOW);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[0], HIGH);
       request->send(200, "text/plain", "ok");
      }   
  });

  // Receive an HTTP GET request
  server.on("/onTeasing2", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[1], LOW);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[1], HIGH);
       request->send(200, "text/plain", "ok");
      }   
  });


  // Receive an HTTP GET request
  server.on("/onTeasing3", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
    if(RELAY_NO){
       digitalWrite(relayGPIOs[2], LOW);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[2], HIGH);
       request->send(200, "text/plain", "ok");
      }   
  });

  // Receive an HTTP GET request
  server.on("/onTeasing4", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[3], LOW);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[3], HIGH);
       request->send(200, "text/plain", "ok");
      }   
  });

  // Receive an HTTP GET request
  server.on("/onTeasing5", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
    return request->requestAuthentication();
    if(RELAY_NO){
       digitalWrite(relayGPIOs[4], LOW);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[4], HIGH);
       request->send(200, "text/plain", "ok");
      }   
  });

    // Receive an HTTP GET request
  server.on("/onTeasing6", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
    return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[5], LOW);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[5], HIGH);
       request->send(200, "text/plain", "ok");
      }   
  });

    // Receive an HTTP GET request
  server.on("/onTeasing7", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
    return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[6], LOW);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[6], HIGH);
       request->send(200, "text/plain", "ok");
      }   
  });

    // Receive an HTTP GET request
  server.on("/onTeasing8", HTTP_GET, [] (AsyncWebServerRequest * request) {
    if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[7], LOW);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[7], HIGH);
       request->send(200, "text/plain", "ok");
      }   
  });

// Receive an HTTP GET request
  server.on("/offTeasing1", HTTP_GET, [] (AsyncWebServerRequest * request) {
     if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[0], HIGH);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[0], LOW);
       request->send(200, "text/plain", "ok");
      }   
  });

  // Receive an HTTP GET request
  server.on("/offTeasing2", HTTP_GET, [] (AsyncWebServerRequest * request) {
         if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[1], HIGH);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[1], LOW);
       request->send(200, "text/plain", "ok");
      }   
  });

  // Receive an HTTP GET request
  server.on("/offTeasing3", HTTP_GET, [] (AsyncWebServerRequest * request) {
         if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[2], HIGH);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[2], LOW);
       request->send(200, "text/plain", "ok");
      }   
  });

  // Receive an HTTP GET request
  server.on("/offTeasing4", HTTP_GET, [] (AsyncWebServerRequest * request) {
         if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[3], HIGH);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[3], LOW);
       request->send(200, "text/plain", "ok");
      }   
  });

  // Receive an HTTP GET request
  server.on("/offTeasing5", HTTP_GET, [] (AsyncWebServerRequest * request) {
         if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[4], HIGH);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[4], LOW);
       request->send(200, "text/plain", "ok");
      }   
  });

   // Receive an HTTP GET request
  server.on("/offTeasing6", HTTP_GET, [] (AsyncWebServerRequest * request) {
        if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[5], HIGH);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[5], LOW);
       request->send(200, "text/plain", "ok");
      }   
  });

   // Receive an HTTP GET request
  server.on("/offTeasing7", HTTP_GET, [] (AsyncWebServerRequest * request) {
         if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[6], HIGH);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[6], LOW);
       request->send(200, "text/plain", "ok");
      }   
  });

   // Receive an HTTP GET request
  server.on("/offTeasing8", HTTP_GET, [] (AsyncWebServerRequest * request) {
        if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   if(RELAY_NO){
       digitalWrite(relayGPIOs[7], HIGH);
       request->send(200, "text/plain", "ok");
      }
      else{
        digitalWrite(relayGPIOs[7], LOW);
       request->send(200, "text/plain", "ok");
      }   
  });


  server.on("/logout", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(401);
});


server.on("/logged-out", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send_P(200, "text/html", logout_html, processor);
});


server.on("/adding", HTTP_GET, [](AsyncWebServerRequest *request){
  if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
    Serial.println("5");
   request->send_P(200, "text/html", index_html, processor);
});


server.on("/finish", HTTP_GET, [](AsyncWebServerRequest *request){
if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
  Serial.println("0");
  request->send(200, "text/plain", "ok");
});


server.on("/add", HTTP_GET, [](AsyncWebServerRequest *request){
  if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
   request->send_P(200, "text/html", add_html, processor);
});


server.on("/ending", HTTP_GET, [](AsyncWebServerRequest *request){
  if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
Serial.println("0");
  request->send_P(200, "text/html", ended_html, processor);
});


  server.on("/question", HTTP_GET, [](AsyncWebServerRequest *request){
    if(!request->authenticate(accessUserName, accessPassword))
      return request->requestAuthentication();
  request->send_P(200, "text/html", ask_html, processor);
});
  

  // Start server
  server.begin();
#ifdef ESP32
  EasyDDNS.service(DDNSService);
  EasyDDNS.client(DDNSDomain, DDNSToken); // Enter your DDNS Domain & Token
  #endif
}
  
void loop() {
// Check for new public IP every 10 seconds
#ifdef ESP32
  EasyDDNS.update(60000);
 #endif
}

I'm pretty sure that the coders of BoundAnna will improve this sketch a lot (adding the time remaining without having to reload the page, for example, or including inputs to set the session duration directly from the Webserver instead of using the Arduino with the LCD).

This was not the point of the thread. The main idea was to show a release method that doesn't need a computer or ice.

It would be a great idea to use a timed ac socket in addition if you are going to play with an ac power supply instead of a battery when you are giving control to a partner.

Remember to use a safety alternative release method.



EDIT: I just updated the code
Last edited by Te rieh on 12 Dec 2021, 11:43, edited 3 times in total.
User avatar
Shannon SteelSlave
Moderator
Posts: 6530
Joined: 03 Feb 2019, 19:49
Location: New England, USA

Re: Arduino Electromagnet Release Method

Post by Shannon SteelSlave »

Send me a PM T. We should talk. Be back in a few hours.
Bondage is like a foreign film without subtitles. Only through sharing and practice can we hope to understand.
A Jedi uses bondage for knowledge and defense, never for attack.
I am so smart! I am so smart! S-M-R-T!....I, I mean S-M-A-R-T!
👠👠
User avatar
kinbaku
*****
Posts: 5047
Joined: 10 Jan 2020, 20:26
Location: Belgium

Re: Arduino Electromagnet Release Method

Post by kinbaku »

Shannon SteelSlave wrote:Send me a PM T. We should talk. Be back in a few hours.
Congrats. Very well elaborated and documented. Thanks for sharing. Be sure to contact Shannon. And possibly also with Riddle, you can certainly help each other (he uses a slightly different method with the timer and electromagnet).
User avatar
Riddle
****
Posts: 1135
Joined: 24 Sep 2008, 08:37
Location: Oregon, USA
Contact:

Re: Arduino Electromagnet Release Method & Remote Control

Post by Riddle »

Nicely done. I like the clean install into a box.
Resident timer maker. :hi:
Let’s make timers together!
User avatar
bounddosster
*****
Posts: 2003
Joined: 30 Jan 2014, 23:23
Location: England. East of Midlands.

Re: Arduino Electromagnet Release Method & Remote Control

Post by bounddosster »

Nice job.
That's my excuse and I'm sticking to it.
Te rieh
*
Posts: 11
Joined: 07 Dec 2021, 06:02

Re: Arduino Electromagnet Release Method & Remote Control

Post by Te rieh »

kinbaku wrote:Congrats. Very well elaborated and documented. Thanks for sharing.
Thanks for your kind words.

Riddle wrote:Nicely done. I like the clean install into a box.
Thanks. I really appreciate your comment.

I didn’t know that there was a former Arduino’s coder in BA.
My plan is to add a motion sensor between the rotatory encoder and the switch.
Any advice would be much appreciated.
bounddosster wrote:Nice job.
Thanks to you for your post.
User avatar
kinbaku
*****
Posts: 5047
Joined: 10 Jan 2020, 20:26
Location: Belgium

Re: Arduino Electromagnet Release Method & Remote Control

Post by kinbaku »

Te rieh wrote:My plan is to add a motion sensor between the rotatory encoder and the switch.
Any advice would be much appreciated.
Have you seen Techcolar and the discussion on rotations?
Or is it only a motion sensor that you want to install extra?
Te rieh
*
Posts: 11
Joined: 07 Dec 2021, 06:02

Re: Arduino Electromagnet Release Method & Remote Control

Post by Te rieh »

kinbaku wrote:
Te rieh wrote:My plan is to add a motion sensor between the rotatory encoder and the switch.
Any advice would be much appreciated.
Have you seen Techcolar and the discussion on rotations?
Or is it only a motion sensor that you want to install extra?
Didn't have the time to take a deep look in the Sofware forum. Look like you were having a lot of fun.

My idea is to get a motion detector sensor to prevent me to get closer to the key holder until the time is done.

But the techcollar is, definitely, impressive.
User avatar
kinbaku
*****
Posts: 5047
Joined: 10 Jan 2020, 20:26
Location: Belgium

Re: Arduino Electromagnet Release Method & Remote Control

Post by kinbaku »

Te rieh wrote:Didn't have the time to take a deep look in the Sofware forum. Look like you were having a lot of fun.

My idea is to get a motion detector sensor to prevent me to get closer to the key holder until the time is done.
I have bought this one. You put it on the keyholder and activate it as long as the time has not expired. You program it so that any movement in the active corner (Detectable area) of the sensor during a 5 minute period will increase the waiting time by one hour. :twisted:
And yes, we have a lot of fun by the Software forum (... and other forums by BoundAnna 8) )
Jonnydom16
*
Posts: 38
Joined: 21 Feb 2020, 04:24

Re: Arduino Electromagnet Release Method & Remote Control

Post by Jonnydom16 »

WOW….. JUST WOW! All I can think of is the “SHUT UP AND TAKE MY MONEY” meme lol
Post Reply