Programatically changing piston subscription time


#1

1) Give a description of the problem
I have a basic lighting piston to turn on/off our exterior lights at predetermined times. Those times are calculated in another piston and saved to global variables. Basically I want to turn the lights on slightly early (and off slightly late) based on weather conditions

2) What is the expected behaviour?
The lighting piston should execute at the time calculated

3) What is happening/not happening?
It seems like the piston subscription does not get changed after the global variable is updated. I was under the impression that global variable changes are instant (save for in the piston doing the changes). But apparently that doesn’t update existing subscriptions? As such, the lighting piston executes based on the previous value in the global

**4) Post a Green Snapshot of the piston!
Time calculation:

Exterior Lighting:

In essence, I need to somehow force the Exterior Lighting piston to resubscribe or reevaluate the time after it’s calculated in the Set Globals piston. Is that possible?


#2

You are right. The scheduled time is set during execution… not during idle times…

You can add this empty block at the top of the piston.

Every day at X
    Then (this space intentionally left blank)
END IF

Ideally, X in this empty block should be a time a couple of minutes before your absolute earliest trigger


The only purpose of this block is to force the piston to run so it can recalculate the new wakeup time to match the value of the global


#3

OK, thanks. I suspected I might need to do something like that.

Would calling the lighting piston from the Set Globals/Time Calculation achieve the same result? What I am trying to avoid is duplicating logic between pistons, which is why I created the calculator one in the first place


#4

The piston that sets the global should not call the lighting piston. This is because a global is not written until the very last line has been executed, so the lighting piston will usually see the old times.


You could make a block:

IF @global changes
    Then do X
END IF

…but I would limit this to only one piston. There’s no need for 5 pistons to fire whenever that time changes


#5

Ah, duh, I knew that. Thanks, I’ll probably just go with the first recommendation.


#6

So, that didn’t work.

The earliest my @turnOnTime global can be set to is 40 minutes before sunset. The calculation is done 50 minutes before subset. So I added the the recommended block to run and do nothing 45 minutes before sunset:

Every day at 45 minutes before $sunset
do
end every

Here is the trace from the last two runs. One right after I made the code change (I guess it runs to update the next run time). You can see it scheduled the next run for 5:40, which is in fact 45 minutes before sunbset. And then the one that ran at that time, which set the next run for 6:07PM, which was the time stored in @turnOnTime before it was updated. Based on the new value in @turnOnTime, it should have ran and turned on the lights at 5:45:

10/15/2019, 5:39:59 PM +76ms
+0ms ╔Received event [PRM].time = 1571186400000 with a delay of -925ms
+160ms ║RunTime Analysis CS > 29ms > PS > 80ms > PE > 52ms > CE
+163ms ║Runtime (51078 bytes) successfully initialized in 80ms (v0.3.110.20191009) (162ms)
+164ms ║╔Execution stage started
+209ms ║╚Execution stage complete. (45ms)
+211ms ║Setting up scheduled job for Tue, Oct 15 2019 @ 6:07:00 PM PDT (in 1620.713s), with 4 more jobs pending
+217ms ╚Event processed successfully (218ms)
10/15/2019, 5:17:52 PM +412ms
+0ms ╔Starting piston... (v0.3.110.20191009)
+206ms ║╔Subscribing to devices...
+261ms ║║Subscribing to Phone - Pat's Pixel 3.presence...
+275ms ║║Subscribing to Phone - Rochele's Pixel 3.presence...
+353ms ║║Subscribing to Front Porch 1...
+355ms ║║Subscribing to Front Porch 2...
+356ms ║║Subscribing to Balcony Lights...
+357ms ║║Subscribing to Backyard String Lights...
+358ms ║║Subscribing to Back Porch Lights...
+359ms ║║Subscribing to Front Porch Lights...
+361ms ║║Subscribing to Garage Exterior Lights...
+362ms ║╚Finished subscribing (166ms)
+477ms ║Evaluating switch with values [[i:55:null:0, v:[t:string, v:Halloween, vt:string]]]
+481ms ║Comparison (string) Halloween is (string) Halloween = true (2ms)
+569ms ║Comparison (time) 62272966 is_between (time) 65220000 .. (time) 28020000 = false (10ms)
+597ms ║Setting up scheduled job for Tue, Oct 15 2019 @ 5:40:00 PM PDT (in 1326.992s), with 4 more jobs pending
+627ms ╚Piston successfully started (627ms)

#7

Just to clarify, the global must be changed before the 5:40 empty trigger runs.

The only time I have ever seen this fail is if I use super global variables.
(with two @@ in front)


#8

The global was updated at 5:35 (it was changed to 5:45). Empty trigger ran at 5:40, which should have updated the schedule for the new gonna value, but didn’t.

It’s not a super global


#9

…One quick question please:
Did you manually change the global to 5:45, or did a piston change the global?


#10

My “Set Globals” piston changed it.


#11

So I did a little debugging in a separate piston, with a separate global. Mind you, I was changing this global manually. Your last comment implied that might make a difference, but it seems that the behavior is the same. So let me know if the test is invalid

Here is a screenshot of the test piston. You can see the next scheduled runtime is 4am, which is the previous value stored in @tempGlobal

