Temperature depending car heater timer using date & time picker

Howdy, I’ve just started thinking about how I can create a timer function for the block heater of the family car. The idea is that date and time of departure is set in HABPanel using time/date-picker (maybe from Angular UI bootstrap). The chosen time will be used by a rule that takes outdoor temperatureinto consideration to decide long in advance the heater should be running. E.g. when it’s -10 degres C, heater should start 2 hours before departure but when it’s +5 degrees, 30 minutes is sufficient.

Has anyone built something along that line and/or could give some good pointers how to keep it simple? (Simple = High wife acceptance factor)

I do something like this with a time string formmated as such “HH:mm”

var Timer myTimer = null

rule "Timer changed"
when
    Item TimerTime changed or
    Time cron "4 0 0 ? 0 0 0" // Shortly after midnight
then
    val hhmm = TimerTime.state.toString
    val hours = Integer::parseInt(hhmm.split(":").get(0))
    val mins = Integer::parseInt(hhmm.split(":").get(1))

    // Jumps to tomorrow midnight and substract to avoid problems at daylight saving changes
    val timerTime = now.withTimeAtStartOfDay.plusDays(1).minusHours(24-hours).plusMinutes(mins)
    if (myTimer !== null) {
        myTimer?.cancel
        myTimer = null
    }
    myTimer = createTimer(timerTime, [ |
        // Do something
    ])
end

You could add something related to the temperature

rule "Timer changed"
when
    Item TimerTime changed or
    Time cron "4 0 0 ? 0 0 0" // Shortly after midnight
then
    val hhmm = TimerTime.state.toString
    val hours = Integer::parseInt(hhmm.split(":").get(0))
    val mins = Integer::parseInt(hhmm.split(":").get(1))
    val temp = OutsideTemp.state as Number
    var int heatingTime = 0

    switch temp {
      case temp < -10 : heatingTime = 120
      case (temp >= -10 && temp < -5) : heatingTime = 60
        case (temp >= -5 && temp < 5) : heatingTime = 30
      default : heatingTime = 0
    }

    // Jumps to tomorrow midnight and substract to avoid problems at daylight saving changes
    val timerTime = now.withTimeAtStartOfDay.plusDays(1).minusHours(24-hours).plusMinutes(mins).minusMinutes(heatingTime)

    if (myTimer !== null) {
        myTimer?.cancel
        myTimer = null
    }
    myTimer = createTimer(timerTime, [ |
        // Do something (Switch on car heater)
    ])
end
2 Likes

Thanks for sharing Vincent. So if I understand it correctly, you have a string item called TimerTime which would then be possible to set from HABPanel? Would it be of any benefit to use a DateTime type item instead?

Yes

I don’t know.

I don’t think it would make any difference as long as you can get the data into the timer.

Alright, then I just have to figure out how I can make time selection easily available in a HABPanel template.

This time picker looks like just the thing but I don’t know what it takes to get it going : http://angular-ui.github.io/bootstrap/versioned-docs/1.0.0/#/timepicker. I took the example from that link straight away but it only looks like this at the moment:

image
i.e. not like this as in the example:
image

@ysc mentioned in Datetime picker widget? that it would be doable but with programming skills limited to Xml and Qlikview scripts, I’m not sure what it takes to get it going. Anyone familiar with Angular that can give some guidance?

Hi there, just wanted to give some feedback on what happened to this:

  • I ended up not using a date & time-picker (out of lack of knowledge on how to do it). Instead, I’m using a slider in HAB Panel to define in how many hours I have planned my departure and an “OK”-button to commit the slider selection.

  • The countdown timers are based on Design Pattern : Expire Binding based Countdown timer and I have made some dirty fixes just to get it working for my purposes.

  • The OK-button sends the slider value through a rule that converts the hours to minutes and then sends the result to the item that triggers the rule below.

  • The formula that calculates heating time is an approximation of recommended heating times issued by heater makers and the national energy council. The resulting value and makes most sense between -30 and +10 degrees C.

  • Once the heating time is calculated, the results are sent into a rule based on the expire binding timer.

  • Current status on the involved timers are converted into hours and minutes and sent to string items that are shown in HAB Panel.

  • The solution is not as nice as I had planned for but it’s a good start that works flawless and have changed my wife’s view on home automation “silly toys” to “useful stuff”.

  • In case anyone wants to use the solution, here it is. It’s not beatiful, but it works. Improvement suggestions are welcome. (and in case you find names of items and text in screen dumps confusing, it’s because it’s in Swedish.)

Items:

Group gCounters
Group gDelayCounters

Number motorvarmare3fas_counter "Tid kvar 3 fas: [%s]" <clock> (gCounters) {expire="1m,command=-1", autoupdate="false"}
Number delaymotorvarmare3fas_counter "Min till aktivering av 3-fas : [%s]" <clock> (gDelayCounters) {expire="1m,command=-1", autoupdate="false"}
Number delaymotorvarmare3fash_counter "H till aktivering av 3-fas : [%s]" <clock>
String commitdelay "Skicka" <wallswitch>

String delaymotorvarmare3fas_status "Tid till aktivering: [%s]" <clock>
String motorvarmare3fas_status "Tid kvar av pågående värmning: [%s]" <clock>
String motorvarmare3fas_tempruntime_status "Tempanpassad värmartid: [%s]" <clock>
Number motorvarmare3fas_tempruntime "Tempanpassad värmartid: [%s]" <clock>
Number motorvarmare3fas_timetodeparture "Sätt tid till avresa (mins): [%s]" <clock>
Number motorvarmare3fash_timetodeparture "Sätt tid till avresa (hrs): [%s]" <clock>

Rules:

//Receive departure time in hours from HAB Panel
rule "1. Delay activation"

when 
	Item commitdelay received command "DELAY"
	
then
	var cmmd = (motorvarmare3fash_timetodeparture.state as Number).intValue
	logWarn("timer.rules", "Delay activation: Received value " + cmmd + " h")
	motorvarmare3fas_timetodeparture.sendCommand(cmmd * 60) //convert hours to minutes
	logWarn("timer.rules", "Delay activation: Converted to " + 60*cmmd + " min and sent to motorvarmare3fas_timetodeparture")
	motorvarmare3fash_timetodeparture.sendCommand(0)
	logWarn("timer.rules", "Delay activation: Cleared slider item in HAB Panel (motorvarmare3fash_timetodeparture)")
	commitdelay.sendCommand("DELAY CONFIRMED")
	
end

//Adjusts heating time to the current temperature
rule "2. Countdown by temp"

when
	Item motorvarmare3fas_timetodeparture received command
	
then
	val DepTime = (receivedCommand as Number).intValue // integers only
	if (DepTime==0){
		motorvarmare3fas_tempruntime.sendCommand(0)
		commitdelay.sendCommand("CANCEL")
	} else {
		logWarn("timer.rules", "Countdown by temp: DepTime set to  " + DepTime + " minuter")
		val OutdoorTemp = (Netatmo_Outdoor_Temperature.state as Number)
		logWarn("timer.rules", "Countdown by temp: OutdoorTemp set to " + OutdoorTemp + " degrees")
		val RunTime = (60*(-0.075*OutdoorTemp+1.25)).intValue
		logWarn("timer.rules", "Countdown by temp: RunTime set to " + RunTime + " minuter")
		var DelayTime = (DepTime-RunTime).intValue
		if(DelayTime < 0) {			
			DelayTime = 1				//Force 1 minute countdown in case DepTime minus RunTime is less than 0. Otherwise timer won't trigger.
		}												
		logWarn("timer.rules", "Countdown by temp: DelayTime set to " + DelayTime + " minuter")

		motorvarmare3fas_tempruntime.sendCommand(RunTime)
		delaymotorvarmare3fas_counter.sendCommand(DelayTime)		
	}
end

// Countdown before activating the heater for a given amount of time
rule "3. Countdown delay"

when
	Member of gDelayCounters received command
then
	var cmmd = (receivedCommand as Number).intValue // integers only
	var count = 0
	val RunTime = (motorvarmare3fas_tempruntime.state as Number).intValue
	//logWarn("timer.rules", "Countdown delay: Runtime will be set to " + RunTime + " minutes")	
	
//	logWarn("timer.rules", "Delay av 3-fastimer mottog värde " + cmmd + " h")
	//cmmd = cmmd * 60
	
	
	if (triggeringItem.state != NULL) {  // avoid 1st time run error
		count = (triggeringItem.state as Number).intValue
	}
	//val lightname = triggeringItem.name.split("_").get(0)  // derive light
	//val myLight = gTimedDevices.members.filter[ i | i.name == lightname ].head // get light item
    
	if (cmmd == -1 && count > 0) {  // decrement counter, do not go below zero
		if (count == 1) {
			// do actions for counter expiry, as we about to zero it now
			motorvarmare3fas_counter.sendCommand(RunTime)
			logWarn("timer.rules", "Countdown delay: Runtime activated for " + RunTime + " minutes")	
		}
		triggeringItem.postUpdate(count - 1)
	} else if (cmmd == 0) {  // cancel countdown
		triggeringItem.postUpdate(0)
		logWarn("timer.rules", "Countdown delay: Countdown cancelled")	
		// do optional cancel actions
		//myLight.sendCommand(OFF)
	} else if (cmmd >= count || cmmd < -1) {  // new or refreshed target
		if (cmmd < -1) {  // force override
			cmmd = 0 - cmmd  // make it positive
		}
		triggeringItem.postUpdate(cmmd)  // nb we still update even if equal value - resets expire binding
		// do startup/continue actions
		//if (myLight.state != ON) {
		//	myLight.sendCommand(ON)
		//}
	}
end

//Updates status items to show timer values in hours and minutes rather than just minutes or decimmal hours
rule "4. Update counter status"

when
	Item delaymotorvarmare3fas_counter changed
	or
	Item motorvarmare3fas_counter changed
	or
	Item motorvarmare3fas_tempruntime changed
	
then
	val int totalMinsDelay = (delaymotorvarmare3fas_counter.state as Number).intValue	
	val mind = totalMinsDelay % 60
	val hrsd = (totalMinsDelay /	 60) % 24
	val String delayhrsmins = hrsd.toString + "h " + mind.toString + "m"		
	delaymotorvarmare3fas_status.sendCommand(delayhrsmins)
	
	val int totalMinsVarmare = (motorvarmare3fas_counter.state as Number).intValue	
	val minv = totalMinsVarmare % 60
	val hrsv = (totalMinsVarmare /	 60) % 24
	val zerocheck = minv+hrsv
	if(zerocheck == 0){
		motorvarmare3fas_status.sendCommand("N/A")
	} else {
		val String heathrsmins = hrsv.toString + "h " + minv.toString + "m"	
		motorvarmare3fas_status.sendCommand(heathrsmins)
	}
	
	val int totalMinsTemprun = (motorvarmare3fas_tempruntime.state as Number).intValue	
	val mint = totalMinsTemprun % 60
	val hrst = (totalMinsTemprun /	 60) % 24
	val String temprunmins = hrst.toString + "h " + mint.toString + "m"	
	motorvarmare3fas_tempruntime_status.sendCommand(temprunmins)
	
end
	
//Cancel timers. It can also be done without this rule by sending 0 as timer values.

rule "5. Cancel timers"
when 
	Item commitdelay received command "CANCEL"
	
then
	delaymotorvarmare3fas_counter.sendCommand(0)
	motorvarmare3fas_counter.sendCommand(0)
	motorvarmare3fas_tempruntime.sendCommand(0)

end

The looks of it in HAB Panel
image

And to use it in a sitemap, this is the code I’m using:

		Text item=motorvarmare3fas label="Heater [%s]" icon="poweroutlet" labelcolor=[delaymotorvarmare3fas_counter >0 = "orange"]{
			Frame label="Heater/3-phase outlet" {
			Selection item=motorvarmare3fas_timetodeparture label="Time to departure: []" mappings=[
			0="Cancel",60="1h",120="2h",180="3h",240="4h",300="5h",360="6h",420="7h",480="8h",
			540="9h",600="10h",660="11h",720="12h",780="13h",840="14h",900="15h",960="16h",
			1020="17h",1080="18h",1140="20h",1200="21h",1260="22h",1320="23h",1380="24h"]
			Text item=motorvarmare3fas_tempruntime_status icon="time"
			Text item=delaymotorvarmare3fas_status icon="time"
			Text item=motorvarmare3fas_status icon="time"
			Switch item=motorvarmare3fas label="Outlet status [%s]" icon="poweroutlet"
			}
		}

…which looks like this (although label text is translated in the sitemap code above):
image