Continuous Door Open Reminder with Close Confirmation


#1

I have set the below piston in order to get notified when my front door remains open for specific duration and continues to do so, at the same duration interval, until the door is closed. The notifications when the door is opened are generated properly. However, the one at the end to confirm the door is closed is sent twice. It seems to me per the event logs I attached below, that while the “while” task is getting cancelling, due to the change of event state, a new instance of the piston is triggered at the time for some unknown reason, causing the duplicate notifications.

For testing purpose, I swapped my door contact sensor with my door lock, so I don’t have to keep opening and closing my door. I also decreased the duration value.

Piston

Event Logs
10/22/2017, 4:03:33 PM +808ms
+1ms ╔Received event [House Door].lock = locked with a delay of 1219ms
+153ms ║RunTime Analysis CS > 17ms > PS > 24ms > PE > 112ms > CE
+163ms ║Runtime (38762 bytes) successfully initialized in 24ms (v0.2.0fa.20171011) (161ms)
+164ms ║╔Execution stage started
+172ms ║║Comparison (enum) locked is (string) unlocked = false (1ms)
+173ms ║║Condition #6 evaluated false (5ms)
+174ms ║║Condition group #1 evaluated false (state did not change) (6ms)
+186ms ║║Calculating (long) 94433 / (long) 1000 >> (long) 94.433
+189ms ║║Comparison (integer) 30 is_less_than (decimal) 94 = true (1ms)
+190ms ║║Condition #14 evaluated true (13ms)
+190ms ║║Condition group #11 evaluated true (state did not change) (14ms)
+192ms ║║Cancelling statement #12’s schedules…
+211ms ║║Executed virtual command sendPushNotification (7ms)
+213ms ║╚Execution stage complete. (50ms)
+244ms ╚Event processed successfully (244ms)
10/22/2017, 4:03:32 PM +841ms
+2ms ╔Received event [House Door].lock = locked with a delay of 87ms
+281ms ║RunTime Analysis CS > 13ms > PS > 25ms > PE > 243ms > CE
+296ms ║Runtime (38764 bytes) successfully initialized in 25ms (v0.2.0fa.20171011) (294ms)
+297ms ║╔Execution stage started
+307ms ║║Comparison (enum) locked is (string) unlocked = false (1ms)
+308ms ║║Cancelling condition #6’s schedules…
+309ms ║║Condition #6 evaluated false (6ms)
+310ms ║║Cancelling condition #1’s schedules…
+311ms ║║Condition group #1 evaluated false (state changed) (7ms)
+327ms ║║Calculating (long) 93607 / (long) 1000 >> (long) 93.607
+329ms ║║Comparison (integer) 30 is_less_than (decimal) 93 = true (1ms)
+330ms ║║Condition #14 evaluated true (17ms)
+331ms ║║Condition group #11 evaluated true (state did not change) (18ms)
+333ms ║║Cancelling statement #12’s schedules…
+344ms ║║Executed virtual command sendPushNotification (8ms)
+346ms ║╚Execution stage complete. (50ms)
+354ms ╚Event processed successfully (354ms)
10/22/2017, 4:03:26 PM +137ms
+0ms ╔Received event [Home].time = 1508702607333 with a delay of -1197ms
+254ms ║RunTime Analysis CS > 22ms > PS > 50ms > PE > 183ms > CE
+267ms ║Runtime (38779 bytes) successfully initialized in 50ms (v0.2.0fa.20171011) (265ms)
+268ms ║╔Execution stage started
+293ms ║║Calculating (string) The house door has been open for + (string) 1 minute and 26 seconds >> (string) The house door has been open for 1 minute and 26 seconds
+296ms ║║Calculating (string) The house door has been open for 1 minute and 26 seconds + (string) . >> (string) The house door has been open for 1 minute and 26 seconds.
+311ms ║║Executed virtual command sendPushNotification (10ms)
+325ms ║║Comparison (enum) unlocked is (string) unlocked = true (1ms)
+327ms ║║Condition #6 evaluated true (13ms)
+328ms ║║Condition group #1 evaluated true (state did not change) (15ms)
+331ms ║║Cancelling statement #2’s schedules…
+335ms ║║Executed virtual command wait (0ms)
+337ms ║║Requesting a wake up for Sun, Oct 22 2017 @ 4:03:56 PM EDT (in 30.0s)
+343ms ║╚Execution stage complete. (75ms)
+345ms ║Setting up scheduled job for Sun, Oct 22 2017 @ 4:03:56 PM EDT (in 29.994s)
+378ms ╚Event processed successfully (378ms)


