Hi there,
this is a configuration example to make a tedee smartlock accessible and usable in the Openhab system by using API calls in rules.
There is no local API support yet, hence online access is required.
A connected bridge is required to make use of the API
Disclaimer: I admit I tried to get this going in a binding, but my Java knowledge is quite limited and I am sitting in front of Eclipse without any idea on how to start.
First of all I created a few basic items:
String tedee_token
Number tedee_timer
Number tedee_batteryLevel "Schloss Batteriestand [%.0f %%]"
String tedee_isConnected "Status Schloss [%s]"
String tedee_state "Zustand Schloss [%s]"
Switch tedee_tokenfetcher "Holt Token" {expire="1s,state=OFF"}
String tedee_lockoperation "Schlossbedienung []"
I am accessing the api first to get a token from the API with my tedee username and password:
rule "Retrieve Token"
when
Item tedee_tokenfetcher changed from OFF to ON
then
var String api = "https://tedee.b2clogin.com/tedee.onmicrosoft.com/B2C_1_SignIn_Ropc/oauth2/v2.0/token"
var String result = ""
var String username = "your username/email"
var String password = "your password"
var String content = "grant_type=password&username=" + username + "&password=" + password + "&client_id=02106b82-0524-4fd3-ac57-af774f340979&scope=openid 02106b82-0524-4fd3-ac57-af774f340979&response_type=token"
result = sendHttpPostRequest(api, "application/x-www-form-urlencoded", content, 1000)
val evaluation = transform("JSONPATH", "$.error", result)
if (evaluation != "access_denied") {
val token = transform("JSONPATH", "$.access_token", result)
val time = transform("JSONPATH", "$.expires_in", result)
logInfo("tedee", " Updated Token for tedee: " + token)
tedee_token.postUpdate(token)
tedee_timer.postUpdate(time)
} else {
logInfo("tedee","API threw error, restarting in 5 minutes")
createTimer(now.plusMinutes(5), [|
tedee_tokenfetcher.sendCommand(ON)
])
}
if (tedee_token.state == null ) {
logInfo("tedee","API threw error, restarting in 5 minutes")
createTimer(now.plusMinutes(5), [|
tedee_tokenfetcher.sendCommand(ON)
])
}end
As each token is valid for 10800 seconds I set a timer which fetches a new token 5 minutes before the old one expires. The rule is working based on the API expires_in response, in case the time limit changes.
rule "Token update Timer"
when
Item tedee_timer received update
then
val int timeToUpdate = (tedee_timer.state as Number).intValue() - 300
logInfo("tedee", "Fetching new token in " + timeToUpdate + " seconds")
createTimer(now.plusSeconds(timeToUpdate), [|
tedee_tokenfetcher.sendCommand(ON)
])
end
With the token, you are able to fetch all connected logs and get the status of respective locks from the API. I update the corresponding items where necessary and run it every 10 minutes. tedee advises not to sync status faster than every 10 seconds. Generally even a slower update would be feasible in my case, as I am merely interested in whether it is locked or not and battery stats.
The lock id changes each time the lock is deleted and re-added to your tedee account. The id can be retrieved by running a full sync against the api (https://api.tedee.com/api/v1.20/my/lock/sync) without a specific id.
I did not automate this, as I do not expect a lot of changes. I used the uri once during the first call and logged it.
rule "Retrieve Lock status and update items"
when
Time cron "0 0/10 * * * ?"
then
var String lock_id = "your lock id"
var String api = "https://api.tedee.com/api/v1.20/my/lock/" + lock_id + "/sync"
var String tokenheader = "Bearer " + tedee_token.state.toString
var headers = newHashMap("accept" -> "application/json","Authorization" -> tokenheader)
var String result = ""
result = sendHttpGetRequest(api,headers,1000)
if (result.equals("{}")) {
tedee_tokenfetcher.sendCommand(ON)
}
//in case of error, start new token fetch
val evaluation = transform("JSONPATH", "$.success", result)
if (evaluation == "false") {
logInfo("tedee","API threw error, fetching token")
tedee_tokenfetcher.sendCommand(ON)
}
val isConnected = transform("JSONPATH", "$.result.isConnected", result)
val state_int = transform("JSONPATH", "$.result.lockProperties.state", result)
val batteryLevel = Integer::parseInt(transform("JSONPATH", "$.result.lockProperties.batteryLevel", result))
logInfo("tedee", "Schloss ID " + lock_id + " ist verbunden (" + isConnected + "), aktueller Status " + state_int + " und Batterie " + batteryLevel + "%")
//in case of error, start new token fetch
val evaluation = transform("JSONPATH", "$.error", result)
if (evaluation == "access_denied") {
logInfo("tedee","API threw error, fetching token")
tedee_tokenfetcher.sendCommand(ON)
}
//update Lockitems only if information has changed
if (isConnected == "true" && tedee_isConnected != "Verbunden" ) {
tedee_isConnected.postUpdate("Verbunden")
} else {
tedee_isConnected.postUpdate("Getrennt")
}
if (tedee_batteryLevel.state == NULL || batteryLevel != tedee_batteryLevel.state) {
tedee_batteryLevel.postUpdate(batteryLevel)
}
if (tedee_state.state == NULL || state_int != tedee_state.state.toString) {
tedee_state.postUpdate(state_int)
}
if (state_int == "2" || state_int == "6") { // Updates visual representation of Lock in the Basic UI
tedee_lockoperation.postUpdate(state_int)
}
end
I am still using basic sitemap with the ios app and use following rule to initiate a lockoperation through HTTP Post.
Unfortunately the âsoft lockâ mode without pulling the trap is NOT working as long as trap pulling is already configured on the lock.
rule "tedee Bedienung"
when
Item tedee_lockoperation received update
then
var String lock_id = "your lock id"
var String api = "https://api.tedee.com/api/v1.20/my/lock/" + lock_id + "/operation"
var String tokenheader = "Bearer " + tedee_token.state.toString
var headers = newHashMap("accept" -> "application/json","Authorization" -> tokenheader)
switch(tedee_lockoperation.state) {
case "2": {
api = api + "/unlock"
sendHttpPostRequest(api,"application/json-patch+json","",headers,1000)
tedee_state.postUpdate(2)
}
case "6": {
api = api + "/lock"
sendHttpPostRequest(api,"application/json-patch+json","",headers,1000)
tedee_state.postUpdate(6)
}
case "99": {
api = api + "/unlock?mode=3"
sendHttpPostRequest(api,"application/json-patch+json","",headers,1000)
tedee_state.postUpdate(2)
}
}
end
In my Basic sitemap I used basic item integration
Text label="Schloss" {
Text item=tedee_isConnected
Switch item=tedee_lockoperation mappings=[2="Oeffnen",6="Schliessen",99="Soft öffnen"]
Text item=tedee_batteryLevel
Text item=tedee_state label="Status Schloss [MAP(tedee_lockstates.map):%s]"
Switch item=tedee_tokenfetcher
}
Maybe this helps somebody with these locks. I like the small form factor compared to other options as well as the possibility to load via USB. It is not necessary to connect the bridge for basic use, but without it the lock is only exposing bluetooth.
Happy to receive any feedback on how to make the rules better or on how to get the soft opening (mode 3 ) working.