mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-14 13:11:52 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8570c84d37 |
@@ -1,627 +0,0 @@
|
||||
/**
|
||||
* Spruce Controller - Pre Release V2 10/11/2015
|
||||
*
|
||||
* Copyright 2015 Plaid Systems
|
||||
*
|
||||
* Author: NC
|
||||
* Date: 2015-11
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
-----------V3 updates-11-2015------------
|
||||
-Start program button updated to signal schedule check in Scheduler
|
||||
11/17 alarm "0" -> 0 (ln 305)
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Spruce Controller", namespace: "plaidsystems", author: "NCauffman") {
|
||||
capability "Switch"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Actuator"
|
||||
capability "Valve"
|
||||
|
||||
attribute "switch", "string"
|
||||
attribute "switch1", "string"
|
||||
attribute "switch2", "string"
|
||||
attribute "switch8", "string"
|
||||
attribute "switch5", "string"
|
||||
attribute "switch3", "string"
|
||||
attribute "switch4", "string"
|
||||
attribute "switch6", "string"
|
||||
attribute "switch7", "string"
|
||||
attribute "switch9", "string"
|
||||
attribute "switch10", "string"
|
||||
attribute "switch11", "string"
|
||||
attribute "switch12", "string"
|
||||
attribute "switch13", "string"
|
||||
attribute "switch14", "string"
|
||||
attribute "switch15", "string"
|
||||
attribute "switch16", "string"
|
||||
attribute "status", "string"
|
||||
|
||||
command "programOn"
|
||||
command "programOff"
|
||||
command "on"
|
||||
command "off"
|
||||
command "z1on"
|
||||
command "z1off"
|
||||
command "z2on"
|
||||
command "z2off"
|
||||
command "z3on"
|
||||
command "z3off"
|
||||
command "z4on"
|
||||
command "z4off"
|
||||
command "z5on"
|
||||
command "z5off"
|
||||
command "z6on"
|
||||
command "z6off"
|
||||
command "z7on"
|
||||
command "z7off"
|
||||
command "z8on"
|
||||
command "z8off"
|
||||
command "z9on"
|
||||
command "z9off"
|
||||
command "z10on"
|
||||
command "z10off"
|
||||
command "z11on"
|
||||
command "z11off"
|
||||
command "z12on"
|
||||
command "z12off"
|
||||
command "z13on"
|
||||
command "z13off"
|
||||
command "z14on"
|
||||
command "z14off"
|
||||
command "z15on"
|
||||
command "z15off"
|
||||
command "z16on"
|
||||
command "z16off"
|
||||
command "offtime"
|
||||
|
||||
command "refresh"
|
||||
command "rain"
|
||||
command "manual"
|
||||
command "setDisplay"
|
||||
|
||||
command "settingsMap"
|
||||
command "writeTime"
|
||||
command "writeType"
|
||||
command "notify"
|
||||
command "updated"
|
||||
|
||||
fingerprint endpointId: "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18", profileId: "0104", deviceId: "0002", deviceVersion: "00", inClusters: "0000,0003,0004,0005,0006,000F", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZ16-01"
|
||||
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
|
||||
// reply messages
|
||||
|
||||
}
|
||||
preferences {
|
||||
input description: "Press Configure button after making changes to these preferences", displayDuringSetup: true, type: "paragraph", element: "paragraph", title: ""
|
||||
input "RainEnable", "bool", title: "Rain Sensor Attached?", required: false, displayDuringSetup: true
|
||||
input "ManualTime", "number", title: "Automatic shutoff time when a zone is turned on manually?", required: false, displayDuringSetup: true
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
|
||||
standardTile("status", "device.status") {
|
||||
state "schedule", label: 'Schedule Set', icon: "http://www.plaidsystems.com/smartthings/st_spruce_leaf_225_t.png"
|
||||
state "finished", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#46c2e8"
|
||||
state "raintoday", label: 'Rain Today', icon: "st.custom.wuk.nt_chancerain"
|
||||
state "rainy", label: 'Previous Rain', icon: "st.custom.wuk.nt_chancerain"
|
||||
state "raintom", label: 'Rain Tomorrow', icon: "st.custom.wuk.nt_chancerain"
|
||||
state "donewweek", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#52c435"
|
||||
state "skipping", label: 'Skip Today', icon: "st.Outdoor.outdoor20", backgroundColor: "#36cfe3"
|
||||
state "moisture", label: '', icon: "st.Weather.weather2", backgroundColor: "#36cfe3"
|
||||
state "pause", label: 'PAUSE', icon: "st.contact.contact.open", backgroundColor: "#f2a51f"
|
||||
state "active", label: 'Active', icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
|
||||
state "season", label: 'Seasonal Adjustment', icon: "st.Outdoor.outdoor17", backgroundColor: "#ffb900"
|
||||
state "disable", label: 'Disabled', icon: "st.secondary.off", backgroundColor: "#888888"
|
||||
state "warning", label: '', icon: "st.categories.damageAndDanger", backgroundColor: "#ffff7f"
|
||||
state "alarm", label: 'Alarm', icon: "st.categories.damageAndDanger", backgroundColor: "#f9240c"
|
||||
}
|
||||
standardTile("switch", "device.switch") {
|
||||
//state "programOff", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
|
||||
state "off", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
|
||||
state "programOn", label: 'Initialize Program', action: "programOff", icon: "st.contact.contact.open", backgroundColor: "#f6e10e"
|
||||
state "on", label: 'Program Running', action: "off", icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
|
||||
}
|
||||
standardTile("rainsensor", "device.rainsensor") {
|
||||
state "rainSensrooff", label: 'Rain Sensor Clear', icon: "st.Weather.weather14", backgroundColor: "#a9a9a9"
|
||||
state "rainSensoron", label: 'Rain Detected', icon: "st.Weather.weather10", backgroundColor: "#f6e10e"
|
||||
}
|
||||
standardTile("switch1", "device.switch1") {
|
||||
state "z1off", label: '1', action: "z1on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z1on", label: '1', action: "z1off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch2", "device.switch2") {
|
||||
state "z2off", label: '2', action: "z2on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z2on", label: '2', action: "z2off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch3", "device.switch3", inactiveLabel: false) {
|
||||
state "z3off", label: '3', action: "z3on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z3on", label: '3', action: "z3off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch4", "device.switch4", inactiveLabel: false) {
|
||||
state "z4off", label: '4', action: "z4on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z4on", label: '4', action: "z4off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch5", "device.switch5", inactiveLabel: false) {
|
||||
state "z5off", label: '5', action: "z5on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z5on", label: '5', action: "z5off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch6", "device.switch6", inactiveLabel: false) {
|
||||
state "z6off", label: '6', action: "z6on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z6on", label: '6', action: "z6off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch7", "device.switch7", inactiveLabel: false) {
|
||||
state "z7off", label: '7', action: "z7on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z7on", label: '7', action: "z7off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch8", "device.switch8", inactiveLabel: false) {
|
||||
state "z8off", label: '8', action: "z8on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z8on", label: '8', action: "z8off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch9", "device.switch9", inactiveLabel: false) {
|
||||
state "z9off", label: '9', action: "z9on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z9on", label: '9', action: "z9off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch10", "device.switch10", inactiveLabel: false) {
|
||||
state "z10off", label: '10', action: "z10on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z10on", label: '10', action: "z10off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch11", "device.switch11", inactiveLabel: false) {
|
||||
state "z11off", label: '11', action: "z11on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z11on", label: '11', action: "z11off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch12", "device.switch12", inactiveLabel: false) {
|
||||
state "z12off", label: '12', action: "z12on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z12on", label: '12', action: "z12off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch13", "device.switch13", inactiveLabel: false) {
|
||||
state "z13off", label: '13', action: "z13on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z13on", label: '13', action: "z13off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch14", "device.switch14", inactiveLabel: false) {
|
||||
state "z14off", label: '14', action: "z14on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z14on", label: '14', action: "z14off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch15", "device.switch15", inactiveLabel: false) {
|
||||
state "z15off", label: '15', action: "z15on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z15on", label: '15', action: "z15off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch16", "device.switch16", inactiveLabel: false) {
|
||||
state "z16off", label: '16', action: "z16on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z16on", label: '16', action: "z16off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", action: "refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
|
||||
main (["status"])
|
||||
details(["status","rainsensor","switch","switch1","switch2","switch3","switch4","switch5","switch6","switch7","switch8","switch9","switch10","switch11","switch12","switch13","switch14","switch15","switch16","refresh","configure"])
|
||||
}
|
||||
}
|
||||
|
||||
def programOn(){
|
||||
sendEvent(name: "switch", value: "programOn", descriptionText: "Program turned on")
|
||||
}
|
||||
|
||||
def programOff(){
|
||||
sendEvent(name: "switch", value: "off", descriptionText: "Program turned off")
|
||||
off()
|
||||
}
|
||||
|
||||
def updated(){
|
||||
log.debug "updated"
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
//log.debug "Parse description $description"
|
||||
def result = null
|
||||
def map = [:]
|
||||
if (description?.startsWith("read attr -")) {
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
//log.debug "Desc Map: $descMap"
|
||||
//using 000F cluster instead of 0006 (switch) because ST does not differentiate between EPs and processes all as switch
|
||||
if (descMap.cluster == "000F" && descMap.attrId == "0055") {
|
||||
log.debug "Zone"
|
||||
map = getZone(descMap)
|
||||
}
|
||||
else if (descMap.cluster == "0009" && descMap.attrId == "0000") {
|
||||
log.debug "Alarm"
|
||||
map = getAlarm(descMap)
|
||||
}
|
||||
}
|
||||
|
||||
if (map) {
|
||||
result = createEvent(map)
|
||||
}
|
||||
log.debug "Parse returned $map $result"
|
||||
return result
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
def getZone(descMap){
|
||||
def map = [:]
|
||||
|
||||
def EP = Integer.parseInt(descMap.endpoint.trim(), 16)
|
||||
|
||||
String onoff
|
||||
if(descMap.value == "00"){
|
||||
onoff = "off"
|
||||
}
|
||||
else onoff = "on"
|
||||
|
||||
if (EP == 1){
|
||||
map.name = "switch"
|
||||
map.value = onoff
|
||||
map.descriptionText = "${device.displayName} turned sprinkler program $onoff"
|
||||
}
|
||||
|
||||
else if (EP == 18) {
|
||||
map.name = "rainsensor"
|
||||
map.value = "rainSensor" + onoff
|
||||
map.descriptionText = "${device.displayName} rain sensor is $onoff"
|
||||
}
|
||||
else {
|
||||
EP -= 1
|
||||
map.name = "switch" + EP
|
||||
map.value = "z" + EP + onoff
|
||||
map.descriptionText = "${device.displayName} turned Zone $EP $onoff"
|
||||
}
|
||||
|
||||
map.isStateChange = true
|
||||
map.displayed = true
|
||||
return map
|
||||
}
|
||||
|
||||
def getAlarm(descMap){
|
||||
def map = [:]
|
||||
map.name = "status"
|
||||
def alarmID = Integer.parseInt(descMap.value.trim(), 16)
|
||||
log.debug "${alarmID}"
|
||||
if(alarmID <= 0) map.descriptionText = "${device.displayName} has rebooted, no other alarms"
|
||||
else map.descriptionText = "${device.displayName} rebooted, reported error on zone ${alarmID - 1}, please check zone is working correctly"
|
||||
map.value = "alarm"
|
||||
map.isStateChange = true
|
||||
map.displayed = true
|
||||
return map
|
||||
}
|
||||
|
||||
//status notify and change status
|
||||
def notify(value, text){
|
||||
sendEvent(name:"status", value:"$value", descriptionText:"$text", isStateChange: true, display: false)
|
||||
|
||||
}
|
||||
|
||||
//prefrences - rain sensor, manual time
|
||||
def rain() {
|
||||
log.debug "Rain $RainEnable"
|
||||
if (RainEnable) "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {01}"
|
||||
else "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {00}"
|
||||
}
|
||||
def manual(){
|
||||
log.debug "Time $ManualTime"
|
||||
def mTime = 10
|
||||
if (ManualTime) mTime = ManualTime
|
||||
def manualTime = hex(mTime)
|
||||
"st wattr 0x${device.deviceNetworkId} 1 6 0x4002 0x21 {00${manualTime}}"
|
||||
|
||||
}
|
||||
|
||||
//write switch time settings map
|
||||
def settingsMap(WriteTimes, attrType){
|
||||
log.debug WriteTimes
|
||||
|
||||
def i = 1
|
||||
def runTime
|
||||
def sendCmds = []
|
||||
while(i <= 17){
|
||||
|
||||
if (WriteTimes."${i}"){
|
||||
runTime = hex(Integer.parseInt(WriteTimes."${i}"))
|
||||
log.debug "${i} : $runTime"
|
||||
|
||||
if (attrType == 4001) sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4001 0x21 {00${runTime}}")
|
||||
else sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4002 0x21 {00${runTime}}")
|
||||
sendCmds.push("delay 500")
|
||||
}
|
||||
i++
|
||||
}
|
||||
return sendCmds
|
||||
}
|
||||
|
||||
//send switch time
|
||||
def writeType(wEP, cycle){
|
||||
log.debug "wt ${wEP} ${cycle}"
|
||||
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4001 0x21 {00" + hex(cycle) + "}"
|
||||
}
|
||||
//send switch off time
|
||||
def writeTime(wEP, runTime){
|
||||
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4002 0x21 {00" + hex(runTime) + "}"
|
||||
}
|
||||
|
||||
//set reporting and binding
|
||||
def configure() {
|
||||
|
||||
String zigbeeId = swapEndianHex(device.hub.zigbeeId)
|
||||
log.debug "Confuguring Reporting and Bindings ${device.deviceNetworkId} ${device.zigbeeId}"
|
||||
sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
|
||||
|
||||
def configCmds = [
|
||||
//program on/off
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x09 {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
//zones 1-8
|
||||
"zdo bind 0x${device.deviceNetworkId} 2 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 3 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 4 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 5 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 6 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 7 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 8 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 9 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
//zones 9-16
|
||||
"zdo bind 0x${device.deviceNetworkId} 10 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 11 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 12 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 13 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 14 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 15 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 16 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 17 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
//rain sensor
|
||||
"zdo bind 0x${device.deviceNetworkId} 18 1 0x0F {${device.zigbeeId}} {}",
|
||||
|
||||
"zcl global send-me-a-report 6 0 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 2", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 3", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 4", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 5", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 6", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 7", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 8", "delay 500",
|
||||
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 9", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 10", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 11", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 12", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 13", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 14", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 15", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 16", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 17", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 18", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x09 0x00 0x21 1 0 {00}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
]
|
||||
return configCmds + rain() + manual()
|
||||
}
|
||||
|
||||
|
||||
|
||||
private hex(value) {
|
||||
new BigInteger(Math.round(value).toString()).toString(16)
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
|
||||
log.debug "refresh"
|
||||
def refreshCmds = [
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x0F 0x55", "delay 500",
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 2 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 3 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 4 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 5 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 6 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 7 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 8 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 9 0x0F 0x55", "delay 500",
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 10 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 11 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 12 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 13 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 14 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 15 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 16 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 17 0x0F 0x55", "delay 500",
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 18 0x0F 0x51","delay 500",
|
||||
|
||||
]
|
||||
return refreshCmds + rain() + manual()
|
||||
}
|
||||
|
||||
// Commands to device
|
||||
//zones on - 8
|
||||
def on() {
|
||||
//sendEvent(name:"status", value:"active", descriptionText:"Program Running", isStateChange: true, display: false)
|
||||
log.debug "on"
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||
}
|
||||
def off() {
|
||||
log.debug "off"
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||
}
|
||||
def z1on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 2 6 1 {}"
|
||||
}
|
||||
def z1off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 2 6 0 {}"
|
||||
}
|
||||
def z2on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 3 6 1 {}"
|
||||
}
|
||||
def z2off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 3 6 0 {}"
|
||||
}
|
||||
def z3on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 4 6 1 {}"
|
||||
}
|
||||
def z3off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 4 6 0 {}"
|
||||
}
|
||||
def z4on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 5 6 1 {}"
|
||||
}
|
||||
def z4off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 5 6 0 {}"
|
||||
}
|
||||
def z5on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 6 6 1 {}"
|
||||
}
|
||||
def z5off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 6 6 0 {}"
|
||||
}
|
||||
def z6on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 7 6 1 {}"
|
||||
}
|
||||
def z6off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 7 6 0 {}"
|
||||
}
|
||||
def z7on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 8 6 1 {}"
|
||||
}
|
||||
def z7off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 8 6 0 {}"
|
||||
}
|
||||
def z8on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 9 6 1 {}"
|
||||
}
|
||||
def z8off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 9 6 0 {}"
|
||||
}
|
||||
|
||||
//zones 9 - 16
|
||||
def z9on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 10 6 1 {}"
|
||||
}
|
||||
def z9off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 10 6 0 {}"
|
||||
}
|
||||
def z10on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 11 6 1 {}"
|
||||
}
|
||||
def z10off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 11 6 0 {}"
|
||||
}
|
||||
def z11on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 12 6 1 {}"
|
||||
}
|
||||
def z11off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 12 6 0 {}"
|
||||
}
|
||||
def z12on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 13 6 1 {}"
|
||||
}
|
||||
def z12off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 13 6 0 {}"
|
||||
}
|
||||
def z13on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 14 6 1 {}"
|
||||
}
|
||||
def z13off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 14 6 0 {}"
|
||||
}
|
||||
def z14on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 15 6 1 {}"
|
||||
}
|
||||
def z14off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 15 6 0 {}"
|
||||
}
|
||||
def z15on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 16 6 1 {}"
|
||||
}
|
||||
def z15off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 16 6 0 {}"
|
||||
}
|
||||
def z16on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 17 6 1 {}"
|
||||
}
|
||||
def z16off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 17 6 0 {}"
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
/**
|
||||
* Spruce Sensor -Pre-release V2 10/8/2015
|
||||
*
|
||||
* Copyright 2014 Plaid Systems
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
-------10/20/2015 Updates--------
|
||||
-Fix/add battery reporting interval to update
|
||||
-remove polling and/or refresh(?)
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") {
|
||||
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Temperature Measurement"
|
||||
capability "Sensor"
|
||||
//capability "Polling"
|
||||
|
||||
attribute "maxHum", "string"
|
||||
attribute "minHum", "string"
|
||||
|
||||
command "resetHumidity"
|
||||
command "refresh"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01"
|
||||
}
|
||||
|
||||
preferences {
|
||||
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph", title: ""
|
||||
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
input "interval", "number", title: "Measurement Interval 1-120 minutes (default: 10 minutes)", description: "Set how often you would like to check soil moisture in minutes", range: "1..120", defaultValue: 10, displayDuringSetup: false
|
||||
input "resetMinMax", "bool", title: "Reset Humidity min and max", required: false, displayDuringSetup: false
|
||||
}
|
||||
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "temperature", label:'${currentValue}°',
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
}
|
||||
valueTile("humidity", "device.humidity", width: 2, height: 2, canChangeIcon: false, canChangeBackground: true) {
|
||||
state "humidity", label:'${currentValue}%', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#635C0C"],
|
||||
[value: 16, color: "#EBEB21"],
|
||||
[value: 22, color: "#C7DE6A"],
|
||||
[value: 42, color: "#9AD290"],
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("maxHum", "device.maxHum", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "maxHum", label:'High ${currentValue}%', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#635C0C"],
|
||||
[value: 16, color: "#EBEB21"],
|
||||
[value: 22, color: "#C7DE6A"],
|
||||
[value: 42, color: "#9AD290"],
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
}
|
||||
valueTile("minHum", "device.minHum", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "minHum", label:'Low ${currentValue}%', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#635C0C"],
|
||||
[value: 16, color: "#EBEB21"],
|
||||
[value: 22, color: "#C7DE6A"],
|
||||
[value: 42, color: "#9AD290"],
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", decoration: "flat", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "battery", label:'${currentValue}% battery'
|
||||
}
|
||||
|
||||
main (["humidity"])
|
||||
details(["humidity","maxHum","minHum","temperature","battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "Parse description $description config: ${device.latestValue('configuration')} interval: $interval"
|
||||
|
||||
Map map = [:]
|
||||
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
map = parseReportAttributeMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
//check in configuration change
|
||||
if (!device.latestValue('configuration')) result = poll()
|
||||
if (device.latestValue('configuration').toInteger() != interval && interval != null) {
|
||||
result = poll()
|
||||
}
|
||||
log.debug "result: $result"
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def linkText = getLinkText(device)
|
||||
//log.debug "Catchall"
|
||||
def descMap = zigbee.parse(description)
|
||||
|
||||
//check humidity configuration is complete
|
||||
if (descMap.command == 0x07 && descMap.clusterId == 0x0405){
|
||||
def configInterval = 10
|
||||
if (interval != null) configInterval = interval
|
||||
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration Successful")
|
||||
//setConfig()
|
||||
log.debug "config complete"
|
||||
//return resultMap = [name: 'configuration', value: configInterval, descriptionText: "Settings configured successfully"]
|
||||
}
|
||||
else if (descMap.command == 0x0001){
|
||||
def hexString = "${hex(descMap.data[5])}" + "${hex(descMap.data[4])}"
|
||||
def intString = Integer.parseInt(hexString, 16)
|
||||
//log.debug "command: $descMap.command clusterid: $descMap.clusterId $hexString $intString"
|
||||
|
||||
if (descMap.clusterId == 0x0402){
|
||||
def value = getTemperature(hexString)
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
else if (descMap.clusterId == 0x0405){
|
||||
def value = Math.round(new BigDecimal(intString / 100)).toString()
|
||||
resultMap = getHumidityResult(value)
|
||||
|
||||
}
|
||||
else return null
|
||||
}
|
||||
else return null
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
log.debug "Desc Map: $descMap"
|
||||
log.debug "Report Attributes"
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0001" && descMap.attrId == "0000") {
|
||||
resultMap = getBatteryResult(descMap.value)
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
|
||||
log.debug "parseCustom"
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
else if (description?.startsWith('humidity: ')) {
|
||||
def pct = (description - "humidity: " - "%").trim()
|
||||
if (pct.isNumber()) {
|
||||
def value = Math.round(new BigDecimal(pct)).toString()
|
||||
resultMap = getHumidityResult(value)
|
||||
} else {
|
||||
log.error "invalid humidity: ${pct}"
|
||||
}
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private Map getHumidityResult(value) {
|
||||
def linkText = getLinkText(device)
|
||||
def maxHumValue = 0
|
||||
def minHumValue = 0
|
||||
if (device.currentValue("maxHum") != null) maxHumValue = device.currentValue("maxHum").toInteger()
|
||||
if (device.currentValue("minHum") != null) minHumValue = device.currentValue("minHum").toInteger()
|
||||
log.debug "Humidity max: ${maxHumValue} min: ${minHumValue}"
|
||||
def compare = value.toInteger()
|
||||
|
||||
if (compare > maxHumValue) {
|
||||
sendEvent(name: 'maxHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture high is ${value}%")
|
||||
}
|
||||
else if (((compare < minHumValue) || (minHumValue <= 2)) && (compare != 0)) {
|
||||
sendEvent(name: 'minHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture low is ${value}%")
|
||||
}
|
||||
|
||||
return [
|
||||
name: 'humidity',
|
||||
value: value,
|
||||
unit: '%',
|
||||
descriptionText: "${linkText} soil moisture is ${value}%"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = (Integer.parseInt(value, 16).shortValue()/100)
|
||||
//log.debug "Report Temp $value : $celsius C"
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celsius
|
||||
} else {
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
}
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature: $value"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = "${linkText} is ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
private Map getBatteryResult(value) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
def min = 2500
|
||||
def percent = ((Integer.parseInt(value, 16) - min) / 5)
|
||||
percent = Math.max(0, Math.min(percent, 100.0))
|
||||
result.value = Math.round(percent)
|
||||
|
||||
def descriptionText
|
||||
if (percent < 10) result.descriptionText = "${linkText} battery is getting low $percent %."
|
||||
else result.descriptionText = "${linkText} battery is ${result.value}%"
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def resetHumidity(){
|
||||
def linkText = getLinkText(device)
|
||||
def minHumValue = 0
|
||||
def maxHumValue = 0
|
||||
sendEvent(name: 'minHum', value: minHumValue, unit: '%', descriptionText: "${linkText} min soil moisture reset to ${minHumValue}%")
|
||||
sendEvent(name: 'maxHum', value: maxHumValue, unit: '%', descriptionText: "${linkText} max soil moisture reset to ${maxHumValue}%")
|
||||
}
|
||||
|
||||
def setConfig(){
|
||||
def configInterval = 100
|
||||
if (interval != null) configInterval = interval
|
||||
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized")
|
||||
}
|
||||
|
||||
//when device preferences are changed
|
||||
def updated(){
|
||||
log.debug "device updated"
|
||||
if (!device.latestValue('configuration')) configure()
|
||||
else{
|
||||
if (resetMinMax == true) resetHumidity()
|
||||
if (device.latestValue('configuration').toInteger() != interval && interval != null){
|
||||
sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//poll
|
||||
def poll() {
|
||||
log.debug "poll called"
|
||||
List cmds = []
|
||||
if (!device.latestValue('configuration')) cmds += configure()
|
||||
else if (device.latestValue('configuration').toInteger() != interval && interval != null) {
|
||||
cmds += intervalUpdate()
|
||||
}
|
||||
//cmds += refresh()
|
||||
log.debug "commands $cmds"
|
||||
return cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
|
||||
//update intervals
|
||||
def intervalUpdate(){
|
||||
log.debug "intervalUpdate"
|
||||
def minReport = 10
|
||||
def maxReport = 610
|
||||
if (interval != null) {
|
||||
minReport = interval
|
||||
maxReport = interval * 61
|
||||
}
|
||||
[
|
||||
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
]
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh"
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x405 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0"
|
||||
]
|
||||
}
|
||||
|
||||
//configure
|
||||
def configure() {
|
||||
//set minReport = measurement in minutes
|
||||
def minReport = 10
|
||||
def maxReport = 610
|
||||
|
||||
//String zigbeeId = swapEndianHex(device.hub.zigbeeId)
|
||||
//log.debug "zigbeeid ${device.zigbeeId} deviceId ${device.deviceNetworkId}"
|
||||
if (!device.zigbeeId) sendEvent(name: 'configuration',value: 0, descriptionText: "Device Zigbee Id not found, remove and attempt to rejoin device")
|
||||
else sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
|
||||
//log.debug "Configuring Reporting and Bindings. min: $minReport max: $maxReport "
|
||||
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x405 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 1000",
|
||||
|
||||
//temperature
|
||||
"zcl global send-me-a-report 0x402 0x0000 0x29 1 0 {3200}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
//min = soil measure interval
|
||||
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
//min = battery measure interval 1 = 1 hour
|
||||
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
] + refresh()
|
||||
}
|
||||
|
||||
private hex(value) {
|
||||
new BigInteger(Math.round(value).toString()).toString(16)
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
@@ -80,12 +80,19 @@ def parse(String description) {
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
|
||||
// log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.multiinstancev1.MultiInstanceCmdEncap cmd) {
|
||||
def encapsulated = cmd.encapsulatedCommand([0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
|
||||
def encapsulated = null
|
||||
if (cmd.respondsTo("encapsulatedCommand")) {
|
||||
encapsulated = cmd.encapsulatedCommand()
|
||||
} else {
|
||||
def hex1 = { n -> String.format("%02X", n) }
|
||||
def sorry = "command: ${hex1(cmd.commandClass)}${hex1(cmd.command)}, payload: " + cmd.parameter.collect{ hex1(it) }.join(" ")
|
||||
encapsulated = zwave.parse(sorry, [0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
|
||||
}
|
||||
return encapsulated ? zwaveEvent(encapsulated) : null
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* Navien Thermostat
|
||||
*
|
||||
* Author: Navien Within
|
||||
* Date: 2015-11-02
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Navien Room Controller", namespace: "smartthings", author: "Navien") {
|
||||
capability "Thermostat"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
|
||||
command "generateEvent"
|
||||
command "powerONOFF"
|
||||
command "setRoomTemp"
|
||||
command "setOndolTemp"
|
||||
command "controlMode1"
|
||||
command "controlMode2"
|
||||
|
||||
attribute "setRoomSlider", "NUMBER"
|
||||
attribute "setOndolSlider", "NUMBER"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator { }
|
||||
|
||||
//tiles(scale: 2) {
|
||||
tiles {
|
||||
/*
|
||||
multiAttributeTile(name: "temperature", type: "thermostat", canChangeIcon: true, canChangeBackground: true) {
|
||||
tileAttribute("temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState "temperature", label: '${currentValue}°', icon:"st.Home.home1", backgroundColor:"#FFA81E"
|
||||
}
|
||||
tileAttribute ("statusText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "statusText", label: 'statusText'
|
||||
}
|
||||
tileAttribute("button", key: "VALUE_CONTROL"){
|
||||
attributeState "setUp"
|
||||
attributeState "setDown"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
valueTile("thermostatStatus", "device.thermostatStatus", width: 3, height: 2) {
|
||||
state "전원 OFF", label:'${currentValue}', icon:"st.Navien.bgs_power_off", backgroundColor:"#BDBDBD"
|
||||
state "외출 ON", label:'${currentValue}', icon:"st.Navien.bgs_out", backgroundColor:"#FF8C17"
|
||||
state "실내난방", label:'${currentValue}', icon:"st.Navien.bgs_indoor", backgroundColor:"#FF8C17"
|
||||
state "온돌난방", label:'${currentValue}', icon:"st.Navien.bgs_ondol", backgroundColor:"#FF8C17"
|
||||
state "반복예약난방", label:'${currentValue}', icon:"st.Navien.bgs_heating_again", backgroundColor:"#FF8C17"
|
||||
state "24시간예약난방", label:'${currentValue}', icon:"st.Navien.bgs_24heat", backgroundColor:"#FF8C17"
|
||||
state "간편예약난방", label:'${currentValue}', icon:"st.Navien.bgs_heat", backgroundColor:"#FF8C17"
|
||||
state "온수전용", label:'${currentValue}', icon:"st.Navien.bgs_water", backgroundColor:"#FF8C17"
|
||||
state "빠른온수", label:'${currentValue}', icon:"st.Navien.bgs_water_fast", backgroundColor:"#FF8C17"
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", width: 1, height: 1, inactiveLabel: false) {
|
||||
state "OFF", label:'', unit:"C", icon:"st.Navien.bg_recent_off"
|
||||
state "default", label:'${currentValue}°', unit:"C", icon:"st.Navien.bg_recent"
|
||||
}
|
||||
|
||||
valueTile("hotWater", "device.hotWater", width: 1, height: 1) {
|
||||
state "OFF", label:'', unit:"C", icon:"st.Navien.bg_water_off"
|
||||
state "default", label:'${currentValue}°', unit:"C", icon:"st.Navien.bg_water"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", , width: 1, height: 1, inactiveLabel: false) {
|
||||
state "default", action:"refresh.refresh", icon:"st.Navien.but_refresh"
|
||||
}
|
||||
|
||||
valueTile("setRoomTemp", "device.setRoomTemp", width: 1, height:1) {
|
||||
state "OFF", label:'', icon:"st.Navien.bg_indoor_off"
|
||||
state "default", label:'${currentValue}°', icon:"st.Navien.bg_indoor"
|
||||
}
|
||||
|
||||
controlTile("setRoomSlider", "device.setRoomSlider", "slider", height: 1, width: 2, inactiveLabel: false, range:"(10..40)") {
|
||||
state "default", action:"setRoomTemp", backgroundColor:"#F08C00"
|
||||
}
|
||||
|
||||
valueTile("setOndolTemp", "device.setOndolTemp", width: 1, height:1) {
|
||||
state "OFF", label:'', icon:"st.Navien.bg_ondol"
|
||||
state "default", label:'${currentValue}°', icon:"st.Navien.bg_ondol_off"
|
||||
}
|
||||
|
||||
controlTile("setOndolSlider", "device.setOndolSlider", "slider", height: 1, width: 2, inactiveLabel: false, range:"(40..83)") {
|
||||
state "default", action:"setOndolTemp", backgroundColor:"#F08C00"
|
||||
}
|
||||
|
||||
standardTile("power", "device.power", width: 1, height: 1, inactiveLabel: false) {
|
||||
state "ON", label:'', action:"powerONOFF", icon:"st.Navien.but_power"
|
||||
state "default", label:'', action:"powerONOFF", icon:"st.Navien.but_power_off"
|
||||
}
|
||||
|
||||
standardTile("controlMode1", "device.controlMode1", width: 1, height: 1, inactiveLabel: false) {
|
||||
state "실내난방 ON", label:'', action:"controlMode1", icon:"st.Navien.but_indoor"
|
||||
state "온돌난방 ON", label:'', action:"controlMode1", icon:"st.Navien.but_ondol"
|
||||
state "default", label:'', icon:"st.Navien.but_indoor_off"
|
||||
}
|
||||
|
||||
standardTile("controlMode2", "device.controlMode2", width: 1, height: 1, inactiveLabel: false) {
|
||||
state "외출해제", label:'', action:"controlMode2", icon:"st.Navien.but_out_off"
|
||||
state "외출설정", label:'', action:"controlMode2", icon:"st.Navien.but_out"
|
||||
state "default", label:'', icon:"st.Navien.but_out_dis"
|
||||
}
|
||||
|
||||
valueTile("herotile", "device.herotile", width: 3, height:1) {
|
||||
state "default", label:'', icon:"st.Navien.bg_herotile"
|
||||
}
|
||||
|
||||
main "thermostatStatus"
|
||||
details(["thermostatStatus", "temperature", "hotWater", "refresh", "setRoomTemp", "setRoomSlider", "setOndolTemp", "setOndolSlider", "power", "controlMode1", "controlMode2", "herotile"])
|
||||
}
|
||||
}
|
||||
|
||||
def powerONOFF()
|
||||
{
|
||||
log.debug "powerONOFF called"
|
||||
def powerStatus = device.currentValue("power")
|
||||
def results
|
||||
if(powerStatus == "ON")
|
||||
{
|
||||
results = parent.childRequest(this, "1", "33", "0")
|
||||
}
|
||||
else if(powerStatus == "전원 OFF")
|
||||
{
|
||||
results = parent.childRequest(this, "1", "34", "0")
|
||||
}
|
||||
generateEvent(results)
|
||||
log.debug "powerONOFF ended"
|
||||
}
|
||||
|
||||
def setRoomTemp(degrees)
|
||||
{
|
||||
def degreesInteger = degrees as Integer
|
||||
sendEvent("name":"setRoomSlider", "value":degreesInteger)
|
||||
|
||||
def status = device.currentValue("thermostatStatus")
|
||||
if(status == "실내난방")
|
||||
{
|
||||
def results = parent.childRequest(this, "1", "44", degreesInteger*2)
|
||||
generateEvent(results)
|
||||
}
|
||||
}
|
||||
|
||||
def setOndolTemp(degrees)
|
||||
{
|
||||
def degreesInteger = degrees as Integer
|
||||
sendEvent("name":"setOndolSlider", "value":degreesInteger)
|
||||
|
||||
def status = device.currentValue("thermostatStatus")
|
||||
if(status == "온돌난방")
|
||||
{
|
||||
def results = parent.childRequest(this, "1", "36", degreesInteger*2)
|
||||
generateEvent(results)
|
||||
}
|
||||
}
|
||||
|
||||
def controlMode1()
|
||||
{
|
||||
log.debug "controlMode1"
|
||||
def control = device.currentValue("controlMode1")
|
||||
controlMode(control)
|
||||
}
|
||||
|
||||
def controlMode2()
|
||||
{
|
||||
log.debug "controlMode2"
|
||||
def control = device.currentValue("controlMode2")
|
||||
controlMode(control)
|
||||
}
|
||||
|
||||
def controlMode(control)
|
||||
{
|
||||
if(control == "실내난방 ON")
|
||||
{
|
||||
log.debug "실내난방 ON : 제어"
|
||||
def value = device.currentValue("setRoomSlider")*2
|
||||
def results = parent.childRequest(this, "1", "43", value)
|
||||
generateEvent(results)
|
||||
|
||||
}
|
||||
else if(control == "온돌난방 ON")
|
||||
{
|
||||
log.debug "온돌난방 ON : 제어"
|
||||
def value = device.currentValue("setOndolSlider")*2
|
||||
def results = parent.childRequest(this, "1", "35", value)
|
||||
generateEvent(results)
|
||||
|
||||
}
|
||||
else if(control == "외출해제")
|
||||
{
|
||||
log.debug "외출해제 : 제어"
|
||||
def results = parent.childRequest(this, "1", "46", "0")
|
||||
generateEvent(results)
|
||||
|
||||
}
|
||||
else if(control == "외출설정")
|
||||
{
|
||||
log.debug "외출설정 : 제어"
|
||||
def results = parent.childRequest(this, "1", "46", "1")
|
||||
generateEvent(results)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh()
|
||||
{
|
||||
log.debug "refresh called"
|
||||
poll()
|
||||
log.debug "refresh ended"
|
||||
}
|
||||
|
||||
void poll() {
|
||||
log.debug "Executing 'poll' using parent SmartApp"
|
||||
|
||||
def results = parent.pollChild(this)
|
||||
generateEvent(results)
|
||||
}
|
||||
|
||||
void generateEvent(Map results)
|
||||
{
|
||||
log.debug "parsing data $results"
|
||||
|
||||
if(results)
|
||||
{
|
||||
results.each { name, value ->
|
||||
def linkText = getLinkText(device)
|
||||
def isChange = true
|
||||
def isDisplayed = true
|
||||
|
||||
if (name=="temperature" || name=="hotWater")
|
||||
{
|
||||
//isChange = isTemperatureStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
|
||||
sendEvent(
|
||||
name: name,
|
||||
value: value,
|
||||
unit: "C",
|
||||
linkText: linkText,
|
||||
descriptionText: getThermostatDescriptionText(name, value, linkText),
|
||||
handlerName: name,
|
||||
isStateChange: isChange,
|
||||
displayed: isDisplayed)
|
||||
}
|
||||
else if (name=="thermostatStatus")
|
||||
{
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
|
||||
sendEvent(
|
||||
name: name,
|
||||
value: value.toString(),
|
||||
linkText: linkText,
|
||||
descriptionText: getThermostatDescriptionText(name, value, linkText),
|
||||
handlerName: name,
|
||||
isStateChange: isChange,
|
||||
displayed: isDisplayed)
|
||||
}
|
||||
}
|
||||
generateStatusEvent(results)
|
||||
}
|
||||
}
|
||||
|
||||
private getThermostatDescriptionText(name, value, linkText)
|
||||
{
|
||||
if(name == "temperature")
|
||||
{
|
||||
return "$linkText was $value°C"
|
||||
}
|
||||
else if(name == "roomTemp")
|
||||
{
|
||||
return "latest roomTemp setpoint was $value°C"
|
||||
}
|
||||
else if(name == "ondolTemp")
|
||||
{
|
||||
return "latest ondolTemp setpoint was $value°C"
|
||||
}
|
||||
else if (name == "thermostatMode")
|
||||
{
|
||||
return "thermostat mode is ${value}"
|
||||
}
|
||||
else
|
||||
{
|
||||
return "${name} = ${value}"
|
||||
}
|
||||
}
|
||||
|
||||
def generateStatusEvent(Map results) {
|
||||
log.debug "generateStatusEvent"
|
||||
|
||||
def status = results.thermostatStatus
|
||||
def setRoomTemp = results.roomTemp
|
||||
def setOndolTemp = results.ondolTemp
|
||||
|
||||
log.debug "status ===> ${status}"
|
||||
|
||||
if (status == "전원 OFF") {
|
||||
sendEvent("name":"temperature", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"hotWater", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"ON", "description":"전원 ON", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
|
||||
|
||||
}else if(status == "온수전용"){
|
||||
sendEvent("name":"setRoomTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"실내 ON", "description":"ON", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"온돌 ON", "description":"ON", displayed: true, isStateChange: true)
|
||||
|
||||
}else if(status == "외출 ON"){
|
||||
sendEvent("name":"setRoomTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"외출해제", "description":"외출해제", displayed: true, isStateChange: true)
|
||||
|
||||
}else if(status == "실내난방"){
|
||||
sendEvent("name":"setRoomTemp", "value":"${setRoomTemp}", "description":"설정온도", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":setRoomTemp, displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"${setOndolTemp}", "description":"설정온도", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":setOndolTemp, displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"온돌난방 ON", "description":"온돌난방 ON", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"외출설정", "description":"외출설정", displayed: true, isStateChange: true)
|
||||
|
||||
}
|
||||
else if(status == "온돌난방"){
|
||||
sendEvent("name":"setRoomTemp", "value":"${setRoomTemp}", "description":"설정온도", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":setRoomTemp, displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"${setOndolTemp}", "description":"설정온도", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":setOndolTemp, displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"실내난방 ON", "description":"실내난방 ON", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"외출설정", "description":"외출설정", displayed: true, isStateChange: true)
|
||||
|
||||
}
|
||||
|
||||
log.debug "Generate Status Event = ${status}"
|
||||
}
|
||||
@@ -5,7 +5,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0006, 0004, 0003, 0000, 0005", outClusters: "0019", manufacturer: "Compacta International, Ltd", model: "ZBMPlug15", deviceJoinName: "SmartPower Outlet V1"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0006", outClusters: "0019"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -441,28 +441,28 @@ def configure() {
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
|
||||
@@ -481,12 +481,17 @@ def enrollResponse() {
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
//Enroll Response
|
||||
"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||
"raw 0x500 {01 23 00 00 00}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||
]
|
||||
}
|
||||
|
||||
private Map parseAxis(String description) {
|
||||
def hexToSignedInt = { hexVal ->
|
||||
def unsignedVal = hexToInt(hexVal)
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
def z = hexToSignedInt(description[0..3])
|
||||
def y = hexToSignedInt(description[10..13])
|
||||
def x = hexToSignedInt(description[20..23])
|
||||
@@ -513,11 +518,6 @@ private Map parseAxis(String description) {
|
||||
getXyzResult(xyzResults, description)
|
||||
}
|
||||
|
||||
private hexToSignedInt(hexVal) {
|
||||
def unsignedVal = hexToInt(hexVal)
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
def garageEvent(zValue) {
|
||||
def absValue = zValue.abs()
|
||||
def contactValue = null
|
||||
|
||||
@@ -275,7 +275,6 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
|
||||
case 32:
|
||||
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
||||
allCodesDeleted()
|
||||
break
|
||||
case 33:
|
||||
map = [ name: "codeReport", value: cmd.alarmLevel, data: [ code: "" ], isStateChange: true ]
|
||||
map.descriptionText = "$device.displayName code $cmd.alarmLevel was deleted"
|
||||
@@ -342,14 +341,14 @@ def zwaveEvent(UserCodeReport cmd) {
|
||||
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: code ] ]
|
||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier is set"
|
||||
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
||||
map.isStateChange = true
|
||||
map.isStateChange = (code != decrypt(state[name]))
|
||||
}
|
||||
result << createEvent(map)
|
||||
} else {
|
||||
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: "" ] ]
|
||||
if (state.blankcodes && state["reset$name"]) { // we deleted this code so we can tell that our new code gets set
|
||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier was reset"
|
||||
map.displayed = map.isStateChange = true
|
||||
map.displayed = map.isStateChange = false
|
||||
result << createEvent(map)
|
||||
state["set$name"] = state["reset$name"]
|
||||
result << response(setCode(cmd.userIdentifier, state["reset$name"]))
|
||||
@@ -361,7 +360,7 @@ def zwaveEvent(UserCodeReport cmd) {
|
||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier is not set"
|
||||
}
|
||||
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
||||
map.isStateChange = true
|
||||
map.isStateChange = state[name] as Boolean
|
||||
result << createEvent(map)
|
||||
}
|
||||
code = ""
|
||||
|
||||
@@ -1,970 +0,0 @@
|
||||
/**
|
||||
* Total Comfort API
|
||||
*
|
||||
* Based on Code by Eric Thomas
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* lgk v 3 added optional outdoor temp sensors and preferences for it, also made api login required.
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
* lgk version 4 supports celsius and fahrenheit with option, and now colors.
|
||||
* lgk version 5, due to intermittant update failures added last update date/time tile so that you can see when it happended
|
||||
* not there is a new input tzoffset which defaults to my time ie -5 which you must set .
|
||||
* lgk version 6 add support for actually knowing the fan is on or not (added tile),
|
||||
* and also the actual operating state ie heating,cooling or idle via new response variables.
|
||||
* lgk version 7, change the new operating state to be a value vs standard tile
|
||||
* to work around a bug smartthings caused in the latest 2.08 release with text wrapping.
|
||||
* related also added icons to the operating state, and increase the width of the last update
|
||||
* to avoid wrapping.
|
||||
* 2-14-16 llb added full path to icons so the show on android Fire Tablet
|
||||
* 2-14-16 llb added check for indoor humidity sensor to avoid 128 value if not supported
|
||||
* 2-14-16 llb modified operating state tile to show control by Hold or Schedule
|
||||
*
|
||||
*/
|
||||
preferences {
|
||||
input("username", "text", title: "Username", description: "Your Total Comfort User Name", required: true)
|
||||
input("password", "password", title: "Password", description: "Your Total Comfort password",required: true)
|
||||
input("honeywelldevice", "text", title: "Device ID", description: "Your Device ID", required: true)
|
||||
input ("enableOutdoorTemps", "enum", title: "Do you have the optional outdoor temperature sensor and want to enable it?", options: ["Yes", "No"], required: false, defaultValue: "No")
|
||||
input ("tempScale", "enum", title: "Fahrenheit or Celsius?", options: ["F", "C"], required: false, defaultValue: "F")
|
||||
input("tzOffset", "number", title: "Time zone offset +/-xx?", required: false, defaultValue: -5, description: "Time Zone Offset ie -5.")
|
||||
}
|
||||
|
||||
metadata {
|
||||
definition (name: "Total Comfort API", namespace:
|
||||
"Total Comfort API", author: "Eric Thomas, modified lg kahn") {
|
||||
capability "Polling"
|
||||
capability "Thermostat"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Sensor"
|
||||
capability "Relative Humidity Measurement"
|
||||
command "heatLevelUp"
|
||||
command "heatLevelDown"
|
||||
command "coolLevelUp"
|
||||
command "coolLevelDown"
|
||||
attribute "outdoorHumidity", "number"
|
||||
attribute "outdoorTemperature", "number"
|
||||
attribute "lastUpdate", "string"
|
||||
|
||||
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2, canChangeIcon: true) {
|
||||
state("temperature", label: '${currentValue}°',
|
||||
icon: "http://cdn.device-icons.smartthings.com/Weather/weather2-icn@3x.png",
|
||||
unit:"F", backgroundColors: [
|
||||
[value: -14, color: "#1e9cbb"],
|
||||
[value: -10, color: "#90d2a7"],
|
||||
[value: -5, color: "#44b621"],
|
||||
[value: -2, color: "#f1d801"],
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 29, color: "#f1d801"],
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: false, canChangeIcon: true) {
|
||||
state "off", label:'${name}', action:"thermostat.cool", icon: "http://cdn.device-icons.smartthings.com/Outdoor/outdoor19.png"
|
||||
state "cool", label:'${name}', action:"thermostat.heat", icon: "http://cdn.device-icons.smartthings.com/Weather/weather7.png", backgroundColor: '#1e9cbb'
|
||||
state "heat", label:'${name}', action:"thermostat.auto", icon: "http://cdn.device-icons.smartthings.com/Weather/weather14.png", backgroundColor: '#E14902'
|
||||
state "auto", label:'${name}', action:"thermostat.off", icon: "http://cdn.device-icons.smartthings.com/Weather/weather3.png", backgroundColor: '#44b621'
|
||||
}
|
||||
standardTile("thermostatFanMode", "device.thermostatFanMode", inactiveLabel: false, canChangeIcon: true) {
|
||||
state "auto", label:'${name}', action:"thermostat.fanAuto", icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor: '#44b621'
|
||||
state "circulate", label:'${name}', action:"thermostat.fanCirculate", icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor: '#44b621'
|
||||
state "on", label:'${name}', action:"thermostat.fanOn", icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor: '#44b621'
|
||||
}
|
||||
|
||||
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 3, width: 1, inactiveLabel: false) {
|
||||
state "setCoolingSetpoint", label:'Set temperarure to', action:"thermostat.setCoolingSetpoint",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 29, color: "#f1d801"],
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
}
|
||||
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false)
|
||||
{
|
||||
state "default", label:'Cool\n${currentValue}°', unit:"F",
|
||||
backgroundColors: [
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 29, color: "#f1d801"],
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
}
|
||||
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false)
|
||||
{
|
||||
state "default", label:'Heat\n${currentValue}°', unit: "F",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 29, color: "#f1d801"],
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
//tile added for operating state - Create the tiles for each possible state, look at other examples if you wish to change the icons here.
|
||||
|
||||
valueTile("thermostatOperatingState", "device.thermostatOperatingState", inactiveLabel: false) {
|
||||
state 'default', label:'${currentValue}',
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#911535"]]
|
||||
state "Unknown", label:'${name}', backgroundColor : '#cc0000', icon: ""
|
||||
}
|
||||
|
||||
standardTile("fanOperatingState", "device.fanOperatingState", inactiveLabel: false) {
|
||||
state "On", label:'${name}',icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor : '#53a7c0'
|
||||
state "Idle", label:'${name}',icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png"
|
||||
state "Unknown", label:'${name}',icon: "http://cdn.device-icons.smartthings.com/Appliances/appliances11.png", backgroundColor : '#cc0000'
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", action:"polling.poll", icon:"http://cdn.device-icons.smartthings.com/secondary/refresh@2x.png"
|
||||
}
|
||||
|
||||
standardTile("heatLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
|
||||
state "heatLevelUp", label:' ', action:"heatLevelUp", icon:"http://cdn.device-icons.smartthings.com/thermostat/thermostat-up.png"
|
||||
}
|
||||
standardTile("heatLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
|
||||
state "heatLevelDown", label:' ', action:"heatLevelDown", icon:"http://cdn.device-icons.smartthings.com/thermostat/thermostat-down.png"
|
||||
}
|
||||
standardTile("coolLevelUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
|
||||
state "coolLevelUp", label:' ', action:"coolLevelUp", icon:"http://cdn.device-icons.smartthings.com/thermostat/thermostat-up.png"
|
||||
}
|
||||
standardTile("coolLevelDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false) {
|
||||
state "coolLevelDown", label:' ', action:"coolLevelDown", icon:"http://cdn.device-icons.smartthings.com/thermostat/thermostat-down.png"
|
||||
}
|
||||
|
||||
valueTile("relativeHumidity", "device.relativeHumidity", inactiveLabel: false)
|
||||
{
|
||||
state "default", label:'Humidity\n${currentValue}%',
|
||||
icon: "http://cdn.device-icons.smartthings.com/Weather/weather12-icn@3x.png",
|
||||
unit:"%", backgroundColors : [
|
||||
[value: 01, color: "#724529"],
|
||||
[value: 11, color: "#724529"],
|
||||
[value: 21, color: "#724529"],
|
||||
[value: 35, color: "#44b621"],
|
||||
[value: 49, color: "#44b621"],
|
||||
[value: 50, color: "#1e9cbb"]
|
||||
]
|
||||
}
|
||||
|
||||
standardTile("thermostatMode", "device.thermostatMode", inactiveLabel: false, canChangeIcon: true)
|
||||
{
|
||||
}
|
||||
|
||||
/* lgk new tiles for outside temp and hummidity */
|
||||
valueTile("outdoorTemperature", "device.outdoorTemperature", width: 1, height: 1, canChangeIcon: true) {
|
||||
state("temperature", label: 'Outdoor\n ${currentValue}°',
|
||||
icon: "http://cdn.device-icons.smartthings.com/Weather/weather2-icn@3x.png",
|
||||
unit:"F", backgroundColors: [
|
||||
[value: -31, color: "#003591"],
|
||||
[value: -10, color: "#90d2a7"],
|
||||
[value: -5, color: "#44b621"],
|
||||
[value: -2, color: "#f1d801"],
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 00, color: "#cccccc"],
|
||||
[value: 31, color: "#153500"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
valueTile("outdoorHumidity", "device.outdoorHumidity", inactiveLabel: false){
|
||||
state "default", label:'Outdoor\n ${currentValue}%',
|
||||
icon: "http://cdn.device-icons.smartthings.com/Weather/weather12-icn@3x.png",
|
||||
unit:"%", backgroundColors : [
|
||||
[value: 01, color: "#724529"],
|
||||
[value: 11, color: "#724529"],
|
||||
[value: 21, color: "#724529"],
|
||||
[value: 35, color: "#44b621"],
|
||||
[value: 49, color: "#44b621"],
|
||||
[value: 70, color: "#449c00"],
|
||||
[value: 90, color: "#009cbb"]
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("status", "device.lastUpdate", width: 3, height: 1, decoration: "flat") {
|
||||
state "default", label: 'Last Update: ${currentValue}'
|
||||
}
|
||||
|
||||
main "temperature"
|
||||
details(["temperature", "thermostatMode", "thermostatFanMode",
|
||||
"heatLevelUp", "heatingSetpoint" , "heatLevelDown", "coolLevelUp",
|
||||
"coolingSetpoint", "coolLevelDown" ,"thermostatOperatingState","fanOperatingState",
|
||||
"refresh","relativeHumidity","outdoorTemperature","outdoorHumidity", "status"])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def coolLevelUp()
|
||||
{
|
||||
state.DisplayUnits = settings.tempScale
|
||||
if (state.DisplayUnits == "F")
|
||||
{
|
||||
int nextLevel = device.currentValue("coolingSetpoint") + 1
|
||||
|
||||
if( nextLevel > 99){
|
||||
nextLevel = 99
|
||||
}
|
||||
log.debug "Setting cool set point up to: ${nextLevel}"
|
||||
setCoolingSetpoint(nextLevel)
|
||||
}
|
||||
else
|
||||
{
|
||||
int nextLevel = device.currentValue("coolingSetpoint") + 0.5
|
||||
|
||||
if( nextLevel > 37){
|
||||
nextLevel = 37
|
||||
}
|
||||
log.debug "Setting cool set point up to: ${nextLevel}"
|
||||
setCoolingSetpoint(nextLevel)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def coolLevelDown()
|
||||
{
|
||||
state.DisplayUnits = settings.tempScale
|
||||
if (state.DisplayUnits == "F")
|
||||
{
|
||||
int nextLevel = device.currentValue("coolingSetpoint") - 1
|
||||
|
||||
if( nextLevel < 50){
|
||||
nextLevel = 50
|
||||
}
|
||||
log.debug "Setting cool set point down to: ${nextLevel}"
|
||||
setCoolingSetpoint(nextLevel)
|
||||
}
|
||||
|
||||
else
|
||||
|
||||
{
|
||||
double nextLevel = device.currentValue("coolingSetpoint") - 0.5
|
||||
|
||||
if( nextLevel < 10){
|
||||
nextLevel = 10
|
||||
}
|
||||
log.debug "Setting cool set point down to: ${nextLevel}"
|
||||
setCoolingSetpoint(nextLevel)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def heatLevelUp()
|
||||
{
|
||||
state.DisplayUnits = settings.tempScale
|
||||
if (state.DisplayUnits == "F")
|
||||
{
|
||||
log.debug "in fahrenheit level up"
|
||||
int nextLevel = device.currentValue("heatingSetpoint") + 1
|
||||
|
||||
if( nextLevel > 90){
|
||||
nextLevel = 90
|
||||
}
|
||||
log.debug "Setting heat set point up to: ${nextLevel}"
|
||||
setHeatingSetpoint(nextLevel)
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
|
||||
log.debug "in celsius level uo"
|
||||
double nextLevel = device.currentValue("heatingSetpoint") + 0.5
|
||||
|
||||
if( nextLevel > 33){
|
||||
nextLevel = 33
|
||||
}
|
||||
log.debug "Setting heat set point up to: ${nextLevel}"
|
||||
setHeatingSetpoint(nextLevel)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
def heatLevelDown()
|
||||
{
|
||||
state.DisplayUnits = settings.tempScale
|
||||
if (state.DisplayUnits == "F")
|
||||
{
|
||||
log.debug "in fahrenheit level down"
|
||||
int nextLevel = device.currentValue("heatingSetpoint") - 1
|
||||
|
||||
if( nextLevel < 40){
|
||||
nextLevel = 40
|
||||
}
|
||||
log.debug "Setting heat set point down to: ${nextLevel}"
|
||||
setHeatingSetpoint(nextLevel)
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
|
||||
log.debug "in celsius level down"
|
||||
double nextLevel = device.currentValue("heatingSetpoint") - 0.5
|
||||
|
||||
if( nextLevel < 4){
|
||||
nextLevel = 4
|
||||
}
|
||||
log.debug "Setting heat set point down to: ${nextLevel}"
|
||||
setHeatingSetpoint(nextLevel)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
|
||||
}
|
||||
|
||||
// handle commands
|
||||
|
||||
def setHeatingSetpoint(Double temp)
|
||||
{
|
||||
data.SystemSwitch = 'null'
|
||||
data.HeatSetpoint = temp
|
||||
data.CoolSetpoint = 'null'
|
||||
data.HeatNextPeriod = 'null'
|
||||
data.CoolNextPeriod = 'null'
|
||||
data.StatusHeat='1'
|
||||
data.StatusCool='1'
|
||||
data.FanMode = 'null'
|
||||
setStatus()
|
||||
|
||||
if(data.SetStatus==1)
|
||||
{
|
||||
sendEvent(name: 'heatingSetpoint', value: temp as double)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def setHeatingSetpoint(temp) {
|
||||
data.SystemSwitch = 'null'
|
||||
data.HeatSetpoint = temp
|
||||
data.CoolSetpoint = 'null'
|
||||
data.HeatNextPeriod = 'null'
|
||||
data.CoolNextPeriod = 'null'
|
||||
data.StatusHeat='1'
|
||||
data.StatusCool='1'
|
||||
data.FanMode = 'null'
|
||||
setStatus()
|
||||
|
||||
if(data.SetStatus==1)
|
||||
{
|
||||
sendEvent(name: 'heatingSetpoint', value: temp as Integer)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def setCoolingSetpoint(double temp) {
|
||||
data.SystemSwitch = 'null'
|
||||
data.HeatSetpoint = 'null'
|
||||
data.CoolSetpoint = temp
|
||||
data.HeatNextPeriod = 'null'
|
||||
data.CoolNextPeriod = 'null'
|
||||
data.StatusHeat='1'
|
||||
data.StatusCool='1'
|
||||
data.FanMode = 'null'
|
||||
setStatus()
|
||||
|
||||
if(data.SetStatus==1)
|
||||
{
|
||||
sendEvent(name: 'coolingSetpoint', value: temp as double)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def setCoolingSetpoint(temp) {
|
||||
data.SystemSwitch = 'null'
|
||||
data.HeatSetpoint = 'null'
|
||||
data.CoolSetpoint = temp
|
||||
data.HeatNextPeriod = 'null'
|
||||
data.CoolNextPeriod = 'null'
|
||||
data.StatusHeat='1'
|
||||
data.StatusCool='1'
|
||||
data.FanMode = 'null'
|
||||
setStatus()
|
||||
|
||||
if(data.SetStatus==1)
|
||||
{
|
||||
sendEvent(name: 'coolingSetpoint', value: temp as Integer)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def setTargetTemp(temp) {
|
||||
data.SystemSwitch = 'null'
|
||||
data.HeatSetpoint = temp
|
||||
data.CoolSetpoint = temp
|
||||
data.HeatNextPeriod = 'null'
|
||||
data.CoolNextPeriod = 'null'
|
||||
data.StatusHeat='1'
|
||||
data.StatusCool='1'
|
||||
data.FanMode = 'null'
|
||||
setStatus()
|
||||
}
|
||||
|
||||
|
||||
|
||||
def setTargetTemp(double temp) {
|
||||
data.SystemSwitch = 'null'
|
||||
data.HeatSetpoint = temp
|
||||
data.CoolSetpoint = temp
|
||||
data.HeatNextPeriod = 'null'
|
||||
data.CoolNextPeriod = 'null'
|
||||
data.StatusHeat='1'
|
||||
data.StatusCool='1'
|
||||
data.FanMode = 'null'
|
||||
setStatus()
|
||||
}
|
||||
|
||||
|
||||
def off() {
|
||||
setThermostatMode(2)
|
||||
}
|
||||
|
||||
def auto() {
|
||||
setThermostatMode(4)
|
||||
}
|
||||
|
||||
def heat() {
|
||||
setThermostatMode(1)
|
||||
}
|
||||
|
||||
def emergencyHeat() {
|
||||
|
||||
}
|
||||
|
||||
def cool() {
|
||||
setThermostatMode(3)
|
||||
}
|
||||
|
||||
def setThermostatMode(mode) {
|
||||
data.SystemSwitch = mode
|
||||
data.HeatSetpoint = 'null'
|
||||
data.CoolSetpoint = 'null'
|
||||
data.HeatNextPeriod = 'null'
|
||||
data.CoolNextPeriod = 'null'
|
||||
data.StatusHeat=1
|
||||
data.StatusCool=1
|
||||
data.FanMode = 'null'
|
||||
|
||||
setStatus()
|
||||
|
||||
def switchPos
|
||||
|
||||
if(mode==1)
|
||||
switchPos = 'heat'
|
||||
if(mode==2)
|
||||
switchPos = 'off'
|
||||
if(mode==3)
|
||||
switchPos = 'cool'
|
||||
/* lgk modified my therm has pos 5 for auto vision pro */
|
||||
if(mode==4 || swithPos == 5)
|
||||
switchPos = 'auto'
|
||||
|
||||
if(data.SetStatus==1)
|
||||
{
|
||||
sendEvent(name: 'thermostatMode', value: switchPos)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def fanOn() {
|
||||
setThermostatFanMode(1)
|
||||
}
|
||||
|
||||
def fanAuto() {
|
||||
setThermostatFanMode(0)
|
||||
}
|
||||
|
||||
def fanCirculate() {
|
||||
setThermostatFanMode(2)
|
||||
}
|
||||
|
||||
def setThermostatFanMode(mode) {
|
||||
|
||||
data.SystemSwitch = 'null'
|
||||
data.HeatSetpoint = 'null'
|
||||
data.CoolSetpoint = 'null'
|
||||
data.HeatNextPeriod = 'null'
|
||||
data.CoolNextPeriod = 'null'
|
||||
data.StatusHeat='null'
|
||||
data.StatusCool='null'
|
||||
data.FanMode = mode
|
||||
|
||||
setStatus()
|
||||
|
||||
def fanMode
|
||||
|
||||
if(mode==0)
|
||||
fanMode = 'auto'
|
||||
if(mode==1)
|
||||
fanMode = 'on'
|
||||
if(mode==2)
|
||||
fanMode = 'circulate'
|
||||
|
||||
if(data.SetStatus==1)
|
||||
{
|
||||
sendEvent(name: 'thermostatFanMode', value: fanMode)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
def poll() {
|
||||
refresh()
|
||||
}
|
||||
|
||||
|
||||
def setStatus() {
|
||||
|
||||
data.SetStatus = 0
|
||||
|
||||
login()
|
||||
log.debug "Executing 'setStatus'"
|
||||
def today= new Date()
|
||||
log.debug "https://www.mytotalconnectcomfort.com/portal/Device/SubmitControlScreenChanges"
|
||||
log.debug "setting heat setpoint to $data.HeatSetpoint"
|
||||
log.debug "setting cool setpoint to $data.CoolSetpoint"
|
||||
|
||||
def params = [
|
||||
uri: "https://www.mytotalconnectcomfort.com/portal/Device/SubmitControlScreenChanges",
|
||||
headers: [
|
||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'DNT': '1',
|
||||
'Accept-Encoding': 'gzip,deflate,sdch',
|
||||
'Cache-Control': 'max-age=0',
|
||||
'Accept-Language': 'en-US,en,q=0.8',
|
||||
'Connection': 'keep-alive',
|
||||
'Host': 'rs.alarmnet.com',
|
||||
'Referer': "https://www.mytotalconnectcomfort.com/portal/Device/Control/${settings.honeywelldevice}",
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
|
||||
'Cookie': data.cookiess ],
|
||||
body: [ DeviceID: "${settings.honeywelldevice}", SystemSwitch : data.SystemSwitch ,HeatSetpoint :
|
||||
data.HeatSetpoint, CoolSetpoint: data.CoolSetpoint, HeatNextPeriod:
|
||||
data.HeatNextPeriod,CoolNextPeriod:data.CoolNextPeriod,StatusHeat:data.StatusHeat,
|
||||
StatusCool:data.StatusCool,FanMode:data.FanMode,ThermostatUnits: settings.tempScale]
|
||||
|
||||
]
|
||||
|
||||
log.debug "params = $params"
|
||||
httpPost(params) { response ->
|
||||
log.debug "Request was successful, $response.status"
|
||||
|
||||
}
|
||||
|
||||
log.debug "SetStatus is 1 now"
|
||||
data.SetStatus = 1
|
||||
|
||||
}
|
||||
|
||||
def getStatus() {
|
||||
log.debug "Executing getStatus"
|
||||
log.debug "enable outside temps = $enableOutdoorTemps"
|
||||
def today= new Date()
|
||||
log.debug "https://www.mytotalconnectcomfort.com/portal/Device/CheckDataSession/${settings.honeywelldevice}?_=$today.time"
|
||||
|
||||
def params = [
|
||||
uri: "https://www.mytotalconnectcomfort.com/portal/Device/CheckDataSession/${settings.honeywelldevice}",
|
||||
headers: [
|
||||
'Accept': '*/*',
|
||||
'DNT': '1',
|
||||
'Cache' : 'false',
|
||||
'dataType': 'json',
|
||||
'Accept-Encoding': 'plain',
|
||||
'Cache-Control': 'max-age=0',
|
||||
'Accept-Language': 'en-US,en,q=0.8',
|
||||
'Connection': 'keep-alive',
|
||||
'Referer': 'https://www.mytotalconnectcomfort.com/portal',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
|
||||
'Cookie': data.cookiess ],
|
||||
]
|
||||
|
||||
log.debug "doing request"
|
||||
|
||||
httpGet(params) { response ->
|
||||
log.debug "Request was successful, $response.status"
|
||||
//log.debug "data = $response.data"
|
||||
log.debug "ld = $response.data.latestData"
|
||||
|
||||
def curTemp = response.data.latestData.uiData.DispTemperature
|
||||
def fanMode = response.data.latestData.fanData.fanMode
|
||||
def switchPos = response.data.latestData.uiData.SystemSwitchPosition
|
||||
def coolSetPoint = response.data.latestData.uiData.CoolSetpoint
|
||||
def heatSetPoint = response.data.latestData.uiData.HeatSetpoint
|
||||
def statusCool = response.data.latestData.uiData.StatusCool
|
||||
def statusHeat = response.data.latestData.uiData.StatusHeat
|
||||
def curHumidity = response.data.latestData.uiData.IndoorHumidity
|
||||
def Boolean hasOutdoorHumid = response.data.latestData.uiData.OutdoorHumidityAvailable
|
||||
def Boolean hasOutdoorTemp = response.data.latestData.uiData.OutdoorTemperatureAvailable
|
||||
def curOutdoorHumidity = response.data.latestData.uiData.OutdoorHumidity
|
||||
def curOutdoorTemp = response.data.latestData.uiData.OutdoorTemperature
|
||||
def displayUnits = response.data.latestData.uiData.DisplayUnits
|
||||
def fanIsRunning = response.data.latestData.fanData.fanIsRunning
|
||||
def equipmentStatus = response.data.latestData.uiData.EquipmentOutputStatus
|
||||
def Boolean hasIndoorHumid = response.data.latestData.uiData.IndoorHumiditySensorAvailable
|
||||
def curSetpointStatus = response.data.latestData.uiData.CurrentSetpointStatus
|
||||
def TempHoldTime = response.data.latestData.uiData.TemporaryHoldUntilTime
|
||||
def int TempHoldTimeHrs = TempHoldTime / 60
|
||||
def TempHoldTimeMins = TempHoldTime % 60
|
||||
|
||||
/*ld = [fanData:[fanModeCirculateAllowed:true, fanModeAutoAllowed:true, fanModeFollowScheduleAllowed:false,
|
||||
fanIsRunning:false, fanModeOnAllowed:true, fanMode:0],
|
||||
drData:[Load:127.5, HeatSetpLimit:0,
|
||||
OptOutable:false, DeltaHeatSP:-0.01, CoolSetpLimit:0, Phase:-1, DeltaCoolSP:-0.01],
|
||||
uiData:[OutdoorTemperature:128.0000, TemporaryHoldUntilTime:0, ScheduleHeatSp:67.0000,
|
||||
DeviceID:453824, DispTemperatureAvailable:true, VacationHold:0, VacationHoldUntilTime:0,
|
||||
CoolSetpoint:76.0000, ScheduleCoolSp:76.0000, SwitchHeatAllowed:true, CoolNextPeriod:67,
|
||||
IndoorHumidity:31.0000, SwitchAutoAllowed:true, SetpointChangeAllowed:true, HeatLowerSetptLimit:40.0000,
|
||||
OutdoorHumidStatus:128, SwitchOffAllowed:true, OutdoorHumidityAvailable:false,
|
||||
StatusCool:0, OutdoorTemperatureAvailable:false, EquipmentOutputStatus:0, StatusHeat:0,
|
||||
CurrentSetpointStatus:0, HoldUntilCapable:true, CoolUpperSetptLimit:99.0000, SwitchCoolAllowed:true,
|
||||
OutdoorHumidity:128.0000, DualSetpointStatus:false, SwitchEmergencyHeatAllowed:false, Commercial:false,
|
||||
CoolLowerSetptLimit:50.0000, OutdoorHumiditySensorNotFault:true, IndoorHumiditySensorAvailable:true,
|
||||
ScheduleCapable:true, DisplayUnits:F, DispTemperature:70.0000, Deadband:3.0000, HeatUpperSetptLimit:90.0000,
|
||||
IsInVacationHoldMode:false, OutdoorTemperatureSensorNotFault:true, HeatSetpoint:67.0000, DispTemperatureStatus:0,
|
||||
HeatNextPeriod:67, IndoorHumiditySensorNotFault:true, OutdoorTempStatus:128, IndoorHumidStatus:0,
|
||||
SystemSwitchPosition:4], canControlHumidification:false, hasFan:true]
|
||||
|
||||
log.trace("Fan operating state: ${response.data.latestData.fanData.fanIsRunning}")
|
||||
log.trace("EquipmentOutputStatus: ${response.data.latestData.uiData.EquipmentOutputStatus}")
|
||||
log.trace("IndoorHumidity: ${response.data.latestData.uiData.IndoorHumidity}")
|
||||
|
||||
log.trace("OutdoorTemp = ${response.data.latestData.uiData.OutdoorTemperature}")
|
||||
log.trace("fanMode: ${response.data.latestData.fanData.fanMode}")
|
||||
log.trace("SystenSwitchPosition: ${response.data.latestData.uiData.SystemSwitchPosition}")
|
||||
log.trace("StatusCool: ${response.data.latestData.uiData.StatusCool}")
|
||||
log.trace("StatusHeat: ${response.data.latestData.uiData.StatusHeat}")
|
||||
|
||||
log.trace("IndoorHumiditySensorAvailable: ${response.data.latestData.uiData.IndoorHumiditySensorAvailable}")
|
||||
log.trace("IndoorHumidityAvailable: ${response.data.latestData.uiData.IndoorHumidityAvailable}")
|
||||
|
||||
log.debug "OutdoorHumidityAvailable: response.data.latestData.uiData.OutdoorHumidityAvailable"
|
||||
log.debug "OutdoorTemperatureAvailable: $response.data.latestData.uiData.OutdoorTemperatureAvailable"
|
||||
|
||||
log.debug "OutdoorHumiditySensorNotFault = $response.data.latestData.uiData.OutdoorHumiditySensorNotFault"
|
||||
log.debug "OutdoorTemperatureSensorNotFault = $response.data.latestData.uiData.OutdoorTemperatureSensorNotFault"
|
||||
|
||||
log.debug "IndoorHumiditySensorNotFault: $response.data.latestData.uiData.IndoorHumiditySensorNotFault"
|
||||
log.debug "IndoorHumidStatus: $response.data.latestData.uiData.IndoorHumidStatus"
|
||||
log.debug "OutdoorHumidStatus: $response.data.latestData.uiData.OutdoorHumidStatus"
|
||||
log.debug "OutdoorHumidity: = $response.data.latestData.uiData.OutdoorHumidity"
|
||||
log.debug "OutdoorTemperature = $response.data.latestData.uiData.OutdoorTemperature"
|
||||
|
||||
log.debug "got curOutdoorTemp = $curOutdoorTemp"
|
||||
log.debug "got curOutdoorHumidity = $curOutdoorHumidity"
|
||||
log.debug "hasOutdoorHumid = $hasOutdoorHumid"
|
||||
log.debug "hasOutdoorTemp = $hasOutdoorTemp"
|
||||
*/
|
||||
|
||||
// log.debug "displayUnits = $displayUnits"
|
||||
state.DisplayUnits = $displayUnits
|
||||
|
||||
//Operating State Section
|
||||
//Set the operating state to off
|
||||
|
||||
def operatingState = "Unknown"
|
||||
|
||||
// lgk operating state not working here.. shows both on ie 1 when heat doesnt go on to 67 and heat till 76 and current is 73
|
||||
//Check the status of heat and cool
|
||||
|
||||
// lgk old method now use equipment status
|
||||
if (equipmentStatus == 1) {
|
||||
operatingState = "HEAT ON"
|
||||
} else if (equipmentStatus == 2) {
|
||||
operatingState = "COOL ON"
|
||||
} else if (equipmentStatus == 0) {
|
||||
operatingState = "IDLE"
|
||||
} else {
|
||||
operatingState = "Unknown"
|
||||
}
|
||||
|
||||
if(curSetpointStatus == 0) {
|
||||
operatingState = "$operatingState\nControl By\nFollowing\nSchedule"
|
||||
} else if(curSetpointStatus == 1) {
|
||||
operatingState = "$operatingState\nControl By\nHold Until\n" + String.format("%02d:%02d", TempHoldTimeHrs, TempHoldTimeMins)
|
||||
} else if(curSetpointStatus == 2) {
|
||||
operatingState = "$operatingState\nControl By\nPermanent\nHold"
|
||||
|
||||
} else {
|
||||
operatingState = "unknown"
|
||||
}
|
||||
|
||||
/*
|
||||
if(statusCool == 1 && (switchPos == 3 || switchPos == 5 || swithPos == 4)) {
|
||||
operatingState = "cooling"
|
||||
} else if (statusHeat == 1 && (switchPos == 1 || switchPos == 5 || switchPos == 4)) {
|
||||
operatingState = "heating"
|
||||
} else if (statusCool == 0 && statusHeat == 0) {
|
||||
operatingState = "idle"
|
||||
|
||||
} else {
|
||||
operatingState = "unknown"
|
||||
}
|
||||
*/
|
||||
|
||||
log.trace("Set operating State to: ${operatingState}")
|
||||
|
||||
// set fast state
|
||||
def fanState = "Unknown"
|
||||
|
||||
if (fanIsRunning == true)
|
||||
fanState = "On"
|
||||
else fanState = "Idle"
|
||||
|
||||
log.trace("Set Fan operating State to: ${fanState}")
|
||||
|
||||
//End Operating State
|
||||
|
||||
// log.debug curTemp
|
||||
// log.debug fanMode
|
||||
// log.debug switchPos
|
||||
|
||||
//fan mode 0=auto, 2=circ, 1=on
|
||||
|
||||
if(fanMode==0)
|
||||
fanMode = 'auto'
|
||||
if(fanMode==1)
|
||||
fanMode = 'on'
|
||||
if(fanMode==2)
|
||||
fanMode = 'circulate'
|
||||
|
||||
if(switchPos==1)
|
||||
switchPos = 'heat'
|
||||
if(switchPos==2)
|
||||
switchPos = 'off'
|
||||
if(switchPos==3)
|
||||
switchPos = 'cool'
|
||||
if(switchPos==4 || switchPos==5)
|
||||
switchPos = 'auto'
|
||||
|
||||
def formattedCoolSetPoint = String.format("%5.1f", coolSetPoint)
|
||||
def formattedHeatSetPoint = String.format("%5.1f", heatSetPoint)
|
||||
def formattedTemp = String.format("%5.1f", curTemp)
|
||||
|
||||
def finalCoolSetPoint = formattedCoolSetPoint as BigDecimal
|
||||
def finalHeatSetPoint = formattedHeatSetPoint as BigDecimal
|
||||
def finalTemp = formattedTemp as BigDecimal
|
||||
|
||||
//Send events
|
||||
sendEvent(name: 'thermostatOperatingState', value: operatingState)
|
||||
sendEvent(name: 'fanOperatingState', value: fanState)
|
||||
sendEvent(name: 'thermostatFanMode', value: fanMode)
|
||||
sendEvent(name: 'thermostatMode', value: switchPos)
|
||||
sendEvent(name: 'coolingSetpoint', value: finalCoolSetPoint )
|
||||
sendEvent(name: 'heatingSetpoint', value: finalHeatSetPoint )
|
||||
sendEvent(name: 'temperature', value: finalTemp, state: switchPos)
|
||||
if (hasIndoorHumid){sendEvent(name: 'relativeHumidity', value: curHumidity as Integer)}
|
||||
|
||||
if (settings.tzOffset == null)
|
||||
settings.tzOffset = -5
|
||||
|
||||
def now = new Date()
|
||||
def tf = new java.text.SimpleDateFormat("MM/dd/yyyy h:mm a")
|
||||
tf.setTimeZone(TimeZone.getTimeZone("GMT${settings.tzOffset}"))
|
||||
def newtime = "${tf.format(now)}" as String
|
||||
sendEvent(name: "lastUpdate", value: newtime, descriptionText: "Last Update: $newtime")
|
||||
|
||||
|
||||
if (enableOutdoorTemps == "Yes")
|
||||
{
|
||||
|
||||
if (hasOutdoorHumid)
|
||||
{
|
||||
sendEvent(name: 'outdoorHumidity', value: curOutdoorHumidity as Integer)
|
||||
}
|
||||
|
||||
if (hasOutdoorTemp)
|
||||
{
|
||||
sendEvent(name: 'outdoorTemperature', value: curOutdoorTemp as Integer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def getHumidifierStatus()
|
||||
{
|
||||
/*
|
||||
$.ajax({
|
||||
url: humUrl,
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
/portal/Device/Menu/GetHumData/454832';
|
||||
*/
|
||||
def params = [
|
||||
uri: "https://www.mytotalconnectcomfort.com/portal/Device/Menu/GetHumData/${settings.honeywelldevice}",
|
||||
headers: [
|
||||
'Accept': '*/*',
|
||||
'DNT': '1',
|
||||
'dataType': 'json',
|
||||
'cache': 'false',
|
||||
'Accept-Encoding': 'plain',
|
||||
'Cache-Control': 'max-age=0',
|
||||
'Accept-Language': 'en-US,en,q=0.8',
|
||||
'Connection': 'keep-alive',
|
||||
'Host': 'rs.alarmnet.com',
|
||||
'Referer': 'https://www.mytotalconnectcomfort.com/portal/Menu/${settings.honeywelldevice}',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
|
||||
'Cookie': data.cookiess ],
|
||||
]
|
||||
httpGet(params) { response ->
|
||||
log.debug "GetHumidity Request was successful, $response.status"
|
||||
log.debug "response = $response.data"
|
||||
|
||||
// log.debug "ld = $response.data.latestData"
|
||||
// log.debug "humdata = $response.data.latestData.humData"
|
||||
|
||||
log.trace("lowerLimit: ${response.data.latestData.humData.lowerLimit}")
|
||||
log.trace("upperLimit: ${response.data.humData.upperLimit}")
|
||||
log.trace("SetPoint: ${response.data.humData.Setpoint}")
|
||||
log.trace("DeviceId: ${response.data.humData.DeviceId}")
|
||||
log.trace("IndoorHumidity: ${response.data.humData.IndoorHumidity}")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def api(method, args = [], success = {}) {
|
||||
|
||||
}
|
||||
|
||||
// Need to be logged in before this is called. So don't call this. Call api.
|
||||
def doRequest(uri, args, type, success) {
|
||||
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
def unit = getTemperatureScale()
|
||||
log.debug "units = $unit"
|
||||
login()
|
||||
//getHumidifierStatus()
|
||||
getStatus()
|
||||
}
|
||||
|
||||
def login() {
|
||||
log.debug "Executing 'login'"
|
||||
|
||||
def params = [
|
||||
uri: 'https://www.mytotalconnectcomfort.com/portal',
|
||||
headers: [
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Encoding': 'sdch',
|
||||
'Host': 'www.mytotalconnectcomfort.com',
|
||||
'DNT': '1',
|
||||
'Origin': 'www.mytotalconnectcomfort.com/portal/',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36'
|
||||
],
|
||||
body: [timeOffset: '240', UserName: "${settings.username}", Password: "${settings.password}", RememberMe: 'false']
|
||||
]
|
||||
|
||||
data.cookiess = ''
|
||||
|
||||
httpPost(params) { response ->
|
||||
log.debug "Request was successful, $response.status"
|
||||
log.debug response.headers
|
||||
response.getHeaders('Set-Cookie').each {
|
||||
String cookie = it.value.split(';|,')[0]
|
||||
log.debug "Adding cookie to collection: $cookie"
|
||||
if(cookie != ".ASPXAUTH_TH_A=") {
|
||||
data.cookiess = data.cookiess+cookie+';'
|
||||
}
|
||||
}
|
||||
log.debug "cookies: $data.cookiess"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def isLoggedIn() {
|
||||
if(!data.auth) {
|
||||
log.debug "No data.auth"
|
||||
return false
|
||||
}
|
||||
|
||||
def now = new Date().getTime();
|
||||
return data.auth.expires_in > now
|
||||
}
|
||||
|
||||
|
||||
def updated()
|
||||
{
|
||||
log.debug "in updated"
|
||||
state.DisplayUnits = settings.tempScale
|
||||
log.debug "display units now = $state.DisplayUnits"
|
||||
|
||||
}
|
||||
|
||||
def installed() {
|
||||
state.DisplayUnits = settings.tempScale
|
||||
|
||||
log.debug "display units now = $state.DisplayUnits"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Initial State Event Streamer
|
||||
*
|
||||
* Copyright 2016 David Sulpy
|
||||
* Copyright 2015 David Sulpy
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -77,62 +77,6 @@ mappings {
|
||||
}
|
||||
}
|
||||
|
||||
def getAccessKey() {
|
||||
log.trace "get access key"
|
||||
if (atomicState.accessKey == null) {
|
||||
httpError(404, "Access Key Not Found")
|
||||
} else {
|
||||
[
|
||||
accessKey: atomicState.accessKey
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def getBucketKey() {
|
||||
log.trace "get bucket key"
|
||||
if (atomicState.bucketKey == null) {
|
||||
httpError(404, "Bucket key Not Found")
|
||||
} else {
|
||||
[
|
||||
bucketKey: atomicState.bucketKey,
|
||||
bucketName: atomicState.bucketName
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def setBucketKey() {
|
||||
log.trace "set bucket key"
|
||||
def newBucketKey = request.JSON?.bucketKey
|
||||
def newBucketName = request.JSON?.bucketName
|
||||
|
||||
log.debug "bucket name: $newBucketName"
|
||||
log.debug "bucket key: $newBucketKey"
|
||||
|
||||
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
|
||||
atomicState.bucketKey = "$newBucketKey"
|
||||
atomicState.bucketName = "$newBucketName"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
tryCreateBucket()
|
||||
}
|
||||
|
||||
def setAccessKey() {
|
||||
log.trace "set access key"
|
||||
def newAccessKey = request.JSON?.accessKey
|
||||
def newGrokerSubdomain = request.JSON?.grokerSubdomain
|
||||
|
||||
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
|
||||
atomicState.grokerSubdomain = "$newGrokerSubdomain"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
if (newAccessKey && newAccessKey != atomicState.accessKey) {
|
||||
atomicState.accessKey = "$newAccessKey"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
}
|
||||
|
||||
def subscribeToEvents() {
|
||||
if (accelerometers != null) {
|
||||
subscribe(accelerometers, "acceleration", genericHandler)
|
||||
@@ -225,27 +169,85 @@ def subscribeToEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
def getAccessKey() {
|
||||
log.trace "get access key"
|
||||
if (atomicState.accessKey == null) {
|
||||
httpError(404, "Access Key Not Found")
|
||||
} else {
|
||||
[
|
||||
accessKey: atomicState.accessKey
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def getBucketKey() {
|
||||
log.trace "get bucket key"
|
||||
if (atomicState.bucketKey == null) {
|
||||
httpError(404, "Bucket key Not Found")
|
||||
} else {
|
||||
[
|
||||
bucketKey: atomicState.bucketKey,
|
||||
bucketName: atomicState.bucketName
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def setBucketKey() {
|
||||
log.trace "set bucket key"
|
||||
def newBucketKey = request.JSON?.bucketKey
|
||||
def newBucketName = request.JSON?.bucketName
|
||||
|
||||
log.debug "bucket name: $newBucketName"
|
||||
log.debug "bucket key: $newBucketKey"
|
||||
|
||||
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
|
||||
atomicState.bucketKey = "$newBucketKey"
|
||||
atomicState.bucketName = "$newBucketName"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
tryCreateBucket()
|
||||
}
|
||||
|
||||
def setAccessKey() {
|
||||
log.trace "set access key"
|
||||
def newAccessKey = request.JSON?.accessKey
|
||||
def newGrokerSubdomain = request.JSON?.grokerSubdomain
|
||||
|
||||
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
|
||||
atomicState.grokerSubdomain = "$newGrokerSubdomain"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
if (newAccessKey && newAccessKey != atomicState.accessKey) {
|
||||
atomicState.accessKey = "$newAccessKey"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
atomicState.version = "1.1.0"
|
||||
|
||||
atomicState.isBucketCreated = false
|
||||
atomicState.grokerSubdomain = "groker"
|
||||
|
||||
atomicState.version = "1.0.18"
|
||||
subscribeToEvents()
|
||||
|
||||
atomicState.isBucketCreated = false
|
||||
atomicState.grokerSubdomain = "groker"
|
||||
atomicState.eventBuffer = []
|
||||
|
||||
runEvery15Minutes(flushBuffer)
|
||||
|
||||
log.debug "installed (version $atomicState.version)"
|
||||
}
|
||||
|
||||
def updated() {
|
||||
atomicState.version = "1.1.0"
|
||||
atomicState.version = "1.0.18"
|
||||
unsubscribe()
|
||||
|
||||
if (atomicState.bucketKey != null && atomicState.accessKey != null) {
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
if (atomicState.eventBuffer == null) {
|
||||
atomicState.eventBuffer = []
|
||||
}
|
||||
if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
|
||||
atomicState.grokerSubdomain = "groker"
|
||||
}
|
||||
@@ -325,17 +327,37 @@ def genericHandler(evt) {
|
||||
eventHandler(key, value)
|
||||
}
|
||||
|
||||
def eventHandler(name, value) {
|
||||
def epoch = now() / 1000
|
||||
|
||||
def event = new JsonSlurper().parseText("{\"key\": \"$name\", \"value\": \"$value\", \"epoch\": \"$epoch\"}")
|
||||
|
||||
tryShipEvents(event)
|
||||
|
||||
log.debug "Shipped Event: " + event
|
||||
// This is a handler function for flushing the event buffer
|
||||
// after a specified amount of time to reduce the load on ST servers
|
||||
def flushBuffer() {
|
||||
def eventBuffer = atomicState.eventBuffer
|
||||
log.trace "About to flush the buffer on schedule"
|
||||
if (eventBuffer != null && eventBuffer.size() > 0) {
|
||||
atomicState.eventBuffer = []
|
||||
tryShipEvents(eventBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
def tryShipEvents(event) {
|
||||
def eventHandler(name, value) {
|
||||
def epoch = now() / 1000
|
||||
def eventBuffer = atomicState.eventBuffer ?: []
|
||||
eventBuffer << [key: "$name", value: "$value", epoch: "$epoch"]
|
||||
|
||||
if (eventBuffer.size() >= 10) {
|
||||
// Clear eventBuffer right away since we've already pulled it off of atomicState to reduce the risk of missing
|
||||
// events. This assumes the grokerSubdomain, accessKey, and bucketKey are set correctly to avoid the eventBuffer
|
||||
// from growing unbounded.
|
||||
atomicState.eventBuffer = []
|
||||
tryShipEvents(eventBuffer)
|
||||
} else {
|
||||
// Make sure we persist the updated eventBuffer with the new event added back to atomicState
|
||||
atomicState.eventBuffer = eventBuffer
|
||||
}
|
||||
log.debug "Event added to buffer: " + eventBuffer
|
||||
}
|
||||
|
||||
// a helper function for shipping the atomicState.eventBuffer to Initial State
|
||||
def tryShipEvents(eventBuffer) {
|
||||
|
||||
def grokerSubdomain = atomicState.grokerSubdomain
|
||||
// can't ship events if there is no grokerSubdomain
|
||||
@@ -358,7 +380,7 @@ def tryShipEvents(event) {
|
||||
"X-IS-AccessKey": "${accessKey}",
|
||||
"Accept-Version": "0.0.2"
|
||||
],
|
||||
body: event
|
||||
body: eventBuffer
|
||||
]
|
||||
|
||||
try {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
668
smartapps/smartthings/-.src/-.groovy
Normal file
668
smartapps/smartthings/-.src/-.groovy
Normal file
@@ -0,0 +1,668 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
* Navien Service Manager
|
||||
*
|
||||
* Author: sangju
|
||||
* Date: 2015-11-01
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "나비엔 스마트톡 연동",
|
||||
namespace: "smartthings",
|
||||
author: "나비엔 스마트톡",
|
||||
description: "SmartThings에서 나비엔 스마트톡의 온도조절기를 연결합니다.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Navien/navien.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Navien/navien@2x.png",
|
||||
singleInstance: true
|
||||
) {
|
||||
appSetting "clientId"
|
||||
appSetting "serverUrl"
|
||||
appSetting "bcd"
|
||||
}
|
||||
|
||||
preferences {
|
||||
page(name: "loginPage", title: "나비엔 스마트톡 등록")
|
||||
page(name: "navienAuth", title: "나비엔 스마트톡 등록")
|
||||
page(name: "navienDeviceList", title: "나비엔 스마트톡 등록", install: true )
|
||||
}
|
||||
|
||||
def refreshToken
|
||||
def authToken
|
||||
def userName
|
||||
|
||||
def loginPage(){
|
||||
log.debug "authPage()"
|
||||
|
||||
def showUninstall = username != null && password != null
|
||||
return dynamicPage(name: "loginPage", title: "경동나비엔", nextPage:"navienAuth", uninstall:false) {
|
||||
section("나비엔 스마트톡 등록"){
|
||||
input "username", "text", title: "나비엔 스마트톡 아이디", required: true, autoCorrect:false
|
||||
input "password", "password", title: "나비엔 스마트톡 패스워드", required: true, autoCorrect:false
|
||||
}
|
||||
//section("To use Navien, SmartThings encrypts and securely stores your Navien credentials.") {}
|
||||
}
|
||||
}
|
||||
|
||||
def navienAuth(){
|
||||
log.debug "navienAuth()"
|
||||
|
||||
def loginResult = forceLogin()
|
||||
if(loginResult.success)
|
||||
{
|
||||
return dynamicPage(name: "navienAuth", title: "나비엔 스마트톡 인증", nextPage:"navienDeviceList", uninstall:false) {
|
||||
section(){
|
||||
paragraph "나비엔 스마트톡 인증 성공"
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return dynamicPage(name: "navienAuth", title: "나비엔 스마트톡 인증", nextPage:null, uninstall:false) {
|
||||
section("Login failed"){
|
||||
paragraph "나비엔 스마트톡 인증 실패"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def navienDeviceList(){
|
||||
log.debug "navienDeviceList()"
|
||||
|
||||
def connectResult = navienConnecting()
|
||||
def p
|
||||
if(connectResult.success)
|
||||
{
|
||||
statusSetting(state.status)
|
||||
|
||||
def stats = getNavienThermostats()
|
||||
log.debug "device list: $stats"
|
||||
|
||||
p = dynamicPage(name: "navienDeviceList", title: "나비엔 스마트톡 선택", install:true) {
|
||||
section(""){
|
||||
paragraph "나비엔 스마트톡 계정에서 사용할 수 있는 온도 조절 장치의 목록을 확인하고 SmartTings에 연결하려는 목록을 선택하려면 아래에서 선택해 주세요."
|
||||
input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats])
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p = dynamicPage(name: "navienDeviceList", title: "나비엔 스마트톡 선택", nextPage:null, uninstall:false) {
|
||||
section("나비엔 스마트톡 온도 조절 장치의 연결 상태를 확인하시기 바랍니다."){
|
||||
paragraph ""
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug "list p: $p"
|
||||
return p
|
||||
}
|
||||
|
||||
def getNavienThermostats() {
|
||||
def stats = [:]
|
||||
def dni = [getChildName()].join('.')
|
||||
|
||||
if(state.boilerType == "01") stats[dni] = "스마트톡"
|
||||
else if(state.boilerType == "02") stats[dni] = "콘덴싱톡"
|
||||
else stats[dni] = "----"
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def uninstalled() {
|
||||
def devices = getChildDevices()
|
||||
if(devices != null) log.trace "deleting ${devices.size()} device"
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
log.debug "initialize"
|
||||
|
||||
def d = getChildDevice(getChildName())
|
||||
|
||||
if(!d)
|
||||
{
|
||||
d = addChildDevice(getChildNamespace(), getChildName(), getChildName())
|
||||
log.debug "created ${d.displayName} with id $dni"
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug "found ${d.displayName} with id $dni already exists"
|
||||
}
|
||||
|
||||
def devices = d
|
||||
|
||||
log.debug "created ${devices.size()} thermostats"
|
||||
|
||||
def delete
|
||||
// Delete any that are no longer in settings
|
||||
|
||||
if(!thermostats)
|
||||
{
|
||||
log.debug "If delete thermostats"
|
||||
delete = getAllChildDevices()
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug "Else delete thermostats"
|
||||
if(it != null) delete = getChildDevices().findAll { !thermostats.contains(it.deviceNetworkId) }
|
||||
}
|
||||
|
||||
if(delete != null) log.debug "deleting ${delete.size()} thermostats"
|
||||
if(it != null) delete.each { deleteChildDevice(it.deviceNetworkId) }
|
||||
|
||||
atomicState.thermostatData = [:]
|
||||
|
||||
pollHandler()
|
||||
}
|
||||
|
||||
def pollHandler() {
|
||||
log.debug ("pollHandler.")
|
||||
pollChildren()
|
||||
|
||||
atomicState.thermostats.each {stat ->
|
||||
def dni = stat.key
|
||||
|
||||
log.debug ("DNI = ${dni}")
|
||||
debugEvent ("DNI = ${dni}")
|
||||
|
||||
def d = getChildDevice(dni)
|
||||
if(d)
|
||||
{
|
||||
log.debug ("Found Child Device.")
|
||||
debugEvent ("Found Child Device.")
|
||||
debugEvent("Event Data before generate event call = ${stat}")
|
||||
|
||||
d.generateEvent(atomicState.thermostats[dni].data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def pollChildren()
|
||||
{
|
||||
log.trace "polling children"
|
||||
|
||||
def pollParams = [
|
||||
uri: getServerUrl()+"/api/SmartTokApi?bcd="+getBCD()+"&mid=${state.mid}&did=36&subid=1&cmd=01&data=0",
|
||||
headers: ["Authorization": "Bearer ${state.authToken}"]
|
||||
]
|
||||
|
||||
log.trace "Before HTTPGET to navien."
|
||||
def jsonData
|
||||
try{
|
||||
httpGet(pollParams) { resp ->
|
||||
if (resp.status == 200)
|
||||
{
|
||||
log.debug "poll results returned"
|
||||
//atomicState.thermostats = resp.data.Status.inject([:]) { collector, stat ->
|
||||
atomicState.thermostats = "1".inject([:]) { collector, stat ->
|
||||
def dni = [getChildName()].join('.')
|
||||
|
||||
log.debug "updating dni $dni"
|
||||
|
||||
def data = statusSetting(resp.data.Status)
|
||||
|
||||
log.debug ("Event Data = ${data}")
|
||||
|
||||
collector[dni] = [data:data]
|
||||
return collector
|
||||
}
|
||||
|
||||
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error "polling children & got http status ${resp.status}"
|
||||
|
||||
//refresh the auth token
|
||||
if(resp.status == 400)
|
||||
{
|
||||
log.debug "Bad Request Description"
|
||||
}
|
||||
else if(resp.status == 401)
|
||||
{
|
||||
log.debug "Unauthorized Description"
|
||||
}
|
||||
else if(resp.status == 500)
|
||||
{
|
||||
log.debug "InternalServerError Description"
|
||||
atomicState.action = "pollChildren";
|
||||
refreshAuthToken()
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(all)
|
||||
{
|
||||
log.debug "___exception polling children: "
|
||||
//refreshAuthToken()
|
||||
}
|
||||
}
|
||||
|
||||
def getPollRateMillis() { return 2 * 60 * 1000 }
|
||||
|
||||
def pollChild( child )
|
||||
{
|
||||
log.debug "poll child"
|
||||
debugEvent ("poll child")
|
||||
def now = new Date().time
|
||||
log.debug "now ====> ${now}"
|
||||
|
||||
debugEvent ("Last Poll Millis = ${atomicState.lastPollMillis}")
|
||||
def last = atomicState.lastPollMillis ?: 0
|
||||
def next = last + pollRateMillis
|
||||
|
||||
log.debug "pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${atomicState.thermostats}"
|
||||
debugEvent ("pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${atomicState.thermostats}")
|
||||
|
||||
// if( now > next )
|
||||
if( true ) // for now let's always poll/refresh
|
||||
{
|
||||
log.debug "polling children because $now > $next"
|
||||
debugEvent("polling children because $now > $next")
|
||||
|
||||
pollChildren()
|
||||
|
||||
log.debug "polled children and looking for ${child.device.deviceNetworkId} from ${atomicState.thermostats}"
|
||||
debugEvent ("polled children and looking for ${child.device.deviceNetworkId} from ${atomicState.thermostats}")
|
||||
|
||||
def currentTime = new Date().time
|
||||
debugEvent ("Current Time = ${currentTime}")
|
||||
atomicState.lastPollMillis = currentTime
|
||||
|
||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||
|
||||
if(!tData)
|
||||
{
|
||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
|
||||
|
||||
// TODO: flag device as in error state
|
||||
// child.errorState = true
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
tData.updated = currentTime
|
||||
|
||||
return tData.data
|
||||
}
|
||||
else if(atomicState.thermostats[child.device.deviceNetworkId] != null)
|
||||
{
|
||||
log.debug "not polling children, found child ${child.device.deviceNetworkId} "
|
||||
|
||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||
if(!tData.updated)
|
||||
{
|
||||
// we have pulled new data for this thermostat, but it has not asked us for it
|
||||
// track it and return the data
|
||||
tData.updated = new Date().time
|
||||
return tData.data
|
||||
}
|
||||
return null
|
||||
}
|
||||
else if(atomicState.thermostats[child.device.deviceNetworkId] == null)
|
||||
{
|
||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
|
||||
|
||||
// TODO: flag device as in error state
|
||||
// child.errorState = true
|
||||
|
||||
return null
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's not time to poll again and this thermostat already has its latest values
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
def childRequest( child, subid, cmd, data )
|
||||
{
|
||||
getControlSend(subid, cmd, data)
|
||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||
|
||||
if(!tData)
|
||||
{
|
||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
|
||||
return null
|
||||
}
|
||||
tData.updated = currentTime
|
||||
|
||||
return tData.data
|
||||
}
|
||||
|
||||
def getControlSend(subid, cmd, data)
|
||||
{
|
||||
log.trace "getParams"
|
||||
|
||||
def pollParams = [
|
||||
uri: getServerUrl()+"/api/SmartTokApi?bcd="+getBCD()+"&mid=${state.mid}&did=36&subid=${subid}&cmd=${cmd}&data=${data}",
|
||||
headers: ["Authorization": "Bearer ${state.authToken}"]
|
||||
]
|
||||
|
||||
log.trace "Before Control HTTPGET to navien."
|
||||
def jsonData
|
||||
try{
|
||||
httpGet(pollParams) { resp ->
|
||||
debugEvent ("Response (resp.data.Staus) : = ${resp.data.Staus}", true)
|
||||
if (resp.status == 200)
|
||||
{
|
||||
log.debug "poll results returned"
|
||||
//atomicState.thermostats = resp.data.Status.inject([:]) { collector, stat ->
|
||||
atomicState.thermostats = "1".inject([:]) { collector, stat ->
|
||||
def dni = [getChildName()].join('.')
|
||||
|
||||
log.debug "updating dni $dni"
|
||||
|
||||
def response = statusSetting(resp.data.Status)
|
||||
|
||||
log.debug ("Event Data = ${response}")
|
||||
|
||||
collector[dni] = [data:response]
|
||||
return collector
|
||||
}
|
||||
|
||||
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error "polling children & got http status ${resp.status}"
|
||||
|
||||
//refresh the auth token
|
||||
if(resp.status == 400)
|
||||
{
|
||||
log.debug "Bad Request Description"
|
||||
}
|
||||
else if(resp.status == 401)
|
||||
{
|
||||
log.debug "Unauthorized Description"
|
||||
}
|
||||
else if(resp.status == 500)
|
||||
{
|
||||
log.debug "InternalServerError Description"
|
||||
atomicState.action = "pollChildren";
|
||||
refreshAuthToken()
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(all)
|
||||
{
|
||||
log.debug "___exception polling children: "
|
||||
//refreshAuthToken()
|
||||
}
|
||||
}
|
||||
|
||||
private refreshAuthToken() {
|
||||
log.trace "refreshing auth token"
|
||||
|
||||
if(!atomicState.refreshToken) {
|
||||
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
|
||||
} else {
|
||||
def refreshParams = [
|
||||
uri: getServerUrl(),
|
||||
path: "/Token",
|
||||
headers: ['Content-Type': "application/x-www-form-urlencoded"],
|
||||
body: [grant_type: "refresh_token", refresh_token: "${state.refreshToken}"]
|
||||
]
|
||||
|
||||
log.debug refreshParams
|
||||
|
||||
try {
|
||||
def jsonMap
|
||||
httpPost(refreshParams) { resp ->
|
||||
|
||||
if(resp.status == 200) {
|
||||
log.debug "Token refreshed...calling saved RestAction now! ${resp}"
|
||||
|
||||
if(resp.data)
|
||||
{
|
||||
jsonMap = resp.data
|
||||
state.refreshToken = jsonMap.refresh_token
|
||||
state.authToken = jsonMap.access_token
|
||||
|
||||
if(atomicState.action && atomicState.action != "") {
|
||||
log.debug "Executing next action: ${atomicState.action}"
|
||||
"{atomicState.action}"()
|
||||
|
||||
//remove saved action
|
||||
atomicState.action = ""
|
||||
}
|
||||
}
|
||||
atomicState.action = ""
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e) {
|
||||
log.debug "caught exception refreshing auth token: " + e.getStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def statusSetting(status){
|
||||
/*
|
||||
log.debug "state.status ====> ${state.status}"
|
||||
log.debug "제품아이디 ====> ${state.status.substring(0, 16)}" // 제품아이디
|
||||
log.debug "보일러모델타입 ====> ${state.status.substring(26, 28)}" // 보일러모델타입
|
||||
log.debug "에러코드 ====> ${state.status.substring(32, 36)}" // 에러코드
|
||||
log.debug "온수설정온도 ====> ${state.status.substring(36, 38)}" // 온수설정온도
|
||||
log.debug "state.status ====> ${state.status.substring(38, 40)}" // 난방세기
|
||||
log.debug "state.status ====> ${state.status.substring(40, 42)}" // 옵션기능
|
||||
log.debug "작동모드 ====> ${state.status.substring(42, 44)}" // 작동모드
|
||||
log.debug "현재실내온도 ====> ${state.status.substring(44, 46)}" // 현재실내온도
|
||||
log.debug "실내난방설정온도 ====> ${state.status.substring(46, 48)}" // 실내난방설정온도
|
||||
log.debug "온돌난방설정온도 ====> ${state.status.substring(48, 50)}" // 온돌난방설정온도
|
||||
*/
|
||||
state.mid = status.substring(0, 16)
|
||||
state.boilerType = status.substring(26, 28)
|
||||
state.errorCode = status.substring(32, 36)
|
||||
state.hotWater = convertHexToInt(status.substring(36, 38))
|
||||
|
||||
def s = status.substring(42, 44)
|
||||
if(s == "01") state.thermostatStatus = "전원 OFF"
|
||||
else if(s == "02") state.thermostatStatus = "외출 ON"
|
||||
else if(s == "03") state.thermostatStatus = "실내난방"
|
||||
else if(s == "04") state.thermostatStatus = "온돌난방"
|
||||
else if(s == "05") state.thermostatStatus = "반복예약난방"
|
||||
else if(s == "06") state.thermostatStatus = "24시간예약난방"
|
||||
else if(s == "07") state.thermostatStatus = "간편예약난방"
|
||||
else if(s == "08") state.thermostatStatus = "온수전용"
|
||||
else if(s == "09") state.thermostatStatus = "빠른온수"
|
||||
else state.thermostatStatus = "---"
|
||||
|
||||
state.temperature = convertHexToInt(status.substring(44, 46))
|
||||
state.roomTemp = convertHexToInt(status.substring(46, 48))
|
||||
state.ondolTemp = convertHexToInt(status.substring(48, 50))
|
||||
|
||||
def data = [
|
||||
mid: state.mid,
|
||||
boilerType: state.boilerType,
|
||||
errorCode: state.errorCode,
|
||||
hotWater: state.hotWater,
|
||||
thermostatStatus: state.thermostatStatus,
|
||||
temperature: state.temperature,
|
||||
roomTemp: state.roomTemp,
|
||||
ondolTemp: state.ondolTemp
|
||||
]
|
||||
return data
|
||||
}
|
||||
|
||||
def navienConnecting(){
|
||||
log.debug "navienConnecting()"
|
||||
|
||||
def connectParams = [
|
||||
uri: getServerUrl()+"/api/SmartTokApi?bcd="+getBCD()+"&uid=${state.userName}&scd=2",
|
||||
headers: ["Authorization": "Bearer ${state.authToken}"]
|
||||
]
|
||||
|
||||
def result = [success:false]
|
||||
def jsonData
|
||||
|
||||
httpGet(connectParams) { resp ->
|
||||
if (resp.status == 200)
|
||||
{
|
||||
jsonData = resp.data
|
||||
result.success = true
|
||||
state.status = jsonData.Status
|
||||
}
|
||||
else if(resp.status == 400)
|
||||
{
|
||||
result.reason = "Bad Request"
|
||||
}
|
||||
else if(resp.status == 401)
|
||||
{
|
||||
result.reason = "Unauthorized"
|
||||
}
|
||||
else if(resp.status == 500)
|
||||
{
|
||||
result.reason = "Internal ServerError"
|
||||
}
|
||||
else
|
||||
{
|
||||
result.reason = "Bad Connect"
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private forceLogin(){
|
||||
log.debug "forceLogin()"
|
||||
|
||||
updateCookie(null)
|
||||
login()
|
||||
}
|
||||
|
||||
private updateCookie(String cookie){
|
||||
atomicState.cookie = cookie
|
||||
state.cookie = cookie
|
||||
}
|
||||
|
||||
private login(){
|
||||
if(getCookieValueIsValid())
|
||||
{
|
||||
return [success:true]
|
||||
}
|
||||
return doLogin()
|
||||
}
|
||||
|
||||
private doLogin() {
|
||||
log.debug "doLogin()"
|
||||
|
||||
def loginParams = [
|
||||
uri: getServerUrl(),
|
||||
path: "/Token",
|
||||
headers: ['Content-Type': "application/x-www-form-urlencoded"],
|
||||
body: [grant_type: "password", username: username, password: password]
|
||||
]
|
||||
|
||||
def result = [success:false]
|
||||
def jsonMap
|
||||
|
||||
try
|
||||
{
|
||||
httpPost(loginParams) { resp ->
|
||||
if (resp.status == 200 && resp.headers.'Content-Type'.contains("application/json"))
|
||||
{
|
||||
log.debug "login 200 json headers: " + resp.headers.collect { "${it.name}:${it.value}" }
|
||||
def cookie = resp?.headers?.'Set-Cookie'?.split(";")?.getAt(0)
|
||||
if (cookie) {
|
||||
log.debug "login setting cookie to $cookie"
|
||||
updateCookie(cookie)
|
||||
result.success = true
|
||||
|
||||
jsonMap = resp.data
|
||||
state.refreshToken = jsonMap.refresh_token
|
||||
state.authToken = jsonMap.access_token
|
||||
state.userName = jsonMap.userName
|
||||
}
|
||||
else
|
||||
{
|
||||
// ERROR: any more information we can give?
|
||||
result.reason = "Bad login"
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.reason = "Bad login"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(groovyx.net.http.HttpResponseException hre)
|
||||
{
|
||||
result.reason = "Exception"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private Boolean getCookieValueIsValid()
|
||||
{
|
||||
// TODO: make a call with the cookie to verify that it works
|
||||
return getCookieValue()
|
||||
}
|
||||
|
||||
private getCookieValue(){
|
||||
state.cookie
|
||||
}
|
||||
|
||||
def getChildNamespace() { "smartthings" }
|
||||
def getChildName() { "Navien Room Controller" }
|
||||
|
||||
def getChildDeviceIdsString()
|
||||
{
|
||||
log.debug "thermostats ====> ${thermostats}"
|
||||
return thermostats.collect { it.split(/\./).last() }.join(',')
|
||||
}
|
||||
|
||||
def getServerUrl() { return appSettings.serverUrl }
|
||||
def getSmartThingsClientId() { return appSettings.clientId }
|
||||
def getBCD() { return appSettings.bcd }
|
||||
|
||||
def debugEvent(message, displayEvent = true) {
|
||||
|
||||
def results = [
|
||||
name: "appdebug",
|
||||
descriptionText: message,
|
||||
displayed: displayEvent
|
||||
]
|
||||
log.debug "Generating AppDebug Event: ${results}"
|
||||
sendEvent (results)
|
||||
}
|
||||
|
||||
private convertHexToInt(hex) {
|
||||
return (Integer.parseInt(hex,16) / 2)
|
||||
}
|
||||
@@ -326,7 +326,6 @@ def addBulbs() {
|
||||
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
|
||||
}
|
||||
log.debug "created ${d.displayName} with id $dni"
|
||||
d.refresh()
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
@@ -334,8 +333,8 @@ def addBulbs() {
|
||||
//backwards compatable
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
||||
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
|
||||
d.refresh()
|
||||
}
|
||||
d.refresh()
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
@@ -775,4 +774,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
|
||||
|
||||
private List getRealHubFirmwareVersions() {
|
||||
return location.hubs*.firmwareVersionString.findAll { it }
|
||||
}
|
||||
}
|
||||
@@ -316,40 +316,60 @@ private def parseEventMessage(String description) {
|
||||
parts.each { part ->
|
||||
part = part.trim()
|
||||
if (part.startsWith('devicetype:')) {
|
||||
part -= "devicetype:"
|
||||
event.devicetype = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
event.devicetype = valueString
|
||||
}
|
||||
else if (part.startsWith('mac:')) {
|
||||
part -= "mac:"
|
||||
event.mac = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString) {
|
||||
event.mac = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('networkAddress:')) {
|
||||
part -= "networkAddress:"
|
||||
event.ip = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString) {
|
||||
event.ip = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('deviceAddress:')) {
|
||||
part -= "deviceAddress:"
|
||||
event.port = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString) {
|
||||
event.port = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpPath:')) {
|
||||
part -= "ssdpPath:"
|
||||
event.ssdpPath = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString) {
|
||||
event.ssdpPath = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpUSN:')) {
|
||||
part -= "ssdpUSN:"
|
||||
event.ssdpUSN = part.trim()
|
||||
def valueString = part.trim()
|
||||
if (valueString) {
|
||||
event.ssdpUSN = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpTerm:')) {
|
||||
part -= "ssdpTerm:"
|
||||
event.ssdpTerm = part.trim()
|
||||
def valueString = part.trim()
|
||||
if (valueString) {
|
||||
event.ssdpTerm = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('headers')) {
|
||||
part -= "headers:"
|
||||
event.headers = part.trim()
|
||||
def valueString = part.trim()
|
||||
if (valueString) {
|
||||
event.headers = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('body')) {
|
||||
part -= "body:"
|
||||
event.body = part.trim()
|
||||
def valueString = part.trim()
|
||||
if (valueString) {
|
||||
event.body = valueString
|
||||
}
|
||||
}
|
||||
}
|
||||
event
|
||||
|
||||
@@ -473,48 +473,68 @@ private def parseXmlBody(def body) {
|
||||
}
|
||||
|
||||
private def parseDiscoveryMessage(String description) {
|
||||
def event = [:]
|
||||
def device = [:]
|
||||
def parts = description.split(',')
|
||||
parts.each { part ->
|
||||
part = part.trim()
|
||||
if (part.startsWith('devicetype:')) {
|
||||
part -= "devicetype:"
|
||||
event.devicetype = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
device.devicetype = valueString
|
||||
}
|
||||
else if (part.startsWith('mac:')) {
|
||||
part -= "mac:"
|
||||
event.mac = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString) {
|
||||
device.mac = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('networkAddress:')) {
|
||||
part -= "networkAddress:"
|
||||
event.ip = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString) {
|
||||
device.ip = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('deviceAddress:')) {
|
||||
part -= "deviceAddress:"
|
||||
event.port = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString) {
|
||||
device.port = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpPath:')) {
|
||||
part -= "ssdpPath:"
|
||||
event.ssdpPath = part.trim()
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString) {
|
||||
device.ssdpPath = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpUSN:')) {
|
||||
part -= "ssdpUSN:"
|
||||
event.ssdpUSN = part.trim()
|
||||
def valueString = part.trim()
|
||||
if (valueString) {
|
||||
device.ssdpUSN = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpTerm:')) {
|
||||
part -= "ssdpTerm:"
|
||||
event.ssdpTerm = part.trim()
|
||||
def valueString = part.trim()
|
||||
if (valueString) {
|
||||
device.ssdpTerm = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('headers')) {
|
||||
part -= "headers:"
|
||||
event.headers = part.trim()
|
||||
def valueString = part.trim()
|
||||
if (valueString) {
|
||||
device.headers = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('body')) {
|
||||
part -= "body:"
|
||||
event.body = part.trim()
|
||||
def valueString = part.trim()
|
||||
if (valueString) {
|
||||
device.body = valueString
|
||||
}
|
||||
}
|
||||
}
|
||||
event
|
||||
device
|
||||
}
|
||||
|
||||
def doDeviceSync(){
|
||||
|
||||
Reference in New Issue
Block a user