Author Topic: Store a previous variable value in another (new) variable?  (Read 770 times)

Offline parkerc

  • Beta Testers
  • Sr. Hero Member
  • *****
  • Posts: 2473
  • Karma: +35/-48
  • Life Moves Pretty Fast....
Store a previous variable value in another (new) variable?
« on: April 09, 2018, 07:32:37 am »
Hi

What is the best way to capture and  store a previous value of a variable when it about to be changed?

The specific example Im thinking is for a motion/door sensor - so I can see/calculate the amount of time that has passed since the door was last opened

My thought was to use luup.variable_watch, to look out for when the door trips and quickly capture the old variable first (before being its overwritten with the new) writing the old one to a new PreviousLastTrip variable 

Initial tests suggest the timing of luup.variable watch maybe is too slow, as the same (new) ?LastTrip? value for the Sensor is picked up and written to the proposed new ?PreviousLastTrip? variable. (Making them both the same)

Is there away to tell Vera before you change a particular variable, run a piece of code first, so the previous variable value is not lost  ?

« Last Edit: April 09, 2018, 07:35:41 am by parkerc »

Offline parkerc

  • Beta Testers
  • Sr. Hero Member
  • *****
  • Posts: 2473
  • Karma: +35/-48
  • Life Moves Pretty Fast....
Re: Store a previous variable value in another (new) variable?
« Reply #1 on: April 09, 2018, 08:14:24 am »
Here is my current code, which is not fast enough to get the original/previous value .

I?m hoping for any advice on how best to do this.

Code: [Select]
luup.variable_watch("dooropened","urn:micasaverde-com:serviceId:SecuritySensor1","Tripped",338)

function dooropened()

local lasttripy = luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1","LastTrip",338)
print (lasttripy)

local lasttimestamp = luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1","Timestamp",338)
print (lasttimestamp)

luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousLastTrip",lasttripy,338)
luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousTimestamp",lasttimestamp,338)
end

dooropened()
« Last Edit: April 09, 2018, 08:20:03 am by parkerc »

Offline rigpapa

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 759
  • Karma: +115/-1
Re: Store a previous variable value in another (new) variable?
« Reply #2 on: April 09, 2018, 08:37:29 am »
The watch callback is actually defined as a function with five arguments. They are: device number, SID, variable name, old value and new value. The old value is what you want to store; it's the value before the change takes effect.

function watchCallback( devNum, variableSID, varName, oldVal, newVal )

If you want to save LastTrip, then watch LastTrip. Don't assume that watching Tripped will get you LastTrip before it changes. But if you watch LastTrip explicitly, and save its old value as passed to the callback, you should be good.
Author of Reactor, DelayLight, SiteSensor, Rachio, Deus Ex Machina II, Intesis WMP Gateway, Auto Virtual Thermostat and VirtualSensor plugins. Vera Plus w/100+ Z-wave devices. Vera3 sandbox.

Offline akbooer

  • Beta Testers
  • Master Member
  • *****
  • Posts: 6203
  • Karma: +276/-70
  • "Less is more"
Re: Store a previous variable value in another (new) variable?
« Reply #3 on: April 09, 2018, 08:55:02 am »
Yes, indeed.  The documentation for all luup callbacks is here, for reference:

http://wiki.micasaverde.com/index.php/Luup_Declarations
3x Vera Lite-UI5/Edge-UI7, 25x Fibaro, 23x TKB, 9x MiniMote, 2x NorthQ Power, 2x Netatmo, 1x Foscam FI9831P, 9x Philips Hue,
Razberry, MySensors Arduino, HomeWave, AltUI, AltHue, DataYours, Grafana, openLuup, ZWay, ZeroBrane Studio.

Offline parkerc

  • Beta Testers
  • Sr. Hero Member
  • *****
  • Posts: 2473
  • Karma: +35/-48
  • Life Moves Pretty Fast....
Re: Store a previous variable value in another (new) variable?
« Reply #4 on: April 09, 2018, 10:53:16 am »
Many thanks guys.

Sorry to ask, but how does a callback fit into my original code, I can change the watched variable to LastTrip instead, but not sure about the rest. I can see the details/instructions from the link @akbooer sent but they are clear as mud to me :(

Quote
<watch> (callback)

variables: lul_device, lul_service, lul_variable, lul_value_old, lul_value_new
return values: none
When you watch a variable with luup.variable_watch, and the variable changes, your callback is called with the information on the variable that changed.

As you suggest , if the call back is a function the luup.variable_watch can call it, so that changes things to this. Can you help me how the lul_value_old fits in, assuming that?s what I want to write to the other new variable/field.

Code: [Select]
luup.variable_watch("watchCallBack","urn:micasaverde-com:serviceId:SecuritySensor1","LastTrip",338)

function watchCallBack (lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)

local lasttripy = luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1","LastTrip",338)
   print (lasttripy)
   
   local lasttimestamp = luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1","Timestamp",338)
   print (lasttimestamp)
   
   luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousLastTrip",lasttripy,338)
   luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousTimestamp",lasttimestamp,338)
end

watchCallBack()


Offline akbooer

  • Beta Testers
  • Master Member
  • *****
  • Posts: 6203
  • Karma: +276/-70
  • "Less is more"
Re: Store a previous variable value in another (new) variable?
« Reply #5 on: April 09, 2018, 11:02:13 am »
The documentation for the variable_watch() call itself is here:

http://wiki.micasaverde.com/index.php/Luup_Lua_extensions#function:_variable_watch

You don't call the global function yourself, it's called when the watched variable changes.

As @rigpapa explains, you use the old value given to you... there's no need or purpose in doing a luup.variable_get() to query it.

Code: [Select]
function watchCallBack (lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)

  local lasttripy = lul_value_old
  print (lasttripy)

  local lasttimestamp = luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1","Timestamp",338)
  print (lasttimestamp)

  luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousLastTrip",lasttripy,338)
  luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousTimestamp",lasttimestamp,338)
end


luup.variable_watch("watchCallBack","urn:micasaverde-com:serviceId:SecuritySensor1","LastTrip",338)

Any help?
3x Vera Lite-UI5/Edge-UI7, 25x Fibaro, 23x TKB, 9x MiniMote, 2x NorthQ Power, 2x Netatmo, 1x Foscam FI9831P, 9x Philips Hue,
Razberry, MySensors Arduino, HomeWave, AltUI, AltHue, DataYours, Grafana, openLuup, ZWay, ZeroBrane Studio.

Offline rigpapa

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 759
  • Karma: +115/-1
Re: Store a previous variable value in another (new) variable?
« Reply #6 on: April 09, 2018, 11:02:37 am »
You're getting warmer.

First, don't call your callback directly at the end, that's neither necessary nor correct.

Your callback should look something more like this:

Code: [Select]
function watchCallBack (lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)
    if lul_service=="urn:micasaverde-com:serviceId:SecuritySensor1" then
        if lul_variable == "LastTrip" then
            luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousLastTrip",lul_value_old, lul_device)
        elseif lul_variable == "Timestamp" then
            luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousTimestamp",lul_value_old,lul_device)
        end
    end
end

The callback will have passed to it the name of the watched service/variable. There's one callback for everything you watch, so the callback needs to look at what it has been called for, hence the conditionals. You don't need to get the old value, because it's being given to you as a function argument. Just take it and store it.

And for this version, you also need to add a luup.variable_watch() call for Timestamp.

Edit: also, I would not use the urn:micasaverde-com:serviceId:SecuritySensor1 service for my PreviousLastTrip and PreviousTimestamp variables. The point of having the service IDs is to create separation of namespaces. Don't play inside Vera's namespaces. You can just use your own: urn:my-domain-com:serviceId:SecuritySensor1. This all but guarantees you'll never conflict with future firmware, and makes it more obvious to you and anyone else what's yours and what's Vera's.
« Last Edit: April 09, 2018, 11:08:28 am by rigpapa »
Author of Reactor, DelayLight, SiteSensor, Rachio, Deus Ex Machina II, Intesis WMP Gateway, Auto Virtual Thermostat and VirtualSensor plugins. Vera Plus w/100+ Z-wave devices. Vera3 sandbox.

Offline parkerc

  • Beta Testers
  • Sr. Hero Member
  • *****
  • Posts: 2473
  • Karma: +35/-48
  • Life Moves Pretty Fast....
Re: Store a previous variable value in another (new) variable?
« Reply #7 on: April 09, 2018, 11:28:51 am »
Sorry for the confusion, I only had the function being called at the end of my code so I could test it, rather than wait for the watched variable to change by itself (my-bad)

I think what I didn?t understand is that the function being called (by the luup.variable_watch) - can also be made to know/pull other additional values from the device being watched.

So, does the lul term mean it relates to the defined device (being called) ?
« Last Edit: April 09, 2018, 11:39:49 am by parkerc »

Offline rigpapa

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 759
  • Karma: +115/-1
Re: Store a previous variable value in another (new) variable?
« Reply #8 on: April 09, 2018, 11:45:14 am »
The arguments passed into the function (the lul's in your example) by Vera tell you what watch was triggered. You can have as many watches on as many different things as you want, but the same function gets called for all of them. You need to look at the arguments to sort out why the callback was called (e.g. what is changing), and what you're going to do about it. Make sense?
Author of Reactor, DelayLight, SiteSensor, Rachio, Deus Ex Machina II, Intesis WMP Gateway, Auto Virtual Thermostat and VirtualSensor plugins. Vera Plus w/100+ Z-wave devices. Vera3 sandbox.

Offline akbooer

  • Beta Testers
  • Master Member
  • *****
  • Posts: 6203
  • Karma: +276/-70
  • "Less is more"
Re: Store a previous variable value in another (new) variable?
« Reply #9 on: April 09, 2018, 11:57:23 am »
The arguments passed into the function (the lul's in your example) by Vera tell you what watch was triggered. You can have as many watches on as many different things as you want, but the same function gets called for all of them. You need to look at the arguments to sort out why the callback was called (e.g. what is changing), and what you're going to do about it. Make sense?

Some really good advice from @rigpapa here, but it's only really true if you choose to use the same callback function for each watched variable, it's certainly not a requirement.
3x Vera Lite-UI5/Edge-UI7, 25x Fibaro, 23x TKB, 9x MiniMote, 2x NorthQ Power, 2x Netatmo, 1x Foscam FI9831P, 9x Philips Hue,
Razberry, MySensors Arduino, HomeWave, AltUI, AltHue, DataYours, Grafana, openLuup, ZWay, ZeroBrane Studio.

Offline parkerc

  • Beta Testers
  • Sr. Hero Member
  • *****
  • Posts: 2473
  • Karma: +35/-48
  • Life Moves Pretty Fast....
Re: Store a previous variable value in another (new) variable?
« Reply #10 on: April 09, 2018, 11:58:41 am »
The arguments passed into the function (the lul's in your example) by Vera tell you what watch was triggered.

I thought you can only watch one variable (hence the luup.variable_watch name) and you make that defined variable the trigger to call the associated  function ? (Example below, looks for one variable)

Code: [Select]
luup.variable_watch("doChange123","urn:upnp-org:serviceId:TemperatureSensor1","CurrentTemperature",123)
There looks to be many more variables defined via those lul references in the function - they do not act as a trigger too run the function too -  do they ?

You can have as many watches on as many different things as you want, but the same function gets called for all of them.

Sorry, Im not sure I follow, do all uses of luup.variable_watch on Vera have to refer to the same function name ?

You need to look at the arguments to sort out why the callback was called (e.g. what is changing), and what you're going to do about it.

This is the argument of the function - correct ?

lul_device, lul_service, lul_variable, lul_value_old, lul_value_new

Make sense?

Sorry no, I think my questions above sadly show it?s not second nature to me yet   ;D
« Last Edit: April 09, 2018, 12:20:31 pm by parkerc »

Offline rigpapa

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 759
  • Karma: +115/-1
Re: Store a previous variable value in another (new) variable?
« Reply #11 on: April 09, 2018, 01:20:26 pm »
OK. Let's step WAYYYYYY back.

When you call a function, you can pass data to it that they function may need. For example, when we call luup.log() to write something to the log, we have to tell it what we want to write, so we pass it one argument (also sometimes called a parameter), which is the string we want to write:

Code: [Select]
luup.log("Hello world!")
Now, luup.log() is defined something like this (within the context of the luup global variable):

Code: [Select]
function log( message )
What this says is, I'm defining a function called log, and whatever the caller passes to me as the first argument, receive it in a variable called message. So, within the code executing inside the function (called its scope) the variable named message will be defined. If we take our original example call, that message variable will contain the string "Hello World!", and the function's code can do whatever it needs to do with it.

Now in fact, luup.log() takes two arguments, the message to be written, and a logging level, so it's actually defined like this:

Code: [Select]
function log( message, level )
In this case, we see that there is an additional argument, level. The list of arguments is comma-separated. Now, luup.log() has special code to test if that argument is passed at all, and if not, supply a default value, so most usage of luup.log() as we use it omits the level (the default value is 50). But we could call luup.log this way, and it would result in our message being output to the log at the highest error level (level 1):

Code: [Select]
luup.log( "Hello World!", 1 )
OK. Now let's go back to your callback.

Calling luup.watch_variable puts the service/variable you specify on a list of watched variables. It's not just one. It's a list. The list remembers what service/variables are being watched, and the name of the callback function to call when that one changes. A bit simplified, but that's the gist.

When a service/variable changes, Luup goes through that list, and if it finds that variable is being watched, calls the associated callback. When it calls the callback, it calls it with five arguments: the device number of the device whose service/variable changed, the service ID of the variable, the name of the variable, its current value (what it has been), and its new value (what its going to be). So, your callback function should be declared to receive all five of these variables. That's what "lul_device, lul_service, lul_variable, etc." is about. The fact that @akbooer used lul_whatever in his example and I used other names is just a reflection of style. In either case, the variables given in the function declaration are the ones that will be available inside the function. You make them special by choosing their names, but there is otherwise nothing special about the name. You can call them fred, wilma, barney, betty and bambam and you'll still get all the data (and I'm dating myself).

So, when you declare your callback like this:

Code: [Select]
function watchCallback( lul_device, lul_service, lul_variable, lul_value_old, lul_value_new )
...you will have variables in the function code named lul_device containing the device number that Luup called about, lul_service containing the service Id, lul_variable containing the variable name, lul_value_old containing the old (prior to change) value, and lul_value_new containing the new (post-change) value. If we declare our function this way:

Code: [Select]
function watchCallback( a1, a2, a3, a4, a5 )
...then we would have variables named a1, a2, a3, a4 and a5 in the function scope to use, and since a1 is first, it has the device number; a2 then has the service ID, and a3 the variable name, and a4 the old value, and a5 the new value. These variable names are horrible because they don't help you (or some future person reading your code) remember/know what the arguments are intended to mean, so this is wretched style and you should not do this, I'm merely illustrating the point that you can call these whatever you want (descriptive is better).

But regardless, all five values being passed to your callback are describing a single event, the change of one service variable that you have watched. But if you've watched many, you need to know which, so you need all five pieces of this data, all five arguments, to know which device, which service, which variable, and what values are changing. And no matter what service variable is being watched, when it changes, Luup will call your callback exactly this way.

For example, let's say you are watching the setpoint temperature on a thermostat, and the last trip time on a sensor, and both watches have been given the same callback function (we'll just call it "watchCallback") to use. When the setpoint changes from 72 to 68, Luup will, in effect, call your callback with code that looks something like this:

Code: [Select]
watchCallback( thermostatDevNum, "urn:upnp-org:serviceId:TemperatureSetpoint1", "CurrentSetpoint", 72, 68 )
And then your sensor trips, Luup will call your callback function something like this:

Code: [Select]
watchCallback( sensorDevNum, "urn:micasaverde-com:serviceId:SecuritySensor1", "Tripped", 0, 1 )
So you see, Luup changes the arguments to your callback based on what has been changed--what it is telling you about. But the callback always gets five arguments, and the first one, whatever you name it, always gets the device number, and the second always gets the service ID, and the third the variable name, etc.

You can call luup.variable_watch() as many times as you want, but logically you would only call it once per variable you want to watch. Once Luup knows to watch that variable for you, you don't need to tell it again. But you can call it for as many different service variables and devices as you need to watch. You also can use different callback fucntions for different variables and/or different devices (again, and name them as you choose), but for your simple use case, it's really not necessary.

Hmm. Is this helping at all?
Author of Reactor, DelayLight, SiteSensor, Rachio, Deus Ex Machina II, Intesis WMP Gateway, Auto Virtual Thermostat and VirtualSensor plugins. Vera Plus w/100+ Z-wave devices. Vera3 sandbox.

Offline parkerc

  • Beta Testers
  • Sr. Hero Member
  • *****
  • Posts: 2473
  • Karma: +35/-48
  • Life Moves Pretty Fast....
Re: Store a previous variable value in another (new) variable?
« Reply #12 on: April 09, 2018, 05:55:20 pm »
Hmm. Is this helping at all?


It really is, (thank you so much for spending the time to write that)


Thanks to you both, ive now got two examples to see how it work, I will load them both up to better understand the two approaches
« Last Edit: April 10, 2018, 12:18:02 pm by parkerc »

Offline parkerc

  • Beta Testers
  • Sr. Hero Member
  • *****
  • Posts: 2473
  • Karma: +35/-48
  • Life Moves Pretty Fast....
Re: Store a previous variable value in another (new) variable?
« Reply #13 on: April 10, 2018, 12:16:39 pm »
Hi

There seems to be something not quite right with the code ( Im using @akbooer?s version) as the previous time stamp is being populated with the new/updated time stamp, yet the previous LastTrip is different - so I think I can see what needs changing. (See attached)

How would I convert the lul_value_old value , so it is in the same human readable forms as original Timestamp field (%d/%m %H:%M)?

Code: [Select]
function watchCallBack (lul_device, lul_service, lul_variable, lul_value_old, lul_value_new)

  local lasttripy = lul_value_old
  print (lasttripy)

  local lasttimestamp = luup.variable_get("urn:micasaverde-com:serviceId:SecuritySensor1","Timestamp",338)
  print (lasttimestamp)

  luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousLastTrip",lasttripy,338)
  luup.variable_set("urn:micasaverde-com:serviceId:SecuritySensor1","PreviousTimestamp",lasttimestamp,338)
end

luup.variable_watch("watchCallBack","urn:micasaverde-com:serviceId:SecuritySensor1","LastTrip",338)
« Last Edit: April 10, 2018, 12:19:02 pm by parkerc »

Offline rigpapa

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 759
  • Karma: +115/-1
Re: Store a previous variable value in another (new) variable?
« Reply #14 on: April 10, 2018, 06:51:36 pm »
If you want to store the old value of Timestamp, you need to watch it as well. You need to watch both LastTrip and Timestamp, and when your callback gets called, figure out which one is changing and act accordingly. There is no guarantee as to the order in which Vera updates its variables, so unless you watch the variables you are interested in, your are all but guaranteed to miss one or the other.

To format your value before storing it, use os.date("%m-%d %H:%M:%S", oldvalue), that will return a string that you can store in your Previous service variables.
Author of Reactor, DelayLight, SiteSensor, Rachio, Deus Ex Machina II, Intesis WMP Gateway, Auto Virtual Thermostat and VirtualSensor plugins. Vera Plus w/100+ Z-wave devices. Vera3 sandbox.