Prevent Blynk Button Presses While Stepper Motor Is Running

I am working on creating a project using an Arduino UNO and an HC-08 BLE module and I’m currently running the Blynk app (most recent version) on an iPhone 6 running iOS 11. The goal is to use a stepper motor to raise and lower a large map to hide a TV when the TV is not in use.

There is one physical momentary switch attached to the Arduino and one button widget configured in the Blynk app to act as a switch. The goal is to have both the physical button and the app switch accomplish the same task. Pressing either button will either roll up or unroll a map, depending on the current state of the map (i.e. HIGH/LOW).

At this point I’ve got most of the code working and responding as one would expect including the Blynk app mirroring the current state of the map and playing nicely with the physical button. However, there is one minor issue that is driving me crazy and I’d love some help.

If I press the button in the app to either raise or lower the map, the appropriate code executes as one would expect; however, if the Blynk button is pressed multiple times while the stepper motor is either raising or lowering the map, then the map will be raised and lowered over and over, for as many times as the button was pushed. I know I am probably missing something obvious here, but how would I go about having both Blynk and the Arduino ignore button presses made while the stepper motor is executing either a raise or lower action.

I’ve searched the documentation and these forums for an answer and haven’t found it, though in fairness I’m pretty new to this whole coding things so I could be missing something completely obvious. If that is the case I sincerely apologize and thank you for your assistance.

#define BLYNK_PRINT Serial

#include <SoftwareSerial.h>
SoftwareSerial SwSerial(10, 11); // RX, TX
    
#include <BlynkSimpleSerialBLE.h>
#include <SoftwareSerial.h>
#include <Stepper.h>

const int stepsPerRevolution = 200;
Stepper myStepper(stepsPerRevolution, 3, 4, 5, 6);

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "redacted";

SoftwareSerial SerialBLE(10, 11); // RX, TX

// Set your LED and physical button pins here
const int hallPin = 7;
int hallState = 0;
const int button = 2;
int buttonState;
int mapState = HIGH;

unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

BlynkTimer timer;
void checkPhysicalButton(); //set up blynk app and what happens when blynk switch is pressed

// Every time we connect to the cloud...
BLYNK_CONNECTED() {
  Blynk.virtualWrite(V2, mapState);
}

// When app button is pressed move the map either up or down, depending on its previous location
BLYNK_WRITE(V2){
if (param.asInt() == 1 && mapState == LOW){
        Blynk.setProperty(V2, "color", "#808080");
        Blynk.setProperty(V2, "onLabel", "Unavailable");
        Blynk.setProperty(V2, "offLabel", "Unavailable");
        myStepper.step(-1300);
        for (int i=0; i<61; i++){
          myStepper.step(-5);
          myStepper.setSpeed(60-i);
        }
        mapState = HIGH;
        Blynk.setProperty(V2, "onLabel", "Lower");
        Blynk.setProperty(V2, "offLabel", "Raise");
        Blynk.setProperty(V2, "color", "#ED9D00");
        myStepper.setSpeed(60);
    }
  else if (param.asInt() == 0 && mapState == HIGH){
        Blynk.setProperty(V2, "color", "#808080");
        Blynk.setProperty(V2, "onLabel", "Unavailable");
        Blynk.setProperty(V2, "offLabel", "Unavailable");
        myStepper.step(1300);
        for (int i=0; i<61; i++){
          myStepper.step(5);
          myStepper.setSpeed(60-i);
        }
        mapState = LOW;
        Blynk.setProperty(V2, "onLabel", "Lower");
        Blynk.setProperty(V2, "offLabel", "Raise");
        Blynk.setProperty(V2, "color", "#ED9D00");
        myStepper.setSpeed(60);
    }     
}

void checkPhysicalButton(){ //Set what happens when the physical button is pressed

   buttonState = digitalRead(button);
   
   if (buttonState == LOW && millis() - lastDebounceTime > debounceDelay && mapState == HIGH){
        Serial.println("Lowering");
        myStepper.step(1300);
        for (int i=0; i<61; i++){
          myStepper.step(5);
          myStepper.setSpeed(60-i);
        }
        mapState = LOW;
        Blynk.virtualWrite(V2, mapState);
        myStepper.setSpeed(60);
        lastDebounceTime = millis();
    }     
    else if (buttonState == LOW && millis() - lastDebounceTime > debounceDelay && mapState == LOW){
        Serial.println("Raising");
        myStepper.step(-1300);
        for (int i=0; i<61; i++){
          myStepper.step(-5);
          myStepper.setSpeed(60-i);
        }
        mapState = HIGH;
        Blynk.virtualWrite(V2, mapState);
        myStepper.setSpeed(60);
        lastDebounceTime = millis();
    }
}

void setup()
{
  // Debug console
  Serial.begin(9600);

  SerialBLE.begin(9600);
  Blynk.begin(SerialBLE, auth);

  Serial.println("Waiting for connections...");

  pinMode(hallPin, INPUT);
  pinMode(button, INPUT_PULLUP);
  
  // set the speed of the stepper motor at 30 rpm:
  myStepper.setSpeed(30);

  // home the map/stepper motor
  while (digitalRead(hallPin) == HIGH)
  {
    myStepper.step(-1);
  }
  myStepper.setSpeed(10);
  myStepper.step(100);
  myStepper.setSpeed(60);
  
  // Setup a function to be called every 100 ms
  timer.setInterval(100L, checkPhysicalButton);
}

void loop()
{
  Blynk.run();
  timer.run();
}

The key is to have one independent function (NOT a Blynk Function) that handles all the stepper and limit switch control & feedback, including not accepting any “new” requests until the first is complete. Typically done with flags and boolean logic (AKA only if a=something and b=something.else then do c).

Look at the Boolean Operators and if…else in the Control Structure sections of that link

Then you use either/or/both Blynk & physical actuation to send the UP, DOWN, STOP, commands to that function

Thank you for responding. I’m pretty sure I understand the basics of if else… etc. However, I’ll look at them a little harder to see if I can’t come up with a logic solution, it’s been evading me up to this point.

In one iteration of the code I did have a separate “flag” set up as a variable as an attempt to prevent further button presses, or at the very least button responses, while the stepper motor was chugging away, but I couldn’t seem to stop Blynk from literally cueing up presses.

I’m not sure if I made this clear or not, and I doubt it would impact your response, but the repeated button presses from the app are not acting as interrupts to the motion of the stepper motor. Instead they are acting as though they’ve been cued up, completing the programmed motor movement in full for every app button press. Which is odd to me, as I’m used to the Arduino not recognizing any button presses physically while it’s performing actions. But I have a feeling that this is due to my lack of understanding of how the Blynk app is reading the button presses, storing them, and forwarding them on to the Arduino.

As to setting up the stepper movement as a separate function, I see your point, I think. After I get the kids and wife to bed I’ll have some time to sit back down and pound a bit more on the keyboard.

That I understood :wink:

Yes… that is one ‘feature’ of having the action in the Blynk Function, each and every widget process generates a Blynk Function response.

Thus by segregating the Blynk function, any physical button function (via interrupts or polling, etc) and the actual control/feedback function, will make it easier to “ignore” redundant or repetitive commands with logic.

This in not quite the ready made example for your use case… but it demonstrates using a separate Blynk Function to control a timer that then runs an Arduino function that does the work. It might give you a broad visual as to what I am referring to?

And again… this code is not for the same purpose of you needs… but it also uses seperate Blynk Functions to gather values and pass that data onto Arduino functions… AKA…

BLYNK_WRITE(vPin) {
// get info here, assign to variable.
MyFunctionName();  // or instead of calling this function after each widget touch, use a timer... or both if needed.
}

void MyFunctionName() {
// take variable info compare with logic and, if needed/permitted, do stuff here
}