Help with code for a Halloween prop

Hello Everyone,
I am quite new to Spark and time is running out before my Halloween prop has to be ready. I am using a Spark Relay Shield and would like to control 3 different props. Using Tinker, all three props are wired in and work fine, however, now is the time to automate it. I need help with the code to make this happen:

  1. Event will be triggered by my motion sensor (drops to 0VDC when triggered). Sensor input needs to be ignored while the below tasks happen.
  2. Turn on D0, D1, D2 (relays) 10 seconds after sensor trigger.
  3. Keep relays on for 20 seconds
  4. Reset and wait for another motion sensor trigger.

Any help would be greatly appreciated. Have to scare some people this year, all in good fun.

Alright, sounds very do-able.

A couple of questions.

  1. What is the logic level of your motion sensor? Is HIGH 3.3V or 5V?
  2. Are all your props wired to the relay’s “normally open” side?

Answer those and the code should be pretty straight forward

The motion sensor is high @ 3.3V, and yes the props are wired normally open.

We’re very excited now, I sincerely appreciate the help.

Ok. I haven’t tested the following code but it compiles. Hook the motion sensor up to D3 and flash the following code and let me know what happens.

Code: https://gist.github.com/harrisonhjones/403398d12fe8f018bbad

1 Like

Code looks good, thanks for offering a hand :slight_smile:

Skimming the code I see one timer area that could cause a problem, albeit very rarely (like every 50 - 70 days or something like that if I remember)

Instead of:
if((lastMotionTime + propDelay) < millis())

Use subtraction… the math with unsigned numbers always works out correctly even when the millis() count wraps:
if((millis() - lastMotionTime) > propDelay)


Also there is a missing digitalWrite(RELAY3, LOW); and digitalWrite(RELAY3, HIGH);

@BDub
Do you have any documentation on it “always works out”? I’m just curious on why some math works out better than other math. Is there something special going on behind the scenes with the subtraction?

Also, the gist has been updated to include the missing digitalWrite(RELAY3, HIGH/LOW) lines

@harrisonhjones the issue is that millis() increments by 1 every millisecond, but it’s a statically typed 32-bit variable, so every 4294967296 milliseconds it will “wrap” and end up back at zero. This happens roughly every 50 days.

I guess it’s not exactly just because it’s subtraction, but also the way in which you are subtracting.

Since millis() returns an unsigned long (32bit) value, we want to specifically make sure all math is done with uint32_t aka unsigned long.

If you imagine millis() counting up from 0 in milliseconds and one day in the distant future wrapping around back to 0 because it’s always counting up and overflowing eventually…

If you save lastMotionTime = millis() 2 seconds before it wraps and then the following code executes: if((lastMotionTime + propDelay) < millis()) before millis() wraps, it will immediate execute because lastMotionTime + 20000 will wrap around to a small number which will be less than the current millis() value.

If you instead use subtraction, then 2 seconds before millis() wraps if we immediately execute this code: if((millis() - lastMotionTime) > propDelay) you’ll find that a large millis() value minus lastMotionTime (which we just saved 2 seconds ago) will basically be 2000, which is not greater than propDelay until it’s 20000. But what happens after millis() wraps? surely that will be bad… nope. This is where the subtraction helps. Let’s say we are 3 seconds from when we saved lastMotionTime, so millis() equals 1000 now. With unsigned integer math, 1000 minus (that really large value representing millis() 2 seconds before it wrapped… something like 4294965295 or so) is a negative number… but since we are using unsigned it’s always positive, so it wraps around and the answer is 3000. Thus you never have a wrap error case. Actually you’re probably thinking 1000 - 4294965295 should be some really large negative number, but if it’s always positive then it’s just some really large positive number, not 3000. Well…

The really low level explanation involves how computers do math. Subtraction of two integers is really subtraction of two binary numbers. Subtraction is done by adding the 2’s compliment of the number to be subtracted, to the number being subtracted from. In the case of unsigned math, the answer is always positive. Kind of a walkthrough with signed binary math https://www.youtube.com/watch?v=vfY7bN_3VKw

Subtraction “just always works” if you subtract the previous count from the current higher count (even if that higher count has a chance of wrapping). You always get the absolute difference between the two numbers. Notice this doesn’t work the same way if you subtract the currently incrementing value from the previous count, and it also won’t work if you try to subtract the previous count from the current higher count if it has wrapped around past the previous count :wink:

I hope that makes sense! Otherwise I defer to it “just always works” because it does :smile:

1 Like