#2

The piston is going to reevaluate on any event from Lock 1, lock or unlock, so when the door is locked that causes the piston to execute a second time.

For a single door, I would probably use an ON EVENT statement for Lock 1 and use an if/then to take action based on whether the event was the door being locked or unlocked. Just understand that the piston can reevaluate if the status of Lock 1 changes while any wait statements are pending.

I ran into a similar issue while developing my Door Left Open Reminder piston. Perhaps that example will give you some ideas.


#3

@bthrock Thanks for your clarification on event reevaluation.

I was able to fix my problem by moving the if statement at the end related to the door close notification into the when false clause of the while loop. That way, it is only triggered on the second run for the lock event or in my case for the contact close event. Here is the updated piston I am now using with the proper door contact sensor and duration in minutes.


#4

@jesthings

I am just starting out with Webcore and learning here so please excuse my novice question.

Why did you use: “round(previousAge([XXX Door : contact])/60000)” ?


#5

{duration} is an integer variable and represents the number of minutes he wants to wait before getting a notification that the door in open.

previousAge is reported in milliseconds, so he converts that to minutes by dividing by 1000 (seconds per milliseconds) x 60 (seconds per minute). That will produce a decimal result, so rounds it to get to the nearest minute before comparing it to {duration}.


#6

Thanks @bthrock for the explanation.

@inspron; I actually made some changes to that piston since then by adding an additional condition on the lock as my door contact sensor sometimes stays in open state while it is actually closed causing unnecessary notifications.

I also switched the time conversion to the duration variable as the number of minutes would be rounded down creating some edge cases. I could have used the ceil function to always round up but decided to convert everything to milliseconds so there is no need to round any numbers.

If you are wondering about the $currentEventDevice variable, I used it in each false condition to ensure that the relevant actions are only triggered when that given device received an event preventing any duplicate actions from being generated.


#7

I used your code to finally get a working Alexa announcement when my door is left open for 2 minutes.

The only thing is the first part of the code, the false part that says “thank you”, never works. I’m okay if it doesn’t. But just would like to understand why it doesn’t. Also is there a way to have it repeat like only once or twice, or not at all?

Also, I read the other info in the thread but I still don’t really understand the purpose of what the 6000 does in the previous age code.


#8

Correction, it does say Thank you now.


#9

I’ll tackle your last question first. previousAge() provides a result in milliseconds. Divide that to convert to seconds, and that by 60 to get to minutes. You could avoid the extra math and set duration in milliseconds, but most people find minutes or seconds easier to work with.

As for the piston, I would have used a very different approach then you’ve employed, but that doesn’t make it wrong, especially if it works! Nevertheless, just for your future consideration, here are my thoughts.

As used here, the WHILE loop is just a substitute for a more straightforward IF/THEN statement. The piston is going run once when the door opened, then stop. It will run again just once when the door is closed. The state of the door is evaluated only when the contact changes; there’s no loop action. So the “while” is somewhat misleading and/or misused here.

In addition, you’re using a condition (“the door is open”) rather than a trigger (“the door opened”). It isn’t wrong, as in the absence of any triggers webCoRE forces the condition to act as a trigger, but it isn’t necessarily best practices either.

Lastly, the ‘when false’ construction is something of a relic that was carried over from the original Core, but is rarely seen now that WebCoRE allows multiple if statements. I don’t use it, ever, but that’s just my preference as I think IF/THEN statements and restrictions are easier for me and others to understand.

The following piston should accomplish the same thing.

When the door is opened, the trigger will be true and the piston will start a wait of {duration} minutes. If the door is closed before the wait is complete, the IF will evaluate as false, the wait will be cancelled and the ELSE section will be executed. If the wait has already passed, the IF will still evaluate as false and the ELSE section will be executed.

Hope this helps.


#10

Excellent advice and wording @bthrock… This could easily be my “signature” here in the forums, LOL


#11

So with the changes related to the device event history that rolled out about a month ago, I now get a notification every time the door is closed no matter if the door was opened more than 10 minutes or not. This is due in part to the change of behavior of the previousAge function used to determine when the door was first opened.

My understanding is that a running piston only keeps track of the last device event history that occurred prior to its start. This is why that function is now returning the last time it was closed and not when it was last opened.

I am trying to determine how to update this piston. At first glance, I could have a variable to track the piston start time and use it in place of the previousAge function. Another idea would be to remove the while loop and restart the piston each time the open notification reminder is triggered.

Piston

Event Logs (Only included Contact Sensor 1 events)

12/30/2020, 3:23:42 PM +652ms
+2ms	╔Received event [Contact Sensor 1].contact = closed with a delay of 5762ms
+185ms	║RunTime Analysis CS > 131ms > PS > 6ms > PE > 47ms > CE
+188ms	║Runtime (41748 bytes) successfully initialized in 6ms (v0.3.110.20191009) (185ms)
+189ms	║╔Execution stage started
+195ms	║║Comparison (enum) closed is (string) open = false (1ms)
+196ms	║║Cancelling condition #6's schedules...
+201ms	║║Comparison (dynamic) Contact Sensor 1 is_equal_to (dynamic) Contact Sensor 1 = true (1ms)
+202ms	║║Cancelling condition #15's schedules...
+203ms	║║Condition #15 evaluated true (5ms)
+258ms	║║Calculating (integer) 10 * (integer) 60 >> (integer) 600
+261ms	║║Calculating (integer) 600 * (integer) 1000 >> (integer) 600000
+263ms	║║Comparison (long) 2812907 is_greater_than (integer) 600000 = true (1ms)
+265ms	║║Condition #10 evaluated true (60ms)
+266ms	║║Cancelling condition #7's schedules...
+267ms	║║Condition group #7 evaluated true (state changed) (68ms)
+269ms	║║Cancelling statement #8's schedules...
+302ms	║║Executed virtual command sendPushNotification (30ms)
+304ms	║║Condition #6 evaluated false (112ms)
+305ms	║║Cancelling condition #1's schedules...
+305ms	║║Condition group #1 evaluated false (state changed) (114ms)
+307ms	║╚Execution stage complete. (119ms)
+308ms	╚Event processed successfully (308ms)

12/30/2020, 3:23:32 PM +268ms
+1ms	╔Received event [Contact Sensor 1].contact = open with a delay of 177ms
+294ms	║RunTime Analysis CS > 211ms > PS > 6ms > PE > 76ms > CE
+297ms	║Runtime (41749 bytes) successfully initialized in 6ms (v0.3.110.20191009) (296ms)
+297ms	║╔Execution stage started
+304ms	║║Comparison (enum) open is (string) open = true (1ms)
+305ms	║║Cancelling condition #6's schedules...
+306ms	║║Condition #6 evaluated true (5ms)
+314ms	║║Comparison (enum) locked is_not (string) locked = false (1ms)
+316ms	║║Cancelling condition #11's schedules...
+321ms	║║Comparison (dynamic) Contact Sensor 1 is_equal_to (dynamic) Lock 1 = false (1ms)
+322ms	║║Condition #17 evaluated false (4ms)
+323ms	║║Condition group #16 evaluated false (state did not change) (6ms)
+324ms	║║Condition #11 evaluated false (17ms)
+326ms	║║Condition group #1 evaluated false (state did not change) (24ms)
+327ms	║╚Execution stage complete. (30ms)
+328ms	╚Event processed successfully (328ms)

12/30/2020, 2:36:50 PM +769ms
+1ms	╔Received event [Contact Sensor 1].contact = closed with a delay of 68ms
+67ms	║RunTime Analysis CS > 16ms > PS > 4ms > PE > 47ms > CE
+69ms	║Runtime (41749 bytes) successfully initialized in 4ms (v0.3.110.20191009) (67ms)
+70ms	║╔Execution stage started
+78ms	║║Comparison (enum) closed is (string) open = false (1ms)
+80ms	║║Cancelling condition #6's schedules...
+86ms	║║Comparison (dynamic) Contact Sensor 1 is_equal_to (dynamic) Contact Sensor 1 = true (1ms)
+88ms	║║Condition #15 evaluated true (6ms)
+124ms	║║Calculating (integer) 10 * (integer) 60 >> (integer) 600
+127ms	║║Calculating (integer) 600 * (integer) 1000 >> (integer) 600000
+130ms	║║Comparison (long) 5823889 is_greater_than (integer) 600000 = true (2ms)
+131ms	║║Condition #10 evaluated true (42ms)
+132ms	║║Condition group #7 evaluated true (state did not change) (51ms)
+135ms	║║Cancelling statement #8's schedules...
+147ms	║║Executed virtual command sendPushNotification (9ms)
+149ms	║║Condition #6 evaluated false (74ms)
+150ms	║║Cancelling condition #1's schedules...
+151ms	║║Condition group #1 evaluated false (state changed) (76ms)
+153ms	║╚Execution stage complete. (82ms)
+154ms	╚Event processed successfully (153ms)