I have built a device handler to try and keep track of my alarm (Visonic Powermax), but am struggling to get the DH to remember the state. I can run configure/refresh/ping/… and they all call the same postAction which correctly gets the device status (and changes the various tile status’). However as soon as I exit that device page on the phone (or go to settings and back again), the device returns to the default status for all tiles.
Can anybody help me with what I am doing wrong/missing?
Thanks
import groovy.json.JsonSlurper
metadata {
definition (name: "Visonic Controller", namespace: "cjcharles0", author: "Chris Charles") {
capability "Switch"
//capability "Refresh"
capability "Sensor"
capability "Configuration"
//capability "Health Check"
command "reset"
command "ArmAway"
command "ArmHome"
command "Disarm"
}
simulator {
}
preferences {
input("password", "password", title:"Password", required:false, displayDuringSetup:true)
input("ip", "string", title:"IP Address", description: "e.g. 192.168.1.10", required: true, displayDuringSetup: true)
}
tiles (scale: 1){
standardTile("Status", "device.status", height: 1, width:1, inactiveLabel: false, canChangeIcon: false) {
state "disarmed", label:"Disarmed", action:"", backgroundColor:"#D8D8D8"
state "away", label:"Away", action:"", backgroundColor:"#FF9900"
state "home", label:"Home", action:"", backgroundColor:"#FF9900"
state "alarm", label:"Alarm", action:"", backgroundColor:"#FF0000"
}
standardTile("LastAction", "device.lastaction", height: 1, width:2, inactiveLabel: false, canChangeIcon: false) {
state "zone", label:"Zone Action", action:"", backgroundColor:"#D8D8D8"
}
standardTile("ArmAway", "device.armaway", height: 1, width:1, inactiveLabel: false, canChangeIcon: true) {
state "inactive", label:"Away", action:"ArmAway", backgroundColor:"#D8D8D8"
state "changing", label:"Arming Away", action:"", backgroundColor:"#FF9900"
state "active", label:"Armed Away", action:"", icon:"st.Outdoor.outdoor15", backgroundColor:"#00CC00"
}
standardTile("ArmHome", "device.armhome", height: 1, width: 1, inactiveLabel: false, canChangeIcon: true) {
state "inactive", label:"Home", action:"ArmHome", backgroundColor:"#D8D8D8"
state "changing", label:"Arming Home", action:"", backgroundColor:"#FF9900"
state "active", label:"Armed Home", action:"", icon:"st.Home.home2", backgroundColor:"#00CC00"
}
standardTile("Disarm", "device.disarm", height: 1, width: 1, inactiveLabel: false, canChangeIcon: true) {
state "inactive", label:"Disarm", action:"Disarm", icon:"st.presence.house.unlocked", backgroundColor:"#D8D8D8"
state "changing", label:"Disarming", action:"", backgroundColor:"#FF9900"
state "active", label:"Disarmed", action:"", icon:"st.locks.lock.unlocked", backgroundColor:"#00CC00"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", inactiveLabel: false, width: 1, height: 1, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
valueTile("ip", "ip", decoration: "flat", width: 1, height: 1) {
state "ip", label:'IP Address\r\n${currentValue}'
}
}
//main(["Status"])
details(["Status", "LastAction" , "ArmAway", "ArmHome", "Disarm",
"configure", "ip"])
}
def installed() {
log.debug "installed()"
configure()
}
def updated() {
log.debug "updated()"
configure()
}
def ArmAway() {
log.debug "armaway()"
sendEvent(name: "ArmAway", value: "changing", isStateChange: true)
sendEvent(name: "ArmHome", value: "inactive", isStateChange: true)
sendEvent(name: "Disarm", value: "inactive", isStateChange: true)
postAction("/armaway")
}
def ArmHome() {
log.debug "armhome()"
sendEvent(name: "ArmAway", value: "inactive", isStateChange: true)
sendEvent(name: "ArmHome", value: "changing", isStateChange: true)
sendEvent(name: "Disarm", value: "inactive", isStateChange: true)
postAction("/armhome")
}
def Disarm() {
log.debug "disarm()"
sendEvent(name: "ArmAway", value: "inactive", isStateChange: true)
sendEvent(name: "ArmHome", value: "inactive", isStateChange: true)
sendEvent(name: "Disarm", value: "changing", isStateChange: true)
postAction("/disarm")
}
/*def configure() {
log.debug "configure()"
log.debug "Configuring Device For SmartThings Use"
sendEvent(name: "checkInterval", value: 12 * 60, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
def responses = []
if (ip != null) state.dni = setDeviceNetworkId(ip, "80")
state.hubIP = device.hub.getDataValue("localIP")
state.hubPort = device.hub.getDataValue("localSrvPortTCP")
responses << configureStatus()
//responses << configureInstant(state.hubIP, state.hubPort, powerOnState)
//responses << configureDefault()
return response(responses)
}*/
def configureVisonic(){
log.debug "Configuring Visonic (IP/port....)"
}
def configure(){
log.debug "Refreshing Visonic Information"
return postAction("/status")
}
def parse(description) {
def map = [:]
def events = []
def cmds = []
if(description == "updated") return
def descMap = parseDescriptionAsMap(description)
def body = new String(descMap["body"].decodeBase64())
def slurper = new JsonSlurper()
def result = slurper.parseText(body)
log.debug result
if (result.containsKey("stat_str")) {
if (result.stat_str=="Disarmed") {
events << createEvent(name: "Disarm", value: "active", isStateChange: true)
events << createEvent(name: "Status", value: "disarmed", isStateChange: true)
log.debug "Disarmed Status found"}
else if (result.stat_str=="Armed Away") {
events << createEvent(name: "ArmAway", value: "active", isStateChange: true)
events << createEvent(name: "Status", value: "away", isStateChange: true)
log.debug "Armed Away Status found"}
else if (result.stat_str=="Armed Home") {
events << createEvent(name: "ArmHome", value: "active", isStateChange: true)
events << createEvent(name: "Status", value: "home", isStateChange: true)
log.debug "Armed Home Status found"}
}
if (result.containsKey("successtest")) {
if (result.successtest == "true") state.configSuccess = "true" else state.configSuccess = "false"
}
//if (cmds != [] && events != null) return [events, response(cmds)] else if (cmds != []) return response(cmds) else return events
return events
}
//def reset() {
//log.debug "reset()"
//}
def refresh() {
log.debug "refresh()"
postAction("/status")
}
def ping() {
log.debug "ping()"
postAction("/status")
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
def sync(ip, port) {
def existingIp = getDataValue("ip")
def existingPort = getDataValue("port")
if (ip && ip != existingIp) {
updateDataValue("ip", ip)
sendEvent(name: 'ip', value: ip)
}
if (port && port != existingPort) {
updateDataValue("port", port)
}
}
private encodeCredentials(username, password){
def userpassascii = "${username}:${password}"
def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
return userpass
}
private postAction(uri){
log.debug "uri ${uri}"
updateDNI()
def userpass
if(password != null && password != "")
userpass = encodeCredentials("admin", password)
def headers = getHeader(userpass)
def hubAction = new physicalgraph.device.HubAction(
method: "GET",
path: uri,
headers: headers
)
hubAction
}
private setDeviceNetworkId(ip, port = null){
def myDNI
if (port == null) {
myDNI = ip
} else {
def iphex = convertIPtoHex(ip)
def porthex = convertPortToHex(port)
myDNI = "$iphex:$porthex"
}
log.debug "Device Network Id set to ${myDNI}"
return myDNI
}
private updateDNI() {
if (state.dni != null && state.dni != "" && device.deviceNetworkId != state.dni) {
device.deviceNetworkId = state.dni
}
}
private getHostAddress() {
if(getDeviceDataByName("ip") && getDeviceDataByName("port")){
return "${getDeviceDataByName("ip")}:${getDeviceDataByName("port")}"
}else{
return "${ip}:80"
}
}
private String convertIPtoHex(ipAddress) {
String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join()
return hex
}
private String convertPortToHex(port) {
String hexport = port.toString().format( '%04x', port.toInteger() )
return hexport
}
def parseDescriptionAsMap(description) {
description.split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
private getHeader(userpass = null){
def headers = [:]
headers.put("Host", getHostAddress())
headers.put("Content-Type", "application/x-www-form-urlencoded")
if (userpass != null)
headers.put("Authorization", userpass)
return headers
}
def toAscii(s){
StringBuilder sb = new StringBuilder();
String ascString = null;
long asciiInt;
for (int i = 0; i < s.length(); i++){
sb.append((int)s.charAt(i));
sb.append("|");
char c = s.charAt(i);
}
ascString = sb.toString();
asciiInt = Long.parseLong(ascString);
return asciiInt;
}