I changed the global manually at 10:46:43, when the trace started. Below is the log from that. You can see it ran fine, but the last line - it didn’t update the next scheduled run to the new value in @tempGlobal (Which should be 5pm)

10/16/2019, 10:46:43 AM +94ms
+0ms ╔Received event [PRM].:a4a1ff5e4c41075d612263cbb8e1f7fd: = @tempGlobal with a delay of 52ms
+129ms ║RunTime Analysis CS > 31ms > PS > 80ms > PE > 19ms > CE
+132ms ║Runtime (39939 bytes) successfully initialized in 80ms (v0.3.110.20191009) (130ms)
+132ms ║╔Execution stage started
+139ms ║║Comparison (time) 61200000 changes = true (0ms)
+140ms ║║Condition #3 evaluated true (4ms)
+141ms ║║Condition group #2 evaluated true (state did not change) (4ms)
+143ms ║║Cancelling statement #4's schedules...
+149ms ║║Calculating (string) Global changed to + (string) 5:00:00 PM PDT >> (string) Global changed to 5:00:00 PM PDT
+152ms ║║Global changed to 5:00:00 PM PDT
+153ms ║║Executed virtual command log (1ms)
+171ms ║╚Execution stage complete. (39ms)
+175ms ║Setting up scheduled job for Thu, Oct 17 2019 @ 4:00:00 AM PDT (in 61996.731s)
+189ms ╚Event processed successfully (189ms)

Although this ran immediately since it has the changes trigger. The behavior is the same as my Exterior Lighting piston with the empty time trigger


#12

Tried saving the global to a local variable, and scheduling based on that. No change


#13

Because the ‘every day at construct’ executes only that section of code when activated and that code cannot be run any other time, my suspicion is, once set, you can’t change it even by doing this extra block. If instead, you change you main code conditional to ‘if $time is @globaltime’ it might work but I haven’t tested it myself. Or, as I think about it, maybe your extra block needs to be if Time happens daily at 16:00 so that the rest of the code is executed to update the other every conditional.


#14

All good ideas, thanks. I added this as debug towards the end to try and see which (if any) run at the proper time.

In the meantime, I have added this (which I don’t really care for). This basic logic worked in a test piston, but it feels sloppy to me


#15

I can say with 100% confidence that this method works well:

global

My global @nextMoonEvent changes twice a day, so the empty blocks every 8 hours will always capture the new global time before the real event happens (and schedule my wakeup accordingly)


As far as changing the global manually vs programmatically…

The reason I asked is because there are a few red flags in your piston that sets the global time:

A) I think this highlight should be enclosed in parenthesis (like B & D)

B, C & D) I have never tried using a dash to invert a number to a negative number.

define variable = 20
addMinutes($sunset,-variable)

is not the same as

define variable = -20
addMinutes($sunset,variable)

(Notice the dash moved)

If I want a negative offset, I always use the second method.
I make the variable negative in a previous command, and then just call the variable like normal.


If there is a bug with your code, then the global will not be set properly, and the other piston will not react to the new time.


#16

By the way, a quick way to invert a positive variable to a negative number is:

posVar - (posVar * 2)

So, for example, 25 - 50 = -25


For the record, I always convert to a negative number before the addMinutes section


#17

I am completely positive that the global time variables are being calculated and updated properly. I have tested and logged this several times and it’s correct.

Parenthesis aren’t needed on A as there is only 1 operator so there’s no order of precedence to consider (it wouldn’t hurt, but it wouldn’t help either)

Actually those two statements would (and do) evaluate to be exactly the same. When you negate the number doesn’t matter, as long as you pass a negative number to addMinutes it will offset to before the specified time. In both of your examples you are passing -20 to the function

From the wiki:

addMinutes

Syntax

addMinutes(datetime value, integer minutes)

Returns

Returns a datetime that is minutes minutes after the value . For negative values of minutes , it returns a datetime that is minutes minutes before value .

Again, I have checked the values of the calculated times after they are calculated and the values are correct, but the lighting piston isn’t updating it’s schedule to those new values for whatever reason. Fundamentally I don’t know what the difference is between your nextMoonEven subscription and mine. They should behave the same


#18

is not the same as:
addMinutes(datetime value, integerVariable+anotherIntegerVariable)

I would write it like this:
addMinutes(datetime value, (integerVariable+anotherIntegerVariable))
so the math is done FIRST, and then there is only a single integer for the rest of the formula


This can actually be one of the toughest things to get right. Just because a log shows X, does not mean the global was written correctly. (and of course, the same piston cannot report/log any changes to the global until the NEXT execution)


At the risk of sounding like a broken record:
dash(posVar) is not treated the same as {negVar}

Do with that what you will…


#19

If you look at my piston I am saving the calculated value into a temporary variable. Which I then use to update the piston state, as well as the global. That value in the state (and after completion, in the global) is always right.

Can you substantiate this? Because my experience in webCoRE, as well as several other programming languages says otherwise. It’s a very common and simple way to negate an integer, which is what we’re dealing with. Nothing too fancy


#20

OK, no problem… I have invested enough time with this one, so I’ll let someone else chime in…