Is there a way using persistance to take the previous X timestamps and average out the times to determine when the next time it would be recorded?
What i’m trying to acheive is with my cameras, it detects a school bus passing infront of my house to pickup my kids for school. I want to ‘predict’ based on the previous days when it’s crossed to make a rough guess when it will be coming that day.
I’m not sure an average of timestamps makes any kind of sense. I’m pretty sure that epoch is stored in persistence, meaning the timestamps are basically an ever increasing number. Averaging that is going to be meaningless.
What you want is the average of the times of day, not the DateTimes. For that you’ll have to pull all the records you want to include, get the minuteOfDay for each and average that, coverting the result back into a time of day.
Exactly how to go about doing that is going to depend on the rules language you are using.
Yeah, i’m probably overthinking this and was going to just use the timestamp from the previous day, but thought it might be cool to try to average out that week to get a closer estimation. Probably not worth my time and planting a AirTag or something on the bus might be better but would probably get me in jail lol
Here’s what i did for code:
rule "Notify and Log Timestamp when the Bus Arrives (Morning & Afternoon)"
when
Item G_Frigate_Frontyard_objBus changed from 0 to 1
then
val currentTime = now
val morningStart = now.withHour(8).withMinute(20).withSecond(0).withNano(0)
val morningEnd = now.withHour(9).withMinute(20).withSecond(0).withNano(0)
val afternoonStart = now.withHour(15).withMinute(30).withSecond(0).withNano(0)
val afternoonEnd = now.withHour(16).withMinute(20).withSecond(0).withNano(0)
logInfo(filename +"Bus", "Item changed: " + G_Frigate_Frontyard_objBus.name + " at " + currentTime.toString)
logInfo(filename +"Bus", "Checking time windows...")
if (currentTime.isAfter(morningStart) && currentTime.isBefore(morningEnd)) {
if (G_Frigate_morningBus_Flag.state == OFF || G_Frigate_morningBus_Flag.state === NULL ) {
logInfo(filename +"Bus", "First change detected in morning window.")
G_Frigate_morningBus_LastChangeTimestamp.postUpdate(new DateTimeType())
G_Frigate_morningBus_Flag.sendCommand(ON)
pushover.sendMessage("The bus is here", "Bus Detected (Morning)")
} else {
logInfo(filename +"Bus", "Change already processed for this morning window.")
}
} else if (currentTime.isAfter(afternoonStart) && currentTime.isBefore(afternoonEnd)) {
if (G_Frigate_afternoonBus_Flag.state == OFF || G_Frigate_afternoonBus_Flag.state === NULL ) {
logInfo(filename +"Bus", "First change detected in afternoon window.")
G_Frigate_afternoonBus_LastChangeTimestamp.postUpdate(new DateTimeType())
G_Frigate_afternoonBus_Flag.sendCommand(ON)
pushover.sendMessage("The bus is here", "Bus Detected (Afternoon)")
} else {
logInfo(filename +"Bus", "Change already processed for this afternoon window.")
}
} else {
logInfo(filename +"Bus", "Current time is outside the specified time windows.")
}
end
//Bus Detection and Logging
DateTime G_Frigate_morningBus_LastChangeTimestamp "Morning Bus Last Change Timestamp"
DateTime G_Frigate_afternoonBus_LastChangeTimestamp "Afternoon Bus Last Change Timestamp"
Switch G_Frigate_morningBus_Flag "Morning Update Done" { expire="1h,command=OFF" }
Switch G_Frigate_afternoonBus_Flag "Afternoon Update Done" { expire="1h,command=OFF" }
That’s a pretty reasonable approach. Where I live, trying to average the arrival times is going to be an exercise in futility in the winter. The weather is extremely variable which impacts driving times a lot.
If I were to write this rule in JS Scripting (I’m trying to get more examples on the forum and it helps users who are moving off of Rules DSL to see examples):
I use managed rules so I would put the following into the rule condition:
var rval = time.toZDT().isBetweenTimes("08:20", "09:20")
|| time.toZDT().isBetweenTimes("15:30", "16:20");
if(!rval) console.info("Current time is outside the specified time windows");
rval;
This script condition will only let the script action run when the current time is between 08:20 and 09:20 or between 15:30 and 16:30. Then we don’t need to check for that in the script action.
console.info("Item changed: " + items.G_Frigate_Frontyard_objBus.state);
console.info("Checking time windows...");
var flag = (items.G_Frigate_morningBus_Flag.isUninitialized)? "OFF" : items.G_Frigate_morningBus_Flag.state;
var time = (time.toZDT().isBetweenTimes("08:20", "09:20"))? "morning" : "afternoon";
if(flag == "OFF") {
items["G_Frigate_"+time+"Bus_LastChangeTimestamp"].postUpdate(time.toZDT());
items["G_Frigate_"+time+"Bus_Flag"].sendCommand("ON");
actions.get("pushover", "THINGID").sendMessager("The bus is here", "Bus Detected ("+time+")"); // you always want to get the Thing actions inside the rule instead of as a global
}
else {
console.info("Change already processed for this " + time + " window.");
}
Some of what I did above could be done in the Rules DSL implementation too including applying the 1-2-3 Design pattern to avoid duplicated code.
I agree, I have winter seasons and somethings are unpredictable like snowstorms etc.
Thats another reason why i posted my code. I have learn’t a lot from searching/other peoples code so posting my own will definatly help someone in the future.
Im Rules DSL for everything, as it’s what i started and learnt with back in the OH1.8 days and havent ventured off of it or spent time to elarn JS Scripting. I appreciate your post and it is making me reconsonsider seeing how condenced you managed to make the same rule!
If you like condenced, definitely give jRuby a look. It’s way more terse than any of the other options.
If you are not much of a programmer (despite your screen name ) give Blockly a look. You can do more in Blockly than even Rules DSL supports.
And even Jython has found a new maintainer is being resurected as a viable option though I’d put the state of the helper library as experimental right now.
JS Scripting is probably the most commonly used if for no other reason than Blockly “compiles” to JS Scripting.
Some day I’ll have time and maybe figure out how to get a functional programming language availble for rules. I miss programming in Scheme and Common Lisp and think the paradigm would really work for OH rules.
But to also make an apples-to-apples comparison, here is the Rules DSL version with some of the same common approaches applied. Rules DSL can be condenced too.
import org.eclipse.smarthome.model.script.ScriptServiceUtil
rule "Notify and Log Timestamp when the Bus Arrives (Morning & Afternoon)"
when
Item G_Frigate_Frontyard_objBus changed from 0 to 1
then
val morningStart = now.withHour(8).withMinute(20).withSecond(0).withNano(0)
val morningEnd = now.withHour(9).withMinute(20).withSecond(0).withNano(0)
val afternoonStart = now.withHour(15).withMinute(30).withSecond(0).withNano(0)
val afternoonEnd = now.withHour(16).withMinute(20).withSecond(0).withNano(0)
var time = "OTHER"
if(now.isAfter(morningStart) && now.isBefore(morningEnd)) time = "morning"
else if(now.isAfter(afternoonStart) && now.isBefore(morningEnd)) time = "afternoon"
if(time == "OTHER") {
logInfo(filename + "Bus", "Current time is outside the specified windows.")
return;
}
val flagItem = ScriptServiceUtil.getItemRegistry.getItem("G_Frigate_"+time+"Bus_Flag")
val flag = if(flagItem.state == OFF || flagItem.state == NULL || flagItem.state == UNDEF) OFF else ON
if(flag == ON) {
logInfo(filename+"Bus", "Change already process for this morning window.")
return;
}
logInfo(filename+"Bus", "First change detected in " + time + " window.")
postUpdate("G_Frigate_+"time+"Bus_LastChangeTimestamp", now)
flagItem.sendCommand(ON)
actions.get("pushover", "THINGUID").sendMessage("The bus is here", "Bus Detected ("+time+")")
end
If you eliminate some of the extra logging it can become:
import org.eclipse.smarthome.model.script.ScriptServiceUtil
rule "Notify and Log Timestamp when the Bus Arrives (Morning & Afternoon)"
when
Item G_Frigate_Frontyard_objBus changed from 0 to 1
then
val morningStart = now.withHour(8).withMinute(20).withSecond(0).withNano(0)
val morningEnd = now.withHour(9).withMinute(20).withSecond(0).withNano(0)
val afternoonStart = now.withHour(15).withMinute(30).withSecond(0).withNano(0)
val afternoonEnd = now.withHour(16).withMinute(20).withSecond(0).withNano(0)
var time = "OTHER"
if(now.isAfter(morningStart) && now.isBefore(morningEnd)) time = "morning"
else if(now.isAfter(afternoonStart) && now.isBefore(morningEnd)) time = "afternoon"
if(time == "OTHER") return; // fail fast
val flagItem = ScriptServiceUtil.getItemRegistry.getItem("G_Frigate_"+time+"Bus_Flag")
val flag = if(flagItem.state == OFF || flagItem.state == NULL || flagItem.state == UNDEF) OFF else ON
if(flag == ON) return; // fail fast
logInfo(filename+"Bus", "First change detected in " + time + " window.")
postUpdate("G_Frigate_+"time+"Bus_LastChangeTimestamp", now)
flagItem.sendCommand(ON)
actions.get("pushover", "THINGUID").sendMessage("The bus is here", "Bus Detected ("+time+")")
end
Unfortunately it doesn’t really save any lines of code, but it does eliminate the duplicated code.
I’d like to add a Ruby version with a difference: you don’t need to use the “Flag” item. Simply check if the timestamp is today. If it is, that means you’ve already saved today’s timestamp.
rule "Notify and Log Timestamp when the Bus Arrives (Morning & Afternoon)" do
changed G_Frigate_Frontyard_objBus, from: 0, to: 1
run do
timestamp_item = case Time.now
when between("08:20".."09:20") then G_Frigate_morningBus_LastChangeTimestamp
when between("15:30".."16:20") then G_Frigate_afternoonBus_LastChangeTimestamp
end
if timestamp_item
timestamp_item.update Time.now unless timestamp_item.state&.today?
else
logger.info "Current time is outside the specified time windows"
end
end
end
If the “case” expression is too esoteric, you can use a more traditional style
rule "Notify and Log Timestamp when the Bus Arrives (Morning & Afternoon)" do
changed G_Frigate_Frontyard_objBus, from: 0, to: 1
run do
if Time.now.between?("08:20".."09:20")
timestamp_item = G_Frigate_morningBus_LastChangeTimestamp
elsif Time.now.between?("15:30".."16:20")
timestamp_item = G_Frigate_afternoonBus_LastChangeTimestamp
else
logger.info "Current time is outside the specified time windows"
next
end
timestamp_item.update Time.now unless timestamp_item.state&.today?
end
end
Checking if the timestamp today is brilliant and i’m going to look into using that method, the only reason i have my flag is because i live on a dead end so you get the bus going down (pickup) then turn around to leave the street so this is better and elimates items that arn’t truely needed.
I’m knowledgable enough to program with some learning, it’s not my career by nature (Sys admin/Cyber) but i know the basics between different languages, its just time i need to rejig all my DSL rules.
iPhones and Android phones now warn users when there’s an air tag following them around without its owner in close proximity. I’m imagining a bus full of students all getting those notifications at once, the ensuing police visit, and the amusing news headlines that would follow.