Trigger action when enter in a specific geographic zone

Hi!

I would like to make a flow that triggers an action (for exemple, send me a notification on my phone ) when my smartphone is detected in a specific zone.
On google maps, if I take the coordonnates of a zone represented by a square, I get x1, x2, y1 and y2 positions.

So I took an “event: state” node , I put device_tracker.oneplusseb as Entity property and I put that JSONata in the output msg.payload :

(
$entities(‘device_tracker.oneplusseb’).attributes.latitude > y1
) and (
$entities(‘device_tracker.oneplusseb’).attributes.latitude < y2
) and (
$entities(‘device_tracker.oneplusseb’).attributes.longitude > x1
) and (
$entities(‘device_tracker.oneplusseb’).attributes.longitude < x2
)? “In zone” : “Away”

I connected that node with an “call service” node wich notify the service mobile_app_oneplusseb with that Data :

{
“message”:payload,
“title”:“Specific zone”
}

But I don’t get that to work.
Could somebody help me with that ?

Well, where to start with this?!

First up, it is always good to have the flow exported. This makes it much easier to see all the various settings that could be causing an issue, to test it out, and to make changes and repost a suggested answer to help you.

If you are worried about exporting personal information such as geo-location, and as good practise anyway, you can use the scrubber to remove HA server and location data first. Export to clipboard, post to the scrubber input, scrub, copy to clipboard, post here using </> option.

The export is just JSON - so always worth sticking into a basic text editor and just looking or searching for “latitude” or your x/y values just to make sure you have removed them first.

https://zachowj.github.io/node-red-contrib-home-assistant-websocket/scrubber/

Second. What could possibly go wrong? For starters…

  • the event: state node is not triggering
  • the attribute data is incorrect
  • the entities reference is incorrect
  • the returned data and the literals y1-x2 formats don’t match up
  • the service call is not working

I would suggest that your JSONata could be improved a little. If the Event: state node is using the device_tracker.oneplusseb as the entity, then you should be able to use just $entity().attributes to access it, and the logic could be simplified a bit. Since JSONata returns nothing if there is no result, it could just be that there is no result and nothing is being returned…

As a start, I would like to suggest

  • use a current state node, set the entity to ‘device_tracker.oneplusseb’ and use an inject and debug (display complete message) to see what exactly is returned in msg.data (the entity details)
  • check that msg.data.attributes has latitude and longitude, and that they are correct, and exactly what format they are given (decimal degrees, dms…)
  • use an inject node with msg.payload set to “In zone” and make sure that your call service node is working

After that, it is probably a case of checking that moving the phone around does indeed generate a change in entity state, and that the attribute (lat/long) are correct. Then it will be down to something in the flow, perhaps the JSONata, which is difficult to diagnose remotely by conversation.

Hope this helps to get you started!

The “normal” way to do this would be to create a zone in HA, and use an “events: state” node based on that. The only difference being that this is a circular zone rather than rectangular - do you care about the difference?

Hi !

Thanks for all that good advices !

Actually, I had already did some tests with a current state node to know wich entity’s propertity give me the latitude and longitude.
With an inject and a debug node, getting those data with $entity().attributes.latitude and $entity().attributes.longitude when device_tracker.oneplusseb is set as Entity ID is OK.

My problem is that I don’t know where I have to put my condition test for the action to be performed.

When I’m testing with current state node and debug, everything is ok because I can put the following JSONata in output msg.payload and then get msg.payload in my debug node wich print Away or InZone
.

(
   $entity().attributes.latitude > 43.30700
) and (
   $entity().attributes.latitude < 43.30800
) and (
   $entity().attributes.longitude > 5.4123900
) and (
   $entity().attributes.longitude < 5.4123930
)? "InZone" : "Away"

But with an Event:state node and a Call service node, where do I put my condition ?
Do I have to put JSONata in the “If State” of the Event:state node ?
In the output msg.payload of the Event:state node ?
And how do I process the result of the test in the Data field of my Call service node to say that I have to be notify only if the information is “InZone” ?

Here is my test flow wich is ok :

[{"id":"539353e7da970795","type":"inject","z":"e4925652bd2af625","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1110,"y":540,"wires":[["c4a29767325f64d2"]]},{"id":"c4a29767325f64d2","type":"api-current-state","z":"e4925652bd2af625","name":"","server":"abcdef12.ghij34","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"device_tracker.oneplusseb","state_type":"str","blockInputOverrides":false,"outputProperties":[{"property":"test","propertyType":"msg","value":"(\t   $entity().attributes.latitude > 45.04400\t) and (\t   $entity().attributes.latitude < 45.04500\t) and (\t   $entity().attributes.longitude > < 2.48000\t) and (\t   $entity().attributes.longitude < < 2.48100\t)? \"InZone\" : \"Away\"","valueType":"jsonata"},{"property":"latitude","propertyType":"msg","value":"$entity().attributes.latitude","valueType":"jsonata"},{"property":"longitude","propertyType":"msg","value":"$entity().attributes.longitude","valueType":"jsonata"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":1380,"y":540,"wires":[["9cd6dba19522aa96","05474f6fd2ad28c6","3e5369e9010dc7f2"]]},{"id":"9cd6dba19522aa96","type":"debug","z":"e4925652bd2af625","name":"Test","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"test","targetType":"msg","statusVal":"","statusType":"auto","x":1670,"y":560,"wires":[]},{"id":"05474f6fd2ad28c6","type":"debug","z":"e4925652bd2af625","name":"Latitude","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"latitude","targetType":"msg","statusVal":"","statusType":"auto","x":1680,"y":480,"wires":[]},{"id":"3e5369e9010dc7f2","type":"debug","z":"e4925652bd2af625","name":"Longitude","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"longitude","targetType":"msg","statusVal":"","statusType":"auto","x":1680,"y":660,"wires":[]}]

And here is the flow I’m working on :

[{"id":"467f9da11e3b1abd","type":"server-state-changed","z":"e4925652bd2af625","name":"","server":"abcdef12.ghij34","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"device_tracker.oneplusseb","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"(\t   $entity().attributes.latitude > 45.04300\t) and (\t   $entity().attributes.latitude < 45.04400\t) and (\t   $entity().attributes.longitude > 2.48000\t) and (\t   $entity().attributes.longitude < 2.48100\t)? \"InZone\" : \"Away\"","valueType":"jsonata"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":1160,"y":320,"wires":[["c67315962ddbfce7"]]},{"id":"c67315962ddbfce7","type":"api-call-service","z":"e4925652bd2af625","name":"","server":"abcdef12.ghij34","version":5,"debugenabled":false,"domain":"notify","service":"mobile_app_oeplusw4","areaId":[],"deviceId":[],"entityId":[],"data":"{\t\"message\":payload,\t   \"title\":\"Specific zone\"\t}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1580,"y":320,"wires":[[]]}]

Hi !

Thanks for the tips, I’m new in HA and didn’t know it was possible to do that.
I’m going to look at this :+1:

Anyway, for my own Node-red improvment, I also would like to make it work as I’m trying to (if it is possible of course :sweat_smile:)

You look as though you have got everything working.

To try and answer your questions:

You are using the Event: state node to trigger when the device_tracker entity changes state (or changes attribute - you might need to untick the ‘current state equals previous state’ to get the node to fire when the latitude / longitude changes but the state does not).

Answer 1.
You have the JSONata test in the right place - this generates your ‘InZone’ or ‘Away’ outcome, using the device_tracker entity information, and places it into msg.payload so it will be available after this node.

As a suggestion, I have included the modified JSONata that should do the same as your code, but perhaps a little more efficiently. I can’t fully test this, but the ‘and’ in JSONata does not need ( ), and by using variables I can reduce the $entity() function call to just once.

(
    $geo:=$entity().attributes;
    $lat:=$geo.latitude;
    $lon:=$geo.longitude;

    $zone:= $lat > 45.043 and $lat < 45.044 and $lon > 2.48 and $lon <2.481;
    $zone ? "InZone" : "Away"
)

You don’t need to do anything more with this node.

If your action condition is “call the service when InZone only” then this needs to go as a Switch node in between the Events: state and the Call Service node. You can simply test on msg.payload = “InZone” and this will pass the message only when you are ‘in zone’.

No. Any tests here will only test the incoming state of the device_tracker, and is not related to your output msg.payload and the JSONata you are using there.

Answer 2.

Yes, you can do both the ‘zone test’ and the ‘action condition’ in one go.

This is ‘more efficient’ but perhaps more tricky to understand. It should work - I have modified your flow to give you both solutions. Take your pick.

The second approach is to run a JSONata test for the ‘InZone’ directly in the If State condition, using JSONata to return a Boolean result. True if ‘inzone’ and false if not. This means, when in zone, the state change message will appear at the top of the two outputs.

This approach does not give you the result of the InZone test in your output, so you can set msg.payload to “InZone”, or you could just set up your call service node to respond to the message when it arrives.

[{"id":"467f9da11e3b1abd","type":"server-state-changed","z":"383f140bd7a133e3","name":"","server":"","version":5,"outputs":1,"exposeAsEntityConfig":"","entityId":"device_tracker.oneplusseb","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"(\t    $geo:=$entity().attributes;\t    $lat:=$geo.latitude;\t    $lon:=$geo.longitude;\t\t    $zone:= $lat > 45.043 and $lat < 45.044 and $lon > 2.48 and $lon <2.481;\t    $zone ? \"InZone\" : \"Away\"\t)","valueType":"jsonata"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":340,"y":1260,"wires":[["d7895e002d8f6548"]]},{"id":"c67315962ddbfce7","type":"api-call-service","z":"383f140bd7a133e3","name":"","server":"","version":5,"debugenabled":false,"domain":"notify","service":"mobile_app_oeplusw4","areaId":[],"deviceId":[],"entityId":[],"data":"{\t\"message\":payload,\t   \"title\":\"Specific zone\"\t}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":840,"y":1260,"wires":[[]]},{"id":"d7895e002d8f6548","type":"switch","z":"383f140bd7a133e3","name":"InZone only","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"InZone","vt":"str"}],"checkall":"false","repair":false,"outputs":1,"x":610,"y":1260,"wires":[["c67315962ddbfce7"]]},{"id":"48db3c989c934660","type":"server-state-changed","z":"383f140bd7a133e3","name":"","server":"","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":"device_tracker.oneplusseb","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"(\t    $geo:=$entity().attributes;\t    $lat:=$geo.latitude;\t    $lon:=$geo.longitude;\t    \t    $lat > 45.043 and $lat < 45.044 and $lon > 2.48 and $lon <2.481;\t)","ifStateType":"jsonata","ifStateOperator":"jsonata","outputOnlyOnStateChange":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"\"InZone\"","valueType":"str"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":340,"y":1340,"wires":[["967a9266c69a4637"],[]]},{"id":"967a9266c69a4637","type":"api-call-service","z":"383f140bd7a133e3","name":"","server":"","version":5,"debugenabled":false,"domain":"notify","service":"mobile_app_oeplusw4","areaId":[],"deviceId":[],"entityId":[],"data":"{\t\"message\":payload,\t   \"title\":\"Specific zone\"\t}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":840,"y":1340,"wires":[[]]}]

I hope this helps!