OH2 does not currently have a mechanism for updating Thing types. When a binding update includes new or modified Channel definitions, the Thing needs to be manually deleted and recreated in order to use the new/modified Channel. For most bindings, it is not too cumbersome to do this manually. But when you are using the Zwave binding with ~120 devices, this can be a real headache. Here is an example of how I have automated the deletion, rediscovery, and reconfiguration of my Zwave Things. I run this after every Zwave binding update, in case there was a change to one of my devices. This is also very important to do when migrating to or from the master and dev branches.
CONSIDERATIONS
- I am currently running OH 2.3 snapshot 1218. This includes a change in ESH that completely crippled the JSONPATH transform. To work around this, I have used jq, which is very handy to have in the toolbox. I have left some of the original lines using JSONPATH as reference for those who do not wish to use jq.
- This rule is functioning well for me, but I still monitor it closely. Originally, I had some sleeps to slow the process down, but my system seemed to perform well without them so they were removed. I welcome feedback for better approaches to accomplish this task⦠but itās working! The one area that may still need some tuning is where I change the temperature scale. Every now and then I was seeing a couple devices where the change did not get set properly, but after the last changes I made to the rule, I havenāt noticed any failures.
- This is stating the obvious, but if a binding update removes channels, any Items using the old channels will no longer function.
- Using an outdated Thing type can cause issues. I have had trouble with Leviton dimmers and switches that provide instant status updates, where they just stop reporting. In my case, deleting and rediscovering the Things resolved this.
[EDIT: since the dev binding now supports UoM for temperature, I commented out those lines. I think there were some other changes to clean things up.]
ITEM
Switch Delete_Zwave_Things "Delete Z-Wave Things" <error> (gVirtual,gNetwork) ["Switchable"] {autoupdate="false"}
RULE
// IMPORTS (put these at the top of the rule file)
import java.util.LinkedHashMap
import java.util.List
rule "Delete zwave Things, start discovery, and add all zwave Things from Inbox"
when
Item Delete_Zwave_Things received command
then
var LinkedHashMap<String,Integer> statusMap = newLinkedHashMap(
"DeletionSuccess" -> 0,
"DeletionFailure" -> 0,
"DiscoveryFailure" -> 0,
"ThingsAdded" -> 0,
"ThingsNotAdded" -> 0//,
//"TempScaleSuccess" -> 0,
//"TempScaleFailure" -> 0
)
//val String thingResult = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -s --connect-timeout 10 -m 10 -X GET --header \"Accept: application/json\" \"http://localhost:8080/rest/things\"",5000)// get Things
//val List<String> zwaveThingList = transform("JSONPATH","$..[?(@.UID =~ /zwave:device.*/)].UID",thingResult).replace("[","").replace("]","").replace("\"","").split(",")// filter out everything but zwave Things and convert to List
logDebug("Rules", "Delete zwave Things: Start")
var List<String> thingUIDList = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -s --connect-timeout 10 -m 10 -X GET --header \"Accept: application/json\" \"http://localhost:8080/rest/things\" | /usr/bin/jq '.[].UID | select(contains(\"zwave:device\"))'",10000).split("\n")//get List of Things and filter out everything but Zwave device Thing UIDs
if (!thingUIDList.contains("")) {
logDebug("Rules", "Delete Zwave Things: {} Zwave Things",thingUIDList.size)
for (thing : thingUIDList) {// delete Zwave Things
val String deletionResponse = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -o /dev/null -s -w \"%{http_code}\" --connect-timeout 10 -m 10 -X DELETE --header \"Accept: application/json\" \"http://localhost:8080/rest/things/" + thing + "?force=false\"",10000)//delete Thing
if (deletionResponse == "200" || deletionResponse == "202") {
statusMap.put("DeletionSuccess",statusMap.get("DeletionSuccess") + 1)
}
else {
statusMap.put("DeletionFailure",statusMap.get("DeletionFailure") + 1)
}
logDebug("Rules", "Delete Zwave Things: Thing deletion: Response=[{}], thing=[{}]",deletionResponse,thing)
}
}
thingUIDList = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -s --connect-timeout 10 -m 10 -X GET --header \"Accept: application/json\" \"http://localhost:8080/rest/things\" | /usr/bin/jq '.[].UID | select(contains(\"zwave:device\"))'",10000).split("\n")//get List of Things and filter out everything but Zwave device Thing UIDs (if deletion was successful, this should be empty)
if (thingUIDList.size == 1 && thingUIDList.contains("")) {
logDebug("Rules", "Delete Zwave Things: All Zwave Things were deleted, so starting Zwave Discovery")
val String discoveryResponse = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -o /dev/null -s -w \"%{http_code}\" --connect-timeout 10 -m 10 -X POST --header \"Content-Type: application/json\" --header \"Accept: text/plain\" \"http://localhost:8080/rest/discovery/bindings/zwave/scan\"",10000)//start discovery
logDebug("Rules", "Delete Zwave Things: Started Discovery, Response=[{}]",discoveryResponse)
if (discoveryResponse != "200") {
statusMap.put("DiscoveryFailure",1)
}
Thread::sleep(10000)// rules restart after discovery, but this may help
val List<String> inboxList = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -s --connect-timeout 10 -m 10 -X GET --header \"Accept: application/json\" \"http://localhost:8080/rest/inbox\" | /usr/bin/jq -r '.[] | {thingUID:(.thingUID | select(contains(\"zwave:device\"))), label:.label} | .[]'",10000).split("\n")//get List of Thing UIDs and labels from Inbox
logDebug("Rules", "Delete Zwave Things: inboxList.size=[{}]",inboxList.size)
for (var index = 0 ; index < inboxList.size ; index += 2) {
val String approvalResponse = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -o /dev/null -s -w \"%{http_code}\" --connect-timeout 10 -m 10 -X POST --header \"Content-Type: text/plain\" --header \"Accept: application/json\" -d \"" + inboxList.get(index + 1) + "\" \"http://localhost:8080/rest/inbox/" + inboxList.get(index) + "/approve\"",10000)//approve Thing in Inbox
logDebug("Rules", "Delete Zwave Things: Inbox approval: Response=[{}], thing=[{}], label=[{}]",approvalResponse,inboxList.get(index),inboxList.get(index + 1))
if (approvalResponse == "200") {
statusMap.put("ThingsAdded",statusMap.get("ThingsAdded") + 1)
}
else {
statusMap.put("ThingsNotAdded",statusMap.get("ThingsNotAdded") + 1)
}
}
/*
thingUIDList = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -s --connect-timeout 10 -m 10 -X GET --header \"Accept: application/json\" \"http://localhost:8080/rest/things\" | /usr/bin/jq -r '.[].UID | select(contains(\"zwave:device\"))'",10000).split("\n")//get List of Things and filter out everything but Zwave device Thing UIDs
logDebug("Rules", "Delete Zwave Things: thingUIDList.size=[{}]",thingUIDList.size)
val String scaleCelcius = "\"config_scale\":\"0\""
val String scaleFahrenheit = "\"config_scale\":\"1\""
for (var index = 0 ; index < thingUIDList.size ; index++) {
var String zwaveThing = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -s --connect-timeout 10 -m 10 -X GET --header \"Accept: application/json\" \"localhost:8080/rest/things/" + thingUIDList.get(index) + "\"",10000)//get Thing
if (zwaveThing.contains(scaleCelcius)) {
zwaveThing = zwaveThing.replace(scaleCelcius,scaleFahrenheit).replace("\"","\\\"")
val String updateResponse = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -o /dev/null -s -w \"%{http_code}\" --connect-timeout 10 -m 10 -X PUT --header \"Content-Type: application/json\" --header \"Accept: application/json\" -d \"" + zwaveThing + "\" \"http://localhost:8080/rest/things/" + thingUIDList.get(index) + "\"",10000)//update temperature scale
logDebug("Rules", "Delete Zwave Things: Configured temperature scale: Response=[{}], thing=[{}]",updateResponse,thingUIDList.get(index))
if (updateResponse == "200") {
statusMap.put("TempScaleSuccess",statusMap.get("TempScaleSuccess") + 1)
}
else {
statusMap.put("TempScaleFailure",statusMap.get("TempScaleFailure") + 1)
}
//Thread::sleep(200)
}
}
*/
}
else {
logDebug("Rules", "Delete zwave Things: {} zwave Things were not deleted, so did not start zwave Discovery. thingUIDList=[{}]",thingUIDList.size,thingUIDList)
}
logDebug("Rules", "Delete zwave Things: End: {}",statusMap.toString)
//val String thingResultFinal = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -s --connect-timeout 10 -m 10 -X GET --header \"Accept: application/json\" \"http://localhost:8080/rest/things\"",5000)// get Things again
//val List<String> zwaveThingListFinal = transform("JSONPATH","$..[?(@.UID =~ /zwave:device.*/)].UID",thingResultFinal).replace("[","").replace("]","").replace("\"","").split(",")// filter out everything but zwave Things and convert to List
//if (zwaveThingListFinal.get(0) == "") {// if deletion of all zwave Things was successful, start zwave discovery
//logDebug("Rules", "Delete zwave Things: All zwave Things were deleted, so starting zwave Discovery")
//executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -s --connect-timeout 10 -m 10 -X POST --header \"Content-Type: application/json\" --header \"Accept: text/plain\" \"http://localhost:8080/rest/discovery/bindings/zwave/scan\"",5000)//start discovery
//}
//else {
//logDebug("Rules", "Delete zwave Things: {} zwave Things were not deleted, so did not start zwave Discovery [{}]",zwaveThingListFinal.size,zwaveThingListFinal)
//}
end
Here is the Jython versionā¦