Thank you so much for the help! I will load the core this afternoon and test the setup.
Harrison, I will make BDub’s math changes if you don’t have time to change the code online.
BDub & Zach, thanks for the coding input. All of this reinforces the fact that I have some substantial learning to do. Do any of you know of a great place to start my coding voyage?

I will complete the project very soon thanks to all of your help.

1 Like

@zach & @BDub: I appreciate the response but I’m still a little confused. I actually knew (and thought I understood) about the millis() wrapping and the 2s compliment subtraction which is why I’m so confused.

Let’s say we have the following situation:
lastMotionTime = 2 seconds before overflow
4 seconds goes by. Millis() is now 2,000
the “if((lastMotionTime + propDelay) < millis())” check occurs
lastMotionTime + 10,000 should equal 8,000 correct? 8,000 is > millis() so the statement would NOT be true and the IF would not fire. It sounds like you are saying that this is not the case. I just don’t understand

Note: see http://ideone.com/iafX6u. It appears both methods work so I continue to be a little confused

Yeah you’re thinking about it right… but the problem case with your code is when you are within 10 - 20 seconds of rollover (depending on propDelay vs propOnTime) and you save lastMotionTime and then the if statement is evaluated immediately, i.e., before millis() rolls over.

I’m going to steal your text :smile:

Let’s say we have the following situation:
lastMotionTime = 9 seconds before overflow
1 second goes by. millis() is now (4294967296 - 8000)
the if((lastMotionTime + propDelay) < millis()) check occurs
lastMotionTime + 10,000 should equal (4294967296 - 9000 + 10000) which rolls over to 1000.
1,000 is < millis() which is (4294967296 - 8000) so the statement WOULD BE true.

That’s the problem… super tiny problem albeit every 50 days. I only point it out so that when you are building rockets some day… you know! :slight_smile:

Nicely done. You are correct! (obviously). I will make the change to the gist and my own libraries. Good catch.

Note, for those of you who want to “play along from home” the earlier ideone.com link I posted should go to the version in which millis() is 8 seconds before rollover and lastMotionTime is 9 seconds before which illustrates the error in my code

1 Like

Thanks everyone, the code works quite well. All I need to do now is the cosmetics.

Thanks again.

2 Likes

Again, thanks for the help. Here is a link to the youtube video.

Still need to put the electronics in an enclosure and route the EL wire for better visibility.

3 Likes

That’s awesome… and terrifying!

Help me Harrisonhjones, you’re my only hope…

This years project was working this morning. I added to your code from last year and it worked! Now it is inconsistent. As soon as the core comes on line, it immediately starts toggling the relays. The motion sensor seems fine, 0vdc with no movement, strong 5vdc with motion. I hooked it up to D7 and the led comes on momentarily with motion. Could someone tell me what is going wrong?

https://github.com/glockhaus/Halloween/blob/master/Boardshaker

I shall try! Could you setup your reply like this:

What happens:

  • List of things that happen
  • Another item
  • Yet another item

What’s supposed to happen / what’s expected

  • Different stuff here
  • Different stuff here
  • Different stuff here

That’ll make it much easier for me to get a good understanding of the issue!

using a photon on a v2 (newer) relay board.

what happens...
upon photon startup without any motion sensor input, relays D3 & D4 come on and toggle like they are supposed to. But that action is supposed to be triggered by motion. The toggling action occurs for the specified amount of time (10 secs), then immediately start toggling again. This goes on until I unplug the photon.

Expected behavior...
Once the photon is powered up, it should await motion sensor input, wait for a 1/4 second then toggle the relays on and off for 10 seconds. After that, it should return to a state awaiting motion sensor input again.

I had another post going,

but didn't hear much from anyone after that, so I thought I would charge ahead with your code from last year (modified), with some success.

When the Photon first powers up, I think all the GPIO pins are floating. So you might want to add some pulldown resistors to your relay output pins.

Not sure why it’s re-triggering after startup, though…

Well, further troubleshooting reveals that any wire (open, no sensor or load) inserted into the pin designated for the motion input will trigger a never ending run cycle. I suspect that something has happened to either the v2 shield or the photon? I have a new photon arriving tomorrow. I love that they are only $20.

Has anyone reviewed my code to rule it out as the issue. I know it is probably not as efficient as it could be, but I am learning slowly.

dougal, I will research the pulldown resistor idea. Is there a magic value or is it dependent on the load? In this case the relay is switching 12vdc.

A sincere thanks for harrisonhjones and dougal!