Nice graphic alarm (Pir sensors) visualization in HABPanel

You are right @ysc , it works without it. I didn’t know that! I will update the code above.

Thanks for posting this great tutorial. I’m on my phone right now so it’s hard to do a full review of the rule but I do notice some things with a quick scan:

  • done cancel a Timer from inside the Timer body. If the body is running then it is already cancelled. The line end up doing nothing.

  • similarly, it makes no sense I have an if statement inside the body of the timer to test if the timer is cancelled or null immediately after cancelling and setting the timer to null. You don’t need the if because you already know it’s cancelled and null.

  • have a look at the cascading timers design pattern for a more flexible way to do this, though in this case I think I would implement it with Expire Based Timers.

If you combined the Expire Based Timers DP and Associated Items DP and take advantage of the new triggeringItem implicit variable you can not only reduce this down to one Rule for all PIRS but reduce the lines of coffee and complexity of that rule by more than half.

Note I’m just typing this in from memory on my phone. It will be riddled with errors. It is meant as a guide, not workable code.

Group gPirTimers
String Pir01 "PIR1 [%s]" <dmotion> (gPirs, gPirsOutside)
Switch Pir01_Timer (gPirTimers)
 {expire="5s,command=OFF"}
String Pir02 "PIR2 [%s]" <dmotion> (gPirs, gPirsOutside)
Switch Pir02_Timer  (gPirTimers) {expire="5s,command=OFF"}
rule "Pir on"
when
    Item Pir01_raw received update 1 or
    Item Pir02_raw received update 1
then
    val pir = gPirs.members.findFirst[p| p.name == triggering item.name.split("_").get(1)]
    val timer = gPirTimers.members.findFirst[t| t.name = pir.name+"_Timer"]
    pir.postUpdate("red")
    timer.sendCommand(ON)
end

rule "PIR Timer"
when
    Item Pir01_Timer received command OFF or
    Item Pir02_Timer received command OFF
then
    val pir = gPirs.members.findFirst[p| p.name == triggering item.name.split("_").get(0)]

    var color = "off"
    switch(pir.state.toString){
        case "red": color = "orange"
        case "orange": color = "orangelight"
        case "orangelight": color = "yellow"
        case "yellow" : color = "off"
    }

    pir.postUpdate(color)
    if(color != "off") triggeringItem.sendCommand(ON)
end

For now you have to add each new PIR as a trigger to the first rule and each timer as a trigger to the second. Soon we will be able to trigger of if a group I hope.

1 Like

Hi @rlkoshak, that logic is brilliant! And written on a mobile? :slight_smile: I knew already that you are the rules master.

I implemented it by adding new timer items, corrected minor typos as Visual Studio shown, ending up with this rule:

rule "Pir on"
when
    Item Pir01_raw received update 1 or
    Item Pir02_raw received update 1
then
    val pir = gPirs.members.findFirst[p| p.name == triggeringItem.name.split("_").get(1)]
    logInfo("alarm.rules", "triggeringItem, name: ", triggeringItem.name)
    logInfo("alarm.rules", "Pir name: " + pir)
    
    val timer = gPirTimers.members.findFirst[t| t.name == pir.name+"_Timer"]
    logInfo("alarm.rules", "Timer name: " + timer)
    pir.postUpdate("red")
    timer.sendCommand(ON)
end

However, still not working as expected… the log showing this:

2018-01-07 20:42:27.929 [INFO ] [e.smarthome.model.script.alarm.rules] - triggeringItem, name: 
2018-01-07 20:42:27.934 [INFO ] [e.smarthome.model.script.alarm.rules] - Pir name: null
2018-01-07 20:42:27.943 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Pir on': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null

So it seems the rule is somehow not getting the triggeringItem info I think. Tried to find more info but this feature (triggeringItem) seems to be added quite recently and I couldn’t find much more info… Do you have an idea what’s wrong?

Are you running 2.2 release? I think triggeringItem was added shortly before the release so if you are still on 2.1 or 2.2 snapshot you may not have it.

It does indeed appear that triggeringItem is not being populated.

It should work as written based on my understanding of how it is supposed to work. However, maybe it doesn’t like the 1.

Does:

rule "Pir on"
when
    Item Pir01_raw received update or
    Item Pir02_raw received update
then
    if(triggeringItem.state as Number != 1) return;

    val pir = gPirs.members.findFirst[p| p.name == triggeringItem.name.split("_").get(1)]
    logInfo("alarm.rules", "triggeringItem, name: ", triggeringItem.name)
    logInfo("alarm.rules", "Pir name: " + pir)
    
    val timer = gPirTimers.members.findFirst[t| t.name == pir.name+"_Timer"]
    logInfo("alarm.rules", "Timer name: " + timer)
    pir.postUpdate("red")
    timer.sendCommand(ON)
end

Does whatever populates Pir01_raw only update the Item or does it receive a command?

The Group and persistence hack would work here as well in a pinch but we shouldn’t have to resort to that anymore.

What is interesting is triggeringItem is not null, It just doesn’t have the name field filled out.

Log out triggeringItem.state. Maybe the name doesn’t get populated, in which case I need to file an issue.

Yes, running 2.2.0 Release Build
image

Pir01_raw receives update from MQTT broker via MQTT binding so no command.

Number Pir01_raw "PIR1 [%s]" <dmotion> (gPirs, gPirsOutside) {mqtt="<[mosquitto:Garden/Alarm/Pir01:state:default]"} //receiving 0 (no motion detected) or 1 (motion detected)

This is log when tried to get only the .state instead of .name

2018-01-08 19:04:58.323 [INFO ] [e.smarthome.model.script.alarm.rules] - triggeringItem.state: 
2018-01-08 19:04:58.972 [INFO ] [e.smarthome.model.script.alarm.rules] - triggeringItem.state: 
2018-01-08 19:04:58.994 [INFO ] [e.smarthome.model.script.alarm.rules] - triggeringItem, name: 
2018-01-08 19:04:59.000 [INFO ] [e.smarthome.model.script.alarm.rules] - Pir name: null
2018-01-08 19:04:59.020 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Pir on': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null

Additional info appeared in log when used your last rule:

2018-01-08 18:56:59.042 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.2018-01-08T18:56:59.037+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  <XFeatureCallImplCustom>.cancel
  <null>.TimerPir01 = <XNullLiteralImplCustom>
  <XFeatureCallImplCustom>.postUpdate(<XStringLiteralImpl>)
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@8e43dd
} ] threw an unhandled Exception: 
java.lang.NullPointerException: null
	at org.eclipse.smarthome.model.script.engine.ScriptError.<init>(ScriptError.java:66) [145:org.eclipse.smarthome.model.script:0.10.0.b1]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:130) [145:org.eclipse.smarthome.model.script:0.10.0.b1]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:901) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:864) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:223) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:215) [145:org.eclipse.smarthome.model.script:0.10.0.b1]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:759) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:219) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:215) [145:org.eclipse.smarthome.model.script:0.10.0.b1]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:446) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:227) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:215) [145:org.eclipse.smarthome.model.script:0.10.0.b1]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:189) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) [164:org.eclipse.xtext.xbase:2.12.0.v20170519-0752]
	at com.sun.proxy.$Proxy173.apply(Unknown Source) [?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) [145:org.eclipse.smarthome.model.script:0.10.0.b1]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [115:org.eclipse.smarthome.core.scheduler:0.10.0.b1]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [115:org.eclipse.smarthome.core.scheduler:0.10.0.b1]
2018-01-08 18:56:59.049 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.2018-01-08T18:56:59.037+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  <XFeatureCallImplCustom>.cancel
  <null>.TimerPir01 = <XNullLiteralImplCustom>
  <XFeatureCallImplCustom>.postUpdate(<XStringLiteralImpl>)
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@8e43dd
} ] threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [115:org.eclipse.smarthome.core.scheduler:0.10.0.b1]
	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [115:org.eclipse.smarthome.core.scheduler:0.10.0.b1]
Caused by: java.lang.NullPointerException
	at org.eclipse.smarthome.model.script.engine.ScriptError.<init>(ScriptError.java:66) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:130) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:901) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:864) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:223) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:759) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:219) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:446) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:215) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:189) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
	at com.sun.proxy.$Proxy173.apply(Unknown Source) ~[?:?]
	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]
	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]
	... 1 more
2018-01-08 18:57:00.480 [INFO ] [e.smarthome.model.script.alarm.rules] - triggeringItem, name: 
2018-01-08 18:57:00.484 [INFO ] [e.smarthome.model.script.alarm.rules] - Pir name: null
2018-01-08 18:57:00.490 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Pir on': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null
2018-01-08 18:57:05.902 [INFO ] [e.smarthome.model.script.alarm.rules] - triggeringItem, name: 
2018-01-08 18:57:05.908 [INFO ] [e.smarthome.model.script.alarm.rules] - Pir name: null

Maybe I could do a quick test with another Item and Item type to see if the problem is syntax, item type/value or the function itself. Is there any working example that I could apply and test?

If you change it to mosquitto:Garden/Alarm/Pir01:command:default it will be a command instead of an update.

That can be something else to try. You would change your trigger to received command and then you can use the implicit variable receivedCommand

if(receivedCommand != 1) return;

That doesn’t address why triggeringItem appears empty (not null which is interesting) but maybe it works better for command triggered Rules.

It is hard to say whether these errors are related to the rule or not. Typically Quartz errors happen inside Timers. Since we are not using Timers in these rules…

@5iver do you have any ideas or know someone who might have an idea?

1 Like

So I changed my item accordingly as well as the rule and no change… in log:

2018-01-08 21:12:54.278 [INFO ] [e.smarthome.model.script.alarm.rules] - receivedCommand: 
2018-01-08 21:12:54.312 [INFO ] [e.smarthome.model.script.alarm.rules] - triggeringItem, name: 
2018-01-08 21:12:54.320 [INFO ] [e.smarthome.model.script.alarm.rules] - Pir name: null
2018-01-08 21:12:54.333 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Pir on': cannot invoke method public abstract java.lang.String org.eclipse.smarthome.core.items.Item.getName() on null

val pir = gPirs.members.findFirst[p| p.name == triggeringItem.name.split("_").get(1)]

This one is easy to spot with a fresh set of eyes… it looks like pir will always be null, because triggeringItem.name.split("_").get(1) == "raw", which is where the error is coming from (probably a phone keyboard typo :wink:) . The logInfo lines in the rule are also not formatted properly.

@FrankCZ, see if this works for you…

rule "Pir on"
when
    Item Pir01_raw received update 1 or
    Item Pir02_raw received update 1
then
    val pir = gPirs.members.findFirst[p| p.name == triggeringItem.name.split("_").get(0)]
    logInfo("alarm.rules", "triggeringItem.name=[{}]",triggeringItem.name)
    logInfo("alarm.rules", "pir.name=[{}]",pir.name)
    
    val timer = gPirTimers.members.findFirst[t| t.name == pir.name + "_Timer"]
    logInfo("alarm.rules", "timer.name=[{}]",timer.name)
    pir.postUpdate("red")
    timer.sendCommand(ON)
end
2 Likes

Works perfectly @5iver @rlkoshak! Thanks for great support. I can also confirm that both options of changing the item value = state update as well as sending command works fine and the triggeringItem works correctly. Just to make it fully clear to others, I will put here the complete enhanced and working rule.

rule "Pir on"
when
    Item Pir01_raw received update 1 or
    Item Pir02_raw received update 1
then
    val pir = gPirs.members.findFirst[p| p.name == triggeringItem.name.split("_").get(0)]
    //logInfo("alarm.rules", "triggeringItem.name=[{}]",triggeringItem.name)
    //logInfo("alarm.rules", "pir.name=[{}]",pir.name)
    
    val timer = gPirTimers.members.findFirst[t| t.name == pir.name + "_Timer"]
    //logInfo("alarm.rules", "timer.name=[{}]",timer.name)
    pir.postUpdate("red")
    timer.sendCommand(ON)
end

rule "PIR Timer"
when
    Item Pir01_Timer received command OFF or
    Item Pir02_Timer received command OFF
then
    val pir = gPirs.members.findFirst[p| p.name == triggeringItem.name.split("_").get(0)]

    var color = "off"
    switch(pir.state.toString){
        case "red": color = "orange"
        case "orange": color = "orangelight"
        case "orangelight": color = "yellow"
        case "yellow" : color = "off"
    }

    pir.postUpdate(color)
    if(color != "off") triggeringItem.sendCommand(ON)
end

Items definition:

Group gPirs
Group gPirsOutside
Group gPirsInside
Group gPirTimers
Number Pir01_raw "PIR1 [%s]" <dmotion> (gPirs, gPirsOutside) {mqtt="<[mosquitto:Garden/Alarm/Pir01:state:default]"} //receiving 0 (no motion detected) or 1 (motion detected)
Number Pir02_raw "PIR2 [%s]" <dmotion> (gPirs, gPirsOutside) {mqtt="<[mosquitto:Garden/Alarm/Pir02:state:default]"}

String Pir01 "PIR1 [%s]" <dmotion> (gPirs, gPirsOutside) 
String Pir02 "PIR2 [%s]" <dmotion> (gPirs, gPirsOutside)

Switch Pir01_Timer (gPirTimers) {expire="5s,command=OFF"}
Switch Pir02_Timer (gPirTimers) {expire="5s,command=OFF"}
2 Likes

Great stuff @FrankCZ,

I felt like playing with your example yesterday to implement the direction detection you wanted.
I’ve done it with tags - for each sensor you’ll have to put one or more tags in the format: neighbor:direction to indicate their neighbors and the direction they (the neighbors) would be facing if they were still “hot” and the sensor was triggered.

I also added new associated items for the direction (and a group).

Example:

Group gPirs
Group gPirsOutside
Group gPirsInside
Group gPirTimers
Group gPirDirections

Number Pir01_raw "PIR1 [%s]" <dmotion> (gPirs, gPirsOutside) {mqtt=...}
Number Pir02_raw "PIR2 [%s]" <dmotion> (gPirs, gPirsOutside) {mqtt=...}

String Pir01 "PIR1 [%s]" <dmotion> (gPirs, gPirsOutside) ["Pir02:north"]
String Pir02 "PIR2 [%s]" <dmotion> (gPirs, gPirsOutside) ["Pir01:south"]

String Pir01_Direction "PIR1 Direction [%s]" (gPirDirections)
String Pir02_Direction "PIR2 Direction [%s]" (gPirDirections)

Switch Pir01_Timer (gPirTimers) {expire="5s,command=OFF"}
Switch Pir02_Timer (gPirTimers) {expire="5s,command=OFF"}

In this example the tags mean:

  • Pir02 should face north if it’s not ‘off’ and Pir01 is triggered
  • Pir01 should face south if it’s not ‘off’ and Pir02 is triggered

Then the new rules, I think they are quite self-explanatory (the second one is actually optional):

rule "PIR Direction"
when
    Item Pir01 received update red or
    Item Pir02 received update red
then
    triggeringItem.tags.forEach[t| {
        //logInfo("alarm.rules", "found tag: {}", t)
        val neighbor = gPirs.members.findFirst[p| p.name == t.split(":").get(0)]
        //logInfo("alarm.rules", "neighbor {} state = {}", neighbor.name, neighbor.state)
        if (neighbor.state.toString != "off" && neighbor.state != NULL) {
            val direction = t.split(":").get(1)
            val neighborDirItem = gPirDirections.members.findFirst[d| d.name == neighbor.name + "_Direction"]
            neighborDirItem.postUpdate(direction)
        }
    }]
end

rule "PIR Reset Direction"
when
    Item Pir01 received update off or
    Item Pir02 received update off
then
    val dirItem = gPirDirections.members.findFirst[d| d.name == triggeringItem.name + "_Direction"]
    dirItem.postUpdate(NULL)
end

Then the real reason I’ve done this is, I thought it was a good real use case for the SVG floorplan approach I explained a few days ago :grin: You should definitely consider it instead of positioning your sensor by hand in HTML!

So I created a quick example (right-click the image below and save in conf/html as floorplan.svg and/or open it in Inkscape to inspect how it’s done):
floorplan5
(Sources: https://upload.wikimedia.org/wikipedia/commons/a/af/White_House_State_Floor.svg, https://upload.wikimedia.org/wikipedia/commons/e/e0/EMVCoContactlessIndicator.svg)

The trick here is simply to use an ng-class attribute to add the states of both the PirXX item and the associated direction as CSS classes!

Here’s how it looks in the editor (right pane):

Then you define the CSS rules in conf/html/floorplan.css:


.pir-icon.red {
    fill: red !important;
}
.pir-icon.orange {
    fill: darkorange !important;
}
.pir-icon.orangelight {
    fill: orange !important;
}
.pir-icon.off {
    fill: black !important;
}
.pir-icon.south {
    transform: rotate(90deg);
    transform-origin: 50% 50%;
}
.pir-icon.west {
    transform: rotate(180deg);
    transform-origin: 50% 50%;
}
.pir-icon.north {
    transform: rotate(-90deg);
    transform-origin: 50% 50%;
}

(I had to read https://css-tricks.com/transforms-on-svg-elements/ to understand the transform-origin thing, and beware of translation transform attributes applied by Inkscape, the paths of the icons needed to be combined with the Path > Combine menu, http://www.inkscapeforum.com/viewtopic.php?t=17884 helped)

Then you can put it all together in a HABPanel template like this:

<div oc-lazy-load="'/static/floorplan.css'">
  <div ng-include="'/static/floorplan.svg'"></div>
</div>

and it will behave like you expect!

Hope you find this useful - continue to share your progress!

4 Likes

@ysc That is amazing! :slight_smile: The truth is that setting the position in px is different on each device depending on resolution and even magnifier used within the browser so I planned to try the % as you suggested in the original thread.

As I don’t know CSS at all, it will take me some time to absorb and implement it, but your example seems pretty straightforward so thanks a lot for your time! I will definitely work on that! I will also read the svg floorplan tutorial. The more I learn the more I see that possibilities are almost endless :+1: I love it!

1 Like

Lurking and learning. Thanks for putting up the project as a beginner, it has encouraged me seeing all the help and interactions that took place.

Nicely done, me thinks.

Thanks all.

1 Like

Thanks for the demo!

how you have been able to solve false positives with HC-SR501?

Good question! A false positive is usually coming from a weather (sunshine etc) or a pet (dog in my case).

To somehow avoid both I designed and 3D printed a housing for the sensor including a cover for lenses so it detects only in about 5mm high horizontal space in the cover (this one). Furthermore I placed the sensor in a place where sun is usually not shining.
But I still got some false positives anyway so I decided to place two HC-SR501 not far away from each other (about 1m) instead of one. Then I am waiting for confirmation by the second PIR and only after both PIRs detected movement I trigger the alarm (or whatever you want - I use it also for a night light).

This solution removed 99,99% of false positives! Furthemore, and that is also very nice feature, you can detect not only a movement, but also a direction of the movement! Obviously depending on which PIR detecting it first and which the second. So the night light is switched on only if I am moving into the dark part, and not when I am coming from the dark part of my garden (depends on your environment of course).

yeah that could work. And have quite a lot of advantages.
But for instalation where PIR is very near to the ESP (to be very compact devices) this literally false positive detects Wifi and therefore it’s falsing every couple of seconds.

I might give up of making compact devices and actually place PIR further away of actual board, maybe try two as you are suggesting.
But incorporating it to the house design to not look like shit, it’s quite challenging

Yes, it is. I moved from ESP to Arduino MEGA with ethernet module which is, at least in my situation, much more reliable. I will post a picture of installed pirs for an idea… As bottom part of the house is basement, cables are not big issues there…

how long you have cables from PIR’s to your arduino mega? I assume you have connected multiple pirs to one controller as it’s quite pricey? :slight_smile:

Correct… Currently the longest cable is about 6m. And indeed the idea is that the Mega collects multiple sensors (not only pirs).