Location stuff - GeoCode address, Distance etc

Well, I put it all on one line for a reason. That if(condition) value1 else value2 is a special operator called the ternary operator. It must be all on one line.

If you want to break it up into separate lines:

val PointType lastLocation = null
if(triggeringItemName == "MartinsiPhone_Location"){
    lastLocation = previousState as PointType
}
else {
    lastLocation = MartinsiPhone_Location.previousState(true).state as PointType
}

Thanks for your help.

It’s still not working for me though. The error is different though:

2021-05-21 22:39:03.633 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID '110aec41c5' failed: val PointType lastLocation = null
if(triggeringItemName == "MartinsiPhone_Location")
    lastLocation = previousState as PointType
else 
    lastLocation = MartinsiPhone_Location.previousState(true).state as PointType
      
logInfo("last Location", lastLocation.toString())
        
   1. Assignment to final variable; line 3, column 89, length 12
   2. Assignment to final variable; line 6, column 142, length 12

Doh! Use var lastLocation, not val lastLocation.

Still no banana -

[ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID '110aec41c5' failed: cannot invoke method public abstract org.openhab.core.types.State org.openhab.core.persistence.HistoricItem.getState() on null
var PointType lastLocation = null
if(triggeringItemName == "MartinsiPhone_Location"){
    lastLocation = previousState as PointType
}
else {
    lastLocation = MartinsiPhone_Location.previousState(true).state as PointType
}
      
logInfo("last Location", lastLocation.toString())

OK, that’s an error from persistence. It didn’t return a result in your call to previousState. That can happen when there is no data in the database or it’s been too long since the Item changed state and it can’t go back far enough to find a value that’s different. It can also be caused when the database is down or something like that.

You’ll need to narrow down the cause of the error.

A first thing to do is see what previousState is returning by logging it out before calling state. Next is to see what’s different between previousState() and previousState(true).

I don’t think default rrd4j stores Location types.

Without the true it returns the GPS location,
with the true it returns
[ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID '110aec41c5' failed: cannot invoke method public abstract org.openhab.core.types.State org.openhab.core.persistence.HistoricItem.getState() on null

I installed MapDB to make this work.

Am I right in thinking I should keep rrd4j for most uses. eg Graphs. And just use MapDB for this?

I made two files:
mapdb.persist

// persistence strategies have a name and definition and are referred to in the "Items" section
Strategies {
        everyMinute     : "0 * * * * ?"
        everyHour       : "0 0 * * * ?"
        everyDay        : "0 0 0 * * ?"

        // if no strategy is specified for an Item entry below, the default list will be used
       default = everyChange, everyUpdate
}

/*
 * Each line in this section defines for which Item(s) which strategy(ies) should be applied.
 * You can list single items, use "*" for all items or "groupitem*" for all members of a group
 * Item (excl. the group Item itself).
 */
Items {
        MobileDevices* : strategy = everyChange
}

and rrd4j.persist

// persistence strategies have a name and definition and are referred to in the "Items" section
Strategies {
        everyMinute     : "0 * * * * ?"
        everyHour       : "0 0 * * * ?"
        everyDay        : "0 0 0 * * ?"

        // if no strategy is specified for an Item entry below, the default list will be used
       default = everyChange, everyUpdate
}

/*
 * Each line in this section defines for which Item(s) which strategy(ies) should be applied.
 * You can list single items, use "*" for all items or "groupitem*" for all members of a group
 * Item (excl. the group Item itself).
 */
Items {
        * : strategy = everyChange, everyUpdate
        SceneSelection, HouseAlarm*, AllLightGroups*, MobileDevices* : strategy = restoreOnStartup
        
    }

MapDB cannot satisfy previousState(true),it stores only one state record.
MapDB cannot provide charting, it stores only one state record.

You could use MapDB for your purpose here.
Edit the mapdb.persist file so that it never stores your Item.
(You could keep restore on startup if you wish)
Use your rule to run when the Item changes, do whatever it is you do with previousState()
and then persist the new value under the control of your rule.
That way, the results are predictable.

If you really want a proper historical record though, you will have to use some other service - influx or mysql are popular.

Well, that seems like a good place to start. What persistence service should I use?

I’ll just point out that the approach I posted that splits the rules should do what the original does workout depending on persistence at all.

That’s why I posted them. It’s pretty heavy to set up and configure a separate database just to get there previous state fire one item occasionally.

rossko57’s suggestion is probably a better approach if wanting to keep using persistence.

Yes. After exploring this a little bit now, I think you are right.
I’m going to have a go at doing this in the next few days and will update my original post when I get something working.
Thank you.

I’m working on rewriting this. I’m using Visual Studio Code with the OpenHAB plugin.

Can anyone explain why after some amount of time, some (but not all) of the rules I write in VSC get duplicated?

I think there is an open issue on this. Search the forum and github and you should find the discussion.

Thanks for your help on this. I have edited my original post with a setup that seems to be working, and hopefully covering all the points you mentioned. Particularly about using previousState.

Looking good but I still see two gotchas.

The following line will only be run when the .rule file is first loaded. It doesn’t run every time the rule is executed. Therefore is iPhoneThingStatus happens to be ONLINE when this .rules file is first loaded, it will remain ONLINE no matter what status the Thing changes to later on. And because it’s a val even if it did change status you couldn’t change the value of iPhoneThingStatus later anyway.

val iPhoneThingStatus = getThingStatusInfo("icloud:device:bc3dd7b8fb:3dfa568e").toString

To keep this in a global variable what you need to do is:

  • change val to var so iPhoneThingStatus can be updated after the .rules file is loaded
  • In rule “location_dad-Set OFFLINE” add a line to update iPhoneThingStatus with the current state, or just “OFFLINE” as all you really care about is whether it’s ONLINE or not, not the specific reason it’s offline.
  • Add another rule that triggers when the Thing changes to ONLINE and update iPhoneThingStatus to “ONLINE”

That will keep the variable iPhoneThingStatus in sync with the Thing’s actual status.

But there might be an easier way. This variable is only used in two rules and in one of them it’s just used in a log statement. So you can avoid the extra rule proposed above if you:

  • move the definition of iPhoneThingStatus to be the first line (after the log statement) in rule “location_dad-GEO”
  • In rule “location_dad-Set OFFLINE” change the log statement to just a hard coded message “iPhone is OFFLINE. Address set to OFFLINE”.

The second gotcha is in the some of the log statements. You postUpdate to MartinsiPhone_Address and then immediately read the state back on the next line in the log statement. E.g.

        MartinsiPhone_Address.postUpdate("OFFLINE")
        logInfo("Location - Dad", "iPhone is " + iPhoneThingStatus + ". Address set to " + MartinsiPhone_Address.state)

The gotcha here is MartinsiPhone_Address.postUpdate("OFFLINE") doesn’t happen instantaneously. In fact it can take quite some number of milliseconds to process. And it does that process in the background. OH doesn’t wait for MartinsiPhone_Address to actually update to “OFFLINE” before moving on to the next line of code. Consequently often you’ll be logging out the old state of MartinsiPhone_Address and not the one you just set it to because it hasn’t finished processing that update in the background yet.

The solution here is easy. You already know what you updated the Item to so just use that instead of trying to get the state back from the Item before the Item has finished processing the update.

logInfo("Location - Dad", "iPhone is " + iPhoneThingStatus + ". Address set to OFFLINE")

Except for these two gotchas which do need to be addressed, the code now is quite good and you should be proud of it. Thanks for posting and sticking it out with all the updates. I hope this will help you with future rules writing as well.

Thanks. :+1:

I took the easy way out.
Code updated in original post.

New question:

Is it possible to make something only trigger when the UI is actively open/being viewed?
I was thinking that the GeoCoding address only needs to run when someone is watching the UI. Reducing the API requests even more.

Different UI, same problem

in other words, no way to detect “just looking”.

However, all the UIs subscribe to updates events for display. There ought to be some horrible hack allowing detection of those subscriptions, allowing for “UI exists” rather than “in use”.

With OH3 new but as-yet rudimentary user management, it may be possible with some hack to detect user login. That’s a whole other topic and you might start a new topic for that.

Or perhaps do some lateral thinking - for example, some wall panels can detect when somebody is stood in front. Or you might know no-one will be looking if no-one is at home.

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.