Control built-in timers of Tasmota device via openHAB

Tags: #<Tag:0x00007f616e5a6d48> #<Tag:0x00007f616e5a6c80>

Introduction

Time-based execution of Tasmota device can be done from openHAB via cron from .rules file. However, cron-based execution requires two things to be functioning mostly at all times. Your network and openHAB server. If one of them isn’t working at the time when device should turn on or off, cron job won’t be applied.

Tasmota has 16 built-in timers, which are stored in device’s memory. Meaning, once you set up timers, you don’t need to rely on your openHAB server or your network to function properly at all times. Unfortunately you have to access timers via device’s IP address.

Preparation

In order to control built-in timers via openHAB, you need to make sure to have enabled timers on device. (Configuration -> Configure Timer -> Enable Timers checked)

Also, make sure to have these add-ons installed:
Bindings: MQTT Binding
Misc: MQTT Broker Moquette
Persistence: MapDB Persistence
Transformations: JSONPath Transformation, RegEx Transformation

Code

Example below shows how water heater connected to Sonoff smart plug flashed with Tasmota can be turned on and off by two built-in timers. Currently time (hour and minute), action (on or off), save and disable timer can be controlled. Repeat and all days are always checked.

default.things

Bridge mqtt:broker:local_broker "Local broker"  [host="localhost"] {
    Thing topic cellar "Cellar" @ "Cellar" {
        Channels:
            Type switch : water_heater_switch  [stateTopic="stat/tasmota_ABCXYZ/POWER", commandTopic="cmnd/tasmota_ABCXYZ/POWER"]
            Type switch : water_heater_state [stateTopic="stat/tasmota_ABCXYZ/POWER"]
            Type string : water_heater_timer_1 [stateTopic="stat/tasmota_ABCXYZ/RESULT", commandTopic="cmnd/tasmota_ABCXYZ/TIMER1", transformationPattern="REGEX:(.*Timer1.*)∩REGEX:(.*Time.*)∩REGEX:(.*Action.*)"]
            Type string : water_heater_timer_2 [stateTopic="stat/tasmota_ABCXYZ/RESULT", commandTopic="cmnd/tasmota_ABCXYZ/TIMER2", transformationPattern="REGEX:(.*Timer2.*)∩REGEX:(.*Time.*)∩REGEX:(.*Action.*)"]
    }
}

groups.items


Group    gWater_Heater_Timers

water_heater.items

Switch    Water_Heater_Switch              "Switch"          <switch>                               {channel="mqtt:topic:local_broker:cellar:water_heater_switch"}
Switch    Water_Heater_State               "Water Heater"    <water>                                {channel="mqtt:topic:local_broker:cellar:water_heater_state"}
String    Water_Heater_Timer_1                                            (gWater_Heater_Timers)    {channel="mqtt:topic:local_broker:cellar:water_heater_timer_1"}
String    Water_Heater_Timer_1_Overview    "Timer 1 [%s]"    <time>
Number    Water_Heater_Timer_1_Hour        "Hour [%d]"       <time>
Number    Water_Heater_Timer_1_Minute      "Minute [%d]"     <time>
Number    Water_Heater_Timer_1_Action      "Action"          <switch>
Number    Water_Heater_Timer_1_Save        "Save"            <contact>
Number    Water_Heater_Timer_1_Disable     "Disable"         <error>
String    Water_Heater_Timer_2                                            (gWater_Heater_Timers)    {channel="mqtt:topic:local_broker:cellar:water_heater_timer_2"}
String    Water_Heater_Timer_2_Overview    "Timer 2 [%s]"    <time>
Number    Water_Heater_Timer_2_Hour        "Hour [%d]"       <time>
Number    Water_Heater_Timer_2_Minute      "Minute [%d]"     <time>
Number    Water_Heater_Timer_2_Action      "Action"          <switch>
Number    Water_Heater_Timer_2_Save        "Save"            <contact>
Number    Water_Heater_Timer_2_Disable     "Disable"         <error>

water_heater.rules

val String disableTimerJson = "{\"Arm\":0,\"Mode\":0,\"Time\":\"00:00\",\"Window\":0,\"Days\":\"0000000\",\"Repeat\":0,\"Output\":1,\"Action\":0}"

rule "Fetch Water Heater Timers"
when
    // every 5 minutes
    Time cron "0 0/5 * * * ?" 
then
    gWater_Heater_Timers.members.forEach[item | item.sendCommand("")] 

    if (Water_Heater_Timer_1.state == NULL) {
        logWarn("Fetch Water Heater Timers", "Water_Heater_Timer_1 is NULL.")

        return
    } else if (Water_Heater_Timer_1.state.toString.equals("")) {
        logWarn("Fetch Water Heater Timers", "Water_Heater_Timer_1 is empty.")

        return
    }
    
    // Water_Heater_Timer_1.state has to be stored in variable, otherwise error is thrown
    val String timer1Json = Water_Heater_Timer_1.state.toString
    var timer1TimeJson = transform("JSONPATH", "$.Timer1.Time", timer1Json)
    var timer1ActionJson = transform("JSONPATH", "$.Timer1.Action", timer1Json)

    // remove prefix from single digit number for setpoints
    val Integer timer1Hour = Integer::parseInt(timer1TimeJson.toString.split(":").get(0)) 
    val Integer timer1Minute = Integer::parseInt(timer1TimeJson.toString.split(":").get(1))
    val String timer1Action = if (timer1ActionJson.toString == "0") "OFF" else "ON"

    Water_Heater_Timer_1_Hour.postUpdate(timer1Hour)
    Water_Heater_Timer_1_Minute.postUpdate(timer1Minute)
    Water_Heater_Timer_1_Action.postUpdate(timer1ActionJson)
    Water_Heater_Timer_1_Overview.postUpdate(timer1TimeJson + " - " + timer1Action)

    if (Water_Heater_Timer_2.state == NULL) {
        logWarn("Fetch Water Heater Timers", "Water_Heater_Timer_2 is NULL.")

        return
    } else if (Water_Heater_Timer_2.state.toString.equals("")) {
        logWarn("Fetch Water Heater Timers", "Water_Heater_Timer_2 is empty.")

        return
    }
    
    // Water_Heater_Timer_2.state has to be stored in variable, otherwise error is thrown
    val String timer2Json = Water_Heater_Timer_2.state.toString
    var timer2TimeJson = transform("JSONPATH", "$.Timer2.Time", timer2Json)
    var timer2ActionJson = transform("JSONPATH", "$.Timer2.Action", timer2Json)

    // remove prefix from single digit number for setpoints
    val Integer timer2Hour = Integer::parseInt(timer2TimeJson.toString.split(":").get(0)) 
    val Integer timer2Minute = Integer::parseInt(timer2TimeJson.toString.split(":").get(1))
    val String timer2Action = if (timer2ActionJson.toString == "0") "OFF" else "ON"

    Water_Heater_Timer_2_Hour.postUpdate(timer2Hour)
    Water_Heater_Timer_2_Minute.postUpdate(timer2Minute)
    Water_Heater_Timer_2_Action.postUpdate(timer2ActionJson)
    Water_Heater_Timer_2_Overview.postUpdate(timer2TimeJson + " - " + timer2Action)
end

rule "Update Water Heater Timer 1"
when
    Item Water_Heater_Timer_1_Save received command
then
    // add prefix for single digit numbers
    var String timer1Hour =  if (Water_Heater_Timer_1_Hour.state.toString.length == 1) "0" + Water_Heater_Timer_1_Hour.state.toString else Water_Heater_Timer_1_Hour.state.toString
    var String timer1Minute = if (Water_Heater_Timer_1_Minute.state.toString.length == 1) "0" + Water_Heater_Timer_1_Minute.state.toString else Water_Heater_Timer_1_Minute.state.toString
    val String timer1Time =  timer1Hour + ":" + timer1Minute
    var String timer1Action = if (Water_Heater_Timer_1_Action.state == 0) "OFF" else "ON"
    var timer1Json = "{\"Arm\":1,\"Time\":\"" + timer1Time + "\",\"Window\":0,\"Days\":\"SMTWTFS\",\"Repeat\":1,\"Output\":1,\"Action\":" + Water_Heater_Timer_1_Action.state.toString + "}"

    Water_Heater_Timer_1_Overview.postUpdate(timer1Time + " - " + timer1Action)
    Water_Heater_Timer_1.sendCommand(timer1Json)
end

rule "Disable Water Heater Timer 1"
when
    Item Water_Heater_Timer_1_Disable received command
then
    Water_Heater_Timer_1_Overview.postUpdate("00:00 - OFF")
    Water_Heater_Timer_1_Hour.postUpdate(0)
    Water_Heater_Timer_1_Minute.postUpdate(0)
    Water_Heater_Timer_1_Action.postUpdate(0)

    Water_Heater_Timer_1.sendCommand(disableTimerJson)
end

rule "Update Water Heater Timer 2"
when
    Item Water_Heater_Timer_2_Save received command
then
    // add prefix for single digit numbers
    var String timer2Hour =  if (Water_Heater_Timer_2_Hour.state.toString.length == 1) "0" + Water_Heater_Timer_2_Hour.state.toString else Water_Heater_Timer_2_Hour.state.toString
    var String timer2Minute = if (Water_Heater_Timer_2_Minute.state.toString.length == 1) "0" + Water_Heater_Timer_2_Minute.state.toString else Water_Heater_Timer_2_Minute.state.toString
    val String timer2Time =  timer2Hour + ":" + timer2Minute
    var String timer2Action = if (Water_Heater_Timer_2_Action.state == 0) "OFF" else "ON"
    var timer2Json = "{\"Arm\":1,\"Time\":\"" + timer2Time + "\",\"Window\":0,\"Days\":\"SMTWTFS\",\"Repeat\":1,\"Output\":1,\"Action\":" + Water_Heater_Timer_2_Action.state.toString + "}"

    Water_Heater_Timer_2_Overview.postUpdate(timer2Time + " - " + timer2Action)
    Water_Heater_Timer_2.sendCommand(timer2Json)
end

rule "Disable Water Heater Timer 2"
when
    Item Water_Heater_Timer_2_Disable received command
then
    Water_Heater_Timer_2_Overview.postUpdate("00:00 - OFF")
    Water_Heater_Timer_2_Hour.postUpdate(0)
    Water_Heater_Timer_2_Minute.postUpdate(0)
    Water_Heater_Timer_2_Action.postUpdate(0)

    Water_Heater_Timer_2.sendCommand(disableTimerJson)
end

default.sitemap

sitemap default label="Home" { 
	Frame label="Cellar" { 
		Group item=Water_Heater_State valuecolor=[=="OFF"="red", =="ON"="green"] { 
			Switch item=Water_Heater_Switch
			Text icon="none"
			Group item=Water_Heater_Timer_1_Overview { 
				Setpoint item=Water_Heater_Timer_1_Hour minValue=0 maxValue=23 step=1 
				Setpoint item=Water_Heater_Timer_1_Minute minValue=0 maxValue=59 step=1 
				Switch item=Water_Heater_Timer_1_Action mappings=[0="OFF", 1="ON"] 
				Switch item=Water_Heater_Timer_1_Save mappings=[0="CONFIRM"] 
				Text icon="none"
				Text icon="none"
				Switch item=Water_Heater_Timer_1_Disable mappings=[0="DISABLE TIMER"]
			} 
			Group item=Water_Heater_Timer_2_Overview { 
				Setpoint item=Water_Heater_Timer_2_Hour minValue=0 maxValue=23 step=1 
				Setpoint item=Water_Heater_Timer_2_Minute minValue=0 maxValue=59 step=1 
				Switch item=Water_Heater_Timer_2_Action mappings=[0="OFF", 1="ON"] 
				Switch item=Water_Heater_Timer_2_Save mappings=[0="CONFIRM"] 
				Text icon="none"
				Text icon="none"
				Switch item=Water_Heater_Timer_2_Disable mappings=[0="DISABLE TIMER"]
			} 
		}  
	} 
}

Screenshots

Since I can post only one image as a new user, all screenshots are available on repo.

Conclusion

At the moment, more timers can be controlled just by copy-pasting previous stuff inside .items, .things, .sitemap and .rules file. There are few things missing to control. Such as mode, window, days, repeat, output, toogle action.

All code is also available on github. Feel free to post replies here or write an issue or submit a pull request on repository.

5 Likes

Good post! I wrote something similar too - can never have enough tutorials!

1 Like

thanks but timers not working for me, only ON & OFF

Could you please be more specific?

Make sure to have Enable Timers checked in device settings and all required add-ons installed. Also in .things file make sure your state and command topic matches your device’s mqtt full topic.

I installed all required add-ons.
I copied what you wrote down to the following files:

default.things
groups.items
water_heater.items
water_heater.rules
default.sitemap

And just the right topic I changed to what I have set in tasmota mqtt
What works is turning the device off and on
And what does not work is scheduling
For example: I change the time 05:00 press ON and then CONFIRM but the device does not turn on at the time I set.

This is how I set timers 1 & 2 in tasmota:

solved, I found the issue, I also needed to mark the second enable next to repeat
Now it’s working.

1 Like

How can I turn off the Repeat mode?
That will not repeat itself and will be one-time until I choose another time to turn the timer on or off

Generically, you need to send Repeat:0 to the timer.

If you never want it to repeat, change the rules above to change any instance of:

\"Repeat\":1

To:

\"Repeat\":0
2 Likes