How do you interrupt a mills() function?

Hi everyone, I am new to coding, and I am trying to get a relay to turn a lamp on and off for a set time (like for an hour) using the Particle Photon cloud function. It sort of works using delay(), but if I use it, then I can’t send a request to turn the lamp off early using a website. I found that I need to use the mills() function, but all the guides I have seen are confusing to me. To add to the confusion, I want to have a physical button that I can use to interrupt the function from the website, but that shouldn’t be too hard because I can reuse the code from the off button for the website. I would really appreciate if someone could help me figure out how to turn my light on on a timer that can be interrupted/stopped early. Thank you so much for all of your help!

I know my question seems basic, but I couldn’t find any tutorials or threads that helped me. I also tried looking for a library, but I didn’t find any that were too helpful either.

Also someone should really make a comprehensive and easy to use mills() library. I bet it would be really popular.

It might be easier to advise when we can see some code.

There are multiple ways how you use millis(), we'd need to know which one you use.

2 Likes

Let’s look at what your requirements are:

  1. Turn on a relay for a variable period of time from cloud call.
  2. Turn off a relay from a cloud call whilst running.

There are 3 solution parts you need:

  1. Control over energising the relay coil. You cannot directly power it from a photon pin, you need to switch a transistor or mosfet to do this. Essentially you will do digitalWrite(pin, HIGH) to switch on and LOW to turn off.
  2. A timing mechanism- relays generally do not need not millisecond accuracy- so using Time.local seconds rather than millis() would be better - end decision is if (secondsNow - secondsStart >= timeOnSeconds) Or you setup a software timer to repeat every 60 seconds and in the handler check for minutes counted = timeOnMinutes and set a global volatile flag for the loop to use to control off.
  3. Setup a Particle.function() and handler routine to receive a command “relay” and data “on” or “off”.
1 Like

Wow you guys are so quick! I have based the code I’ve been using off of the example “Web-Connected LED” tutorial in the Particle IDE, and I am attaching my code. To give you an update from my first post, I have been trying some different functions to replace the delay(), but none of them work obviously because they are blocking the rest of the code like delay() was. Also, I just wanted to let you know that the times in the code aren’t actually set to what they will be now. I also tried to move the return integer higher in the code, but I learned that if I do that, then the code below it won’t run. I would also really appreciate if you guys could explain what the code means, so I could understand it better. I really appreciate all of your help!

// Create our "shorthand" for relay on same pin as LED
int relay = D7;
int button = D6;

void setup() {

   // Set mode to output
   pinMode(relay, OUTPUT);

   // Declare Particle.function to control from the cloud.
   Particle.function("light",light);
   // This is saying that when we ask the cloud for the function "led", it will employ the function ledToggle() from this app.

   // Close on start
   digitalWrite(relay, LOW);

}

void loop()
{
   // Nothing to do here
}

inline void softDelay(uint32_t msDelay)
{
  for (uint32_t ms = millis(); millis() - ms < msDelay; Particle.process());
}

void myDelay(int del) {
  unsigned long myPrevMillis = millis();
  while (millis()- myPrevMillis <= del);
}

// function gets called when a matching API request is sent
int light(String command) {
    /* Particle.functions always take a string as an argument and return an integer.
    Since we can pass a string, it means that we can give the program commands on how the function should be used.
    In this case, telling the function "on" will turn the LED on and telling it "off" will turn the LED off.
    Then, the function returns a value to us to let us know what happened.
    In this case, it will return 1 for the LEDs turning on, 0 for the LEDs turning off,
    and -1 if we received a totally bogus command that didn't do anything to the LEDs.
    */


    else if (command=="fopen") {
        digitalWrite(relay,HIGH);
        delay(1000);
        digitalWrite(relay,LOW);
        return 1;
    }
    else if (command=="fclose") {
        digitalWrite(relay,LOW);
        return 0;
    }
    else if (command=="t5") {
        digitalWrite(relay,HIGH);
        softDelay(5000);
        digitalWrite(relay,LOW);
        return 5;
    }
    else if (command=="t10") {
        digitalWrite(relay,HIGH);
        myDelay(2000);
        digitalWrite(relay,LOW);
        return 10;
    }
    else if (command=="t20") {
        digitalWrite(relay,HIGH);
        return 20;
        delay(2000);
        digitalWrite(relay,LOW);
        
    }
    else if (command=="t40") {
        digitalWrite(relay,HIGH);
        delay(40000);
        digitalWrite(relay,LOW);
        return 40;
    }
    else {
        return -1;
    }
}

If you want your device be responsive during that time you'd need to write

 while (millis()- myPrevMillis <= del) Particle.process();

or use SYSTEM_THREAD(ENABLED).

But that will not make your loop() code (if you had any) run during that period.

The inner delays (soft or not) within light() should be replaced with a timed callback.

e.g.


Timer offTimer(1000, relayOff, true); // create a one-shot timer with a default period

void relayOff() {
  digitalWrite(relay, LOW);
}

void timedOff(int period) {
  offTimer.changePeriod(period);
  offTimer.start();
}

...
    else if (command=="fopen") {
        digitalWrite(relay,HIGH);
        timedOff(1000);
        return 1;
    }
...

What parts in particular? There isn't anything that appears obscure by any stretch IMO.
The most "confusing" might be how for() is used in softDelay(). But knowing that a for(someStatement; someCondition; someOtherStatement); is nothing else than a while() construct like this

  someStatement;
  while (someCondition) {
    // do nothing
    someOtherStatement;
  }

should clarify that softDelay() is doing the exact same thing as your myDelay() with the (required) addition of the Particle.process() call in the loop body.

BTW, you are missing an "opening" if() branch in your function callback.

1 Like

@ScruffR you’re amazing! I never would have thought of moving the off function to the timer function. It works, and I understand what you did. Also while you’re here, I just finished writing the code to toggle the button on my own, but for some reason, right when the code starts running, the light had a very light glow and I couldn’t turn it on or off through the cloud, so I tried to add a short delay to see if that works, but all it does is make the light blink at full brightness. If I hold down the button, it keeps the light off. I also added code anywhere the status of the was changed, called relayStatus, to be used in this function. I do have a resistor connected. Thank you for your help!

// Create our "shorthand" for relay on same pin as LED
int relay = D7;
int button = D6;
int relayState;
int buttonState;

void setup() {

   // Set mode to output
   pinMode(relay, OUTPUT);
   pinMode(button, INPUT);

   // Declare Particle.function to control from the cloud.
   Particle.function("light",light);
   // This is saying that when we ask the cloud for the function "led", it will employ the function ledToggle() from this app.

   // Close on start
   digitalWrite(relay, LOW);
   relayState = LOW;
}

void loop()
{
    buttonState = digitalRead(button); // Read current state of button
    
    if (buttonState == HIGH && relayState == LOW) {
        digitalWrite(relay,HIGH);
        delay(1000);
        relayState = HIGH;
    }
    
    if (buttonState == HIGH && relayState == HIGH) {
        digitalWrite(relay,LOW);
        delay(1000);
        relayState = LOW;
    }
    
}

Timer offTimer(1000, relayOff, true); // create a one-shot timer with a default period

void relayOff() {
  digitalWrite(relay, LOW);
  relayState = LOW;
}

void timedOff(int period) {
  offTimer.changePeriod(period);
  offTimer.start();
}

// function gets called when a matching API request is sent
int light(String command) {
    /* Particle.functions always take a string as an argument and return an integer.
    Since we can pass a string, it means that we can give the program commands on how the function should be used.
    In this case, telling the function "on" will turn the LED on and telling it "off" will turn the LED off.
    Then, the function returns a value to us to let us know what happened.
    In this case, it will return 1 for the LEDs turning on, 0 for the LEDs turning off,
    and -1 if we received a totally bogus command that didn't do anything to the LEDs.
    */

    
    if (command=="fopen") {
        digitalWrite(relay,HIGH);
        relayState = HIGH;
        return 1;
    }
    else if (command=="fclose") {
        digitalWrite(relay,LOW);
        relayState = LOW;
        return 0;
    }
    else if (command=="t5") {
        digitalWrite(relay,HIGH);
        relayState = HIGH;
        timedOff(300000);
        return 5;
    }
    else if (command=="t10") {
        digitalWrite(relay,HIGH);
        relayState = HIGH;
        timedOff(600000);
        return 10;
    }
    else if (command=="t20") {
        digitalWrite(relay,HIGH);
        relayState = HIGH;
        timedOff(1200000);
        return 20;
    }
    else if (command=="t40") {
        digitalWrite(relay,HIGH);
        relayState = HIGH;
        timedOff(2400000);
        return 40;
    }
    else {
        return -1;
    }
}

I'd not use the variable relayState the way you do there as it may create inconsistency between the actual state and the state of the variable.
You'd either drop it completely and acquire the current state via digitalRead(relay) or you set the variable first and then only ever do digitalWrite(relay, relayState).

I'd also advise against the use of D4 to D7 for a relay on Photons without epecial consideration since these pins are also used as JTAG pins during bootup resulting in indadvertent triggers due to the internal pull-resistors.
See here
https://docs.particle.io/datasheets/wi-fi/photon-datasheet/#jtag-and-swd

The pull-up on D7 explains the dim LED while pressing RESET.

BTW, not sure what exactly that means

You can't toggle the button via code :wink:

That would suggest that the relay would be toggled very rapidly giving you aproximatly 50% duty cycle. What relay are you using. Do you not hear the clicking?

When using buttons you usually only want to act on change of state to prevent things like this to happen.
And you need pull resistors (internal or external) to prevent the pin from floating while the button is not pressed.
Finally you should debounce a button.

This is a very rudimentary approach to achive all of the above

const int pinButton = D0;                           // button closes to GND
const int pinRelay  = D1;

void setup() {
  pinMode(pinButton, INPUT_PULLUP);                 // provide the opposite level to the target level of the button
  pinMode(pinRelay, OUTPUT);
  digitalWrite(pinRelay, LOW);                      // not needed as LOW is the default state 
}

void loop() {
  static int prevState = -1;                        // keep state stored between iterations
  int currState = digitalRead(pinButton);

  if (prevState == curState) return;                // do nothing while the state hasn't changed
  delay(50);                                        // lazy debounce  
  if (currState) {                                  // on button release (returning to default HIGH)
    digitalWrite(pinRelay, !digitalRead(pinRelay)); // toggle relay
  }

  prevState = currState;                            // store current state as previous state for next iteration
}