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.
Source code is also available on github. Feel free to post replies here or write an issue or submit a pull request on repository.