Compare commits

..

10 Commits

Author SHA1 Message Date
nanoheal
9e9370a612 MSA-2038: testing the iot devices 2017-06-13 21:25:15 -07:00
Vinay Rao
d6b0f6a8ed Merge pull request #2089 from SmartThingsCommunity/staging
Rolling down staging to master
2017-06-13 15:24:44 -07:00
Vinay Rao
62c810ba90 Merge pull request #2078 from dkirker/ICP-1110
ICP-1110 Read valve state during pairing
2017-06-13 15:23:07 -07:00
Vinay Rao
0b81793b0f Merge pull request #2081 from SmartThingsCommunity/staging
Rolling down staging to master
2017-06-09 13:03:54 -07:00
Vinay Rao
16f41bddae Merge pull request #2080 from workingmonk/bug/mark_files_deprecated
SLC-82 Marking old connect apps and device types as deprecated
2017-06-09 13:01:45 -07:00
Vinay Rao
117adea586 SLC-82 Marking old connect apps and device types as deprecated 2017-06-09 11:44:06 -07:00
Vinay Rao
783538e36d Merge pull request #2070 from dkirker/ICP-1097
ICP-1097 Fix Sengled Classic default level on pairing
2017-06-08 16:28:50 -07:00
Donald Kirker
fb8e4a2416 ICP-1110 Read valve state during pairing 2017-06-07 23:47:13 -07:00
Vinay Rao
6110aaa0fa Merge pull request #2074 from SmartThingsCommunity/master
Rolling up master to staging
2017-06-06 12:01:26 -07:00
Donald Kirker
6325101f52 ICP-1097 Fix Sengled Classic default level on pairing
Code brought oever from @varzac commit 8bcbe7b924
Original commit message:

> Fix sengled use of FF for max level
> This works around the fact that sengled element touches can get back
> into a state of reporting an invalid value (of FF) if the physical
> button on the bulb is used to cycle back to the 100% state.  Here we
> interpret FF as FE for sengled and then issue a move to level command to
> put it in a state where it will report FE until the level is changed
> again.
>
> This resolves: https://smartthings.atlassian.net/browse/DVCSMP-2597

While the Classic does not have a physical button, since it does represent 100% as FF we want to always catch this.
2017-06-06 03:29:41 -07:00
17 changed files with 500 additions and 1202 deletions

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Bose SoundTouch
*

View File

@@ -56,6 +56,8 @@ metadata {
def installed(){
// Device-Watch simply pings if no device events received for 32min(checkInterval)
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
response(refresh())
}
def updated(){
@@ -85,11 +87,17 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
}
def open() {
zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00).format()
delayBetween([
zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
], 500)
}
def close() {
zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF).format()
delayBetween([
zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF).format(),
zwave.switchBinaryV1.switchBinaryGet().format()
], 500)
}
/**
@@ -105,6 +113,6 @@ def refresh() {
def createEventWithDebug(eventMap) {
def event = createEvent(eventMap)
log.debug "Event created with ${event?.descriptionText}"
log.debug "Event created with ${event?.name}:${event?.value} - ${event?.descriptionText}"
return event
}

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Hue Bloom
*

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Hue Bridge
*

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Hue Bulb
*

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Hue Lux Bulb
*

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Hue White Ambiance Bulb
*

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Copyright 2015 SmartThings
*

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Copyright 2015 SmartThings
*

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Copyright 2015 SmartThings
*

View File

@@ -66,22 +66,29 @@ def parse(String description) {
else {
sendEvent(event)
}
}
else {
def cluster = zigbee.parse(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
} else {
def descMap = zigbee.parseDescriptionAsMap(description)
if (descMap && descMap.clusterInt == 0x0006 && descMap.commandInt == 0x07) {
if (descMap.data[0] == "00") {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
} else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
} else if (device.getDataValue("manufacturer") == "sengled" && descMap && descMap.clusterInt == 0x0008 && descMap.attrInt == 0x0000) {
// This is being done because the sengled element touch/classic incorrectly uses the value 0xFF for the max level.
// Per the ZCL spec for the UINT8 data type 0xFF is an invalid value, and 0xFE should be the max. Here we
// manually handle the invalid attribute value since it will be ignored by getEvent as an invalid value.
// We also set the level of the bulb to 0xFE so future level reports will be 0xFE until it is changed by
// something else.
if (descMap.value.toUpperCase() == "FF") {
descMap.value = "FE"
}
sendHubCommand(zigbee.command(zigbee.LEVEL_CONTROL_CLUSTER, 0x00, "FE0000").collect { new physicalgraph.device.HubAction(it) }, 0)
sendEvent(zigbee.getEventFromAttrData(descMap.clusterInt, descMap.attrInt, descMap.encoding, descMap.value))
} else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
log.debug "${descMap}"
}
}
}

View File

@@ -1,537 +0,0 @@
/**
* Copyright 2017 Stelpro
*
* 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.
*
* Stelpro Ki Thermostat
*
* Author: Stelpro
*
* Date: 2017-06-08
*/
preferences {
input("zipcode", "text", title: "ZipCode (Outdoor Temperature)", description: "[Do not use space](Blank = No Forecast)")
input("heatdetails", "enum", title: "Do you want a detailed operating state notification?", options: ["No", "Yes"], defaultValue: "No", required: false, displayDuringSetup: true)
}
metadata {
definition (name: "Stelpro Ki Thermostat", namespace: "stelpro", author: "Stelpro") {
capability "Thermostat"
capability "Temperature Measurement"
capability "Actuator"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Configuration"
attribute "outsideTemp", "number"
command "switchMode"
command "quickSetHeat"
command "quickSetOutTemp"
command "increaseHeatSetpoint"
command "decreaseHeatSetpoint"
command "setCustomThermostatMode"
command "eco"
command "applyNow"
fingerprint deviceId: "0x0806", inClusters: "0x5E,0x86,0x72,0x40,0x43,0x31,0x85,0x59,0x5A,0x73,0x20,0x42"
}
// simulator metadata
simulator {
//Add test code here
}
tiles(scale : 2) {
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temp", label:'${currentValue}')
attributeState("high", label:'HIGH')
attributeState("low", label:'LOW')
attributeState("--", label:'--')
}
tileAttribute("device.heatingSetpoint", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "increaseHeatSetpoint")
attributeState("VALUE_DOWN", action: "decreaseHeatSetpoint")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'Off')
attributeState("comfort", label:'Comfort')
attributeState("eco", label:'Eco')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT")
{
attributeState("heatingSetpoint", label:'${currentValue}')
}
}
standardTile("mode", "device.thermostatMode", width: 2, height: 2) {
state "off", label:'${name}', action:"switchMode", nextState:"to_comfort", icon:"st.thermostat.off"
state "comfort", label:'${name}', action:"switchMode", nextState:"to_eco", icon:"http://cdn.device-icons.smartthings.com/Home/home29-icn@2x.png"
state "eco", label:'${name}', action:"switchMode", nextState:"...", icon:"http://cdn.device-icons.smartthings.com/Outdoor/outdoor3-icn@2x.png"
state "to_comfort", label: "comfort", action:"switchMode", nextState:"to_eco"
state "to_eco", label: "eco", action:"switchMode", nextState:"..."
state "...", label: "...", action:"heat", nextState:"comfort"
}
standardTile("refresh", "device.refresh", decoration: "flat", width: 2, height: 2) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main ("thermostatMulti")
details(["thermostatMulti", "mode", "refresh"])
}
}
def parse(String description)
{
if (description == "updated")
return []
def unitScale = getTemperatureScale()
//Class, version
def map = createEvent(zwaveEvent(zwave.parse(description, [0x40:2, 0x43:2, 0x31:3, 0x42:1])))
if (!map) {
return null
}
def result = [map]
if (map.name in ["heatingSetpoint","thermostatMode"]) {
def map2 = [
name: "thermostatSetpoint",
unit: getTemperatureScale()
]
if (map.name == "thermostatMode") {
state.lastTriedMode = map.value
}
else {
def mode = device.latestValue("thermostatMode")
log.info "THERMOSTAT, latest mode = ${mode}"
if (map.name == "heatingSetpoint") {
map2.value = map.value
map2.unit = map.unit
}
}
if (map2.value != null) {
log.debug "THERMOSTAT, adding setpoint event: $map"
result << createEvent(map2)
}
}
log.debug "Parse returned $result"
result
}
// Event Generation
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd)
{
def cmdScale = cmd.scale == 1 ? "F" : "C"
def temp;
float tempfloat;
def map = [:]
if (cmd.scaledValue >= 327)
{
map.value = "--"
}
else
{
temp = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision)
tempfloat = (Math.round(temp.toFloat() * 2)) / 2
map.value = tempfloat
}
map.unit = getTemperatureScale()
map.displayed = false
switch (cmd.setpointType) {
case 1:
map.name = "heatingSetpoint"
break;
default:
return [:]
}
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
sendEvent(name:"heatingSetpoint", value:map.value)
map
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd)
{
def temp;
float tempfloat;
def format;
def map = [:]
if (cmd.sensorType == 1) {
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
map.unit = getTemperatureScale()
map.name = "temperature"
temp = map.value
if (temp == "32765") //0x7FFD
{
map.value = "low"
}
else if (temp == "32767") //0x7FFF
{
map.value = "high"
}
else if (temp == "-32768") //0x8000
{
map.value = "--"
}
else
{
tempfloat = (Math.round(temp.toFloat() * 2)) / 2
map.value = tempfloat
}
} else if (cmd.sensorType == 5) {
map.value = cmd.scaledSensorValue
map.unit = "%"
map.name = "humidity"
}
sendEvent(name:"temperature", value:map.value)
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd)
{
def map = [:]
switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
map.value = "idle"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_HEATING:
map.value = "heating"
break
}
map.name = "thermostatOperatingState"
if (settings.heatdetails == "No") {
map.displayed = false
}
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
def map = [:]
switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT:
map.value = "comfort"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF:
map.value = "off"
break
default:
map.value = "eco"
break
}
map.name = "thermostatMode"
sendEvent(name:"thermostatMode", value:map.value)
map
}
def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd) {
delayBetween([
zwave.associationV1.associationRemove(groupingIdentifier:1, nodeId:0).format(),
zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format(),
poll()
], 2300)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
log.debug "Zwave event received: $cmd"
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
log.debug "Zwave event received: $cmd"
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.warn "Unexpected zwave command $cmd"
}
// Command Implementations
def poll() {
def weather
// If there is a zipcode defined, weather forecast will be sent. Otherwise, no weather forecast.
if (settings.zipcode) {
log.debug "ZipCode: ${settings.zipcode}"
weather = getWeatherFeature( "conditions", settings.zipcode )
// Check if the variable is populated, otherwise return.
if (!weather) {
log.debug( "Something went wrong, no data found." )
return false
}
// Set the tiles
def locationScale = getTemperatureScale()
def tempToSend
if (locationScale == "C")
{
log.debug( "Outdoor Temperature: ${weather.current_observation.temp_c}ºC" )
sendEvent( name: 'outsideTemp', value: weather.current_observation.temp_c )
tempToSend = weather.current_observation.temp_c
}
else
{
log.debug( "Outdoor Temperature: ${weather.current_observation.temp_f}ºF" )
sendEvent( name: 'outsideTemp', value: weather.current_observation.temp_f )
tempToSend = weather.current_observation.temp_f
}
delayBetween([
quickSetOutTemp(tempToSend),
zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format(),
zwave.thermostatModeV2.thermostatModeGet().format(),
zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 1).format(),
zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature
sendEvent( name: 'change', value: 0 )
], 100)
} else {
delayBetween([
zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format(),
zwave.thermostatModeV2.thermostatModeGet().format(),
zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 1).format(),
zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature
sendEvent( name: 'change', value: 0 )
], 100)
}
}
def refresh() {
poll()
}
def configure() {
poll()
}
def applyNow() {
float currentHeatSetpoint = device.currentValue("heatingSetpoint")
def deviceScale
def locationScale = getTemperatureScale()
sendEvent( name: 'change', value: 0 )
log.debug("currentHeatSetpoint $currentHeatSetpoint")
if (locationScale == "C")
{
deviceScale = 0
}
else
{
deviceScale = 1
}
delayBetween([
zwave.thermostatSetpointV2.thermostatSetpointSet(setpointType: 1, scale: deviceScale, precision: state.precision, scaledValue: currentHeatSetpoint).format(),
poll()
], 1000)
}
def quickSetHeat(degrees) {
setHeatingSetpoint(degrees, 0)
}
def setHeatingSetpoint(degrees, delay = 0) {
sendEvent(name:"heatingSetpoint", value:degrees)
applyNow()
}
def quickSetOutTemp(degrees) {
setOutdoorTemperature(degrees, 0)
}
def setOutdoorTemperature(degrees, delay = 0) {
setOutdoorTemperature(degrees.toDouble(), delay)
}
def setOutdoorTemperature(Double degrees, Integer delay = 0) {
def deviceScale
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
if (locationScale == "C")
{
deviceScale = 0
}
else
{
deviceScale = 1
}
log.info "setOutdoorTemperature: ${degrees}"
zwave.sensorMultilevelV3.sensorMultilevelReport(sensorType: 1, scale: deviceScale, precision: p, scaledSensorValue: degrees).format()
}
def increaseHeatSetpoint()
{
def currentMode = device.currentState("thermostatMode")?.value
if (currentMode != "off")
{
float currentSetpoint = device.currentValue("heatingSetpoint")
def locationScale = getTemperatureScale()
float maxSetpoint
float step
if (locationScale == "C")
{
maxSetpoint = 30;
step = 0.5
}
else
{
maxSetpoint = 86
step = 1
}
if (currentSetpoint < maxSetpoint)
{
currentSetpoint = currentSetpoint + step
quickSetHeat(currentSetpoint)
}
}
}
def decreaseHeatSetpoint()
{
def currentMode = device.currentState("thermostatMode")?.value
if (currentMode != "off")
{
float currentSetpoint = device.currentValue("heatingSetpoint")
def locationScale = getTemperatureScale()
float minSetpoint
float step
if (locationScale == "C")
{
minSetpoint = 5;
step = 0.5
}
else
{
minSetpoint = 41
step = 1
}
if (currentSetpoint > minSetpoint)
{
currentSetpoint = currentSetpoint - step
quickSetHeat(currentSetpoint)
}
}
}
def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "comfort"
def supportedModes = getDataByName("supportedModes")
def modeOrder = modes()
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(lastTriedMode)
if (supportedModes?.contains(currentMode)) {
while (!supportedModes.contains(nextMode) && nextMode != "comfort") {
nextMode = next(nextMode)
}
}
state.lastTriedMode = nextMode
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[nextMode]).format(),
poll()
], 1000)
}
def modes() {
["comfort", "eco", "off"]
}
def getModeMap() { [
"off": 0,
"comfort": 1,
"eco": 11,
]}
def getDataByName(String name) {
state[name] ?: device.getDataValue(name)
}
def setCoolingSetpoint(coolingSetpoint) {
log.trace "${device.displayName} does not support cool setpoint"
}
def off() {
log.trace "off mode applied"
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
poll()
], 1000)
}
def heat() {
log.trace "heat mode applied"
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
poll()
], 1000)
}
def eco() {
log.trace "eco mode applied"
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 11).format(),
poll()
], 1000)
}
def auto() {
log.trace "${device.displayName} does not support auto mode"
}
def emergencyHeat() {
log.trace "${device.displayName} does not support emergency heat mode"
}
def cool() {
log.trace "${device.displayName} does not support cool mode"
}
def setCustomThermostatMode(mode) {
setThermostatMode(mode)
}
def setThermostatMode(String value) {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(),
poll()
], 1000)
}
def fanOn() {
log.trace "${device.displayName} does not support fan on"
}
def fanAuto() {
log.trace "${device.displayName} does not support fan auto"
}
def fanCirculate() {
log.trace "${device.displayName} does not support fan circulate"
}
def setThermostatFanMode() {
log.trace "${device.displayName} does not support fan mode"
}

View File

@@ -1,651 +0,0 @@
/**
* Copyright 2017 Stelpro
*
* 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.
*
* Stelpro Ki ZigBee Thermostat
*
* Author: Stelpro
*
* Date: 2017-06-08
*/
preferences {
section {
input("unitformat", "enum", title: "What unit format do you want your thermostat to display temperature?", options: ["Celsius", "Fahrenheit"], defaultValue: "Celsius", required: false, displayDuringSetup: false)
input("lock", "enum", title: "Do you want to lock your thermostat's physical keypad?", options: ["No", "Yes"], defaultValue: "No", required: false, displayDuringSetup: false)
input("heatdetails", "enum", title: "Do you want a detailed operating state notification?", options: ["No", "Yes"], defaultValue: "No", required: false, displayDuringSetup: true)
input("zipcode", "text", title: "ZipCode (Outdoor Temperature)", description: "[Do not use space](Blank = No Forecast)")
}
}
metadata {
definition (name: "Stelpro Ki ZigBee Thermostat", namespace: "Stelpro", author: "Stelpro") {
capability "Thermostat"
capability "Temperature Measurement"
capability "Actuator"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Configuration"
command "switchMode"
command "quickSetHeat"
command "quickSetOutTemp"
command "increaseHeatSetpoint"
command "decreaseHeatSetpoint"
command "parameterSetting"
fingerprint profileId: "0104", endpointId: "19", inClusters: " 0000,0003,0201,0204", outClusters: "0402"
}
// simulator metadata
simulator { }
tiles(scale: 2) {
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temp", label:'${currentValue}')
attributeState("high", label:'HIGH')
attributeState("low", label:'LOW')
attributeState("--", label:'--')
}
tileAttribute("device.heatingSetpoint", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "increaseHeatSetpoint")
attributeState("VALUE_DOWN", action: "decreaseHeatSetpoint")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'Off')
attributeState("comfort", label:'Comfort')
attributeState("eco", label:'Eco')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT")
{
attributeState("heatingSetpoint", label:'${currentValue}')
}
}
standardTile("mode", "device.thermostatMode", width: 2, height: 2) {
state "off", label:'Off', action:"switchMode", nextState:"comfort", icon:"st.thermostat.off"
state "comfort", label:'Comfort', action:"switchMode", nextState:"eco", icon:"http://cdn.device-icons.smartthings.com/Home/home29-icn@2x.png"
state "eco", label:'Eco', action:"switchMode", nextState:"off", icon:"http://cdn.device-icons.smartthings.com/Outdoor/outdoor3-icn@2x.png"
}
standardTile("refresh", "device.refresh", decoration: "flat", width: 2, height: 2) {
state "default", action:"poll", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main ("thermostatMulti")
details(["thermostatMulti", "mode", "refresh", "configure"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parse description $description"
def map = [:]
if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.debug "Desc Map: $descMap"
if (descMap.cluster == "0201" && descMap.attrId == "0000")
{
log.debug "TEMP"
map.name = "temperature"
map.value = getTemperature(descMap.value)
if (descMap.value == "7ffd") //0x7FFD
{
map.value = "low"
}
else if (descMap.value == "7fff") //0x7FFF
{
map.value = "high"
}
else if (descMap.value == "8000") //0x8000
{
map.value = "--"
}
sendEvent(name:"temperature", value:map.value)
}
else if (descMap.cluster == "0201" && descMap.attrId == "0012")
{
log.debug "HEATING SETPOINT"
map.name = "heatingSetpoint"
map.value = getTemperature(descMap.value)
if (descMap.value == "8000") //0x8000
{
map.value = "--"
}
sendEvent(name:"heatingSetpoint", value:map.value)
}
else if (descMap.cluster == "0201" && descMap.attrId == "001c")
{
if (descMap.value.size() == 8)
{
log.debug "MODE"
map.name = "thermostatMode"
map.value = getModeMap()[descMap.value]
sendEvent(name:"thermostatMode", value:map.value)
}
else if (descMap.value.size() == 10)
{
log.debug "MODE & SETPOINT MODE"
def twoModesAttributes = descMap.value[0..-9]
map.name = "thermostatMode"
map.value = getModeMap()[twoModesAttributes]
sendEvent(name:"thermostatMode", value:map.value)
}
}
else if (descMap.cluster == "0201" && descMap.attrId == "401c")
{
log.debug "SETPOINT MODE"
log.debug "descMap.value $descMap.value"
map.name = "thermostatMode"
map.value = getModeMap()[descMap.value]
sendEvent(name:"thermostatMode", value:map.value)
}
else if (descMap.cluster == "0201" && descMap.attrId == "0008")
{
log.debug "HEAT DEMAND"
map.name = "thermostatOperatingState"
map.value = getModeMap()[descMap.value]
if (map.value == "off")
{
map.value = "idle"
}
else
{
map.value = "heating"
}
sendEvent(name:"thermostatOperatingState", value:map.value)
if (settings.heatdetails == "No")
{
map.displayed = false
}
}
}
def result = null
if (map) {
result = createEvent(map)
}
log.debug "Parse returned $map"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def getModeMap() { [
"00":"off",
"04":"comfort",
"05":"eco"
]}
def getFanModeMap() { [
"04":"fanOn",
"05":"fanAuto"
]}
def poll() {
def weather
// If there is a zipcode defined, weather forecast will be sent. Otherwise, no weather forecast.
if (settings.zipcode) {
log.debug "ZipCode: ${settings.zipcode}"
weather = getWeatherFeature( "conditions", settings.zipcode )
// Check if the variable is populated, otherwise return.
if (!weather) {
log.debug( "Something went wrong, no data found." )
return false
}
// Set the tiles
def locationScale = getTemperatureScale()
def tempToSend
log.debug( "Outdoor Temperature: ${weather.current_observation.temp_c}ºC" )
sendEvent( name: 'outsideTemp', value: weather.current_observation.temp_c )
tempToSend = weather.current_observation.temp_c
delayBetween([
quickSetOutTemp(tempToSend),
zigbee.readAttribute(0x201, 0x0000), //Read Local Temperature
zigbee.readAttribute(0x201, 0x0008), //Read PI Heating State
zigbee.readAttribute(0x201, 0x0012), //Read Heat Setpoint
zigbee.readAttribute(0x201, 0x001C), //Read System Mode
my_readAttribute(0x201, 0x401C, ["mfgCode": "0x1185"]), //Read Manufacturer Specific Setpoint Mode
zigbee.readAttribute(0x204, 0x0000), //Read Temperature Display Mode
zigbee.readAttribute(0x204, 0x0001), //Read Keypad Lockout
sendEvent( name: 'change', value: 0 )
], 200)
} else {
delayBetween([
zigbee.readAttribute(0x201, 0x0000), //Read Local Temperature
zigbee.readAttribute(0x201, 0x0008), //Read PI Heating State
zigbee.readAttribute(0x201, 0x0012), //Read Heat Setpoint
zigbee.readAttribute(0x201, 0x001C), //Read System Mode
my_readAttribute(0x201, 0x401C, ["mfgCode": "0x1185"]), //Read Manufacturer Specific Setpoint Mode
zigbee.readAttribute(0x204, 0x0000), //Read Temperature Display Mode
zigbee.readAttribute(0x204, 0x0001), //Read Keypad Lockout
sendEvent( name: 'change', value: 0 )
], 200)
}
}
def getTemperature(value) {
if (value != null) {
log.debug("value $value")
def celsius = Integer.parseInt(value, 16) / 100
if (getTemperatureScale() == "C") {
return celsius
} else {
return Math.round(celsiusToFahrenheit(celsius))
}
}
}
def quickSetHeat(degrees) {
sendEvent( name: 'change', value: 1 )
setHeatingSetpoint(degrees)
}
def setHeatingSetpoint(degrees) {
if (degrees != null) {
def temperatureScale = getTemperatureScale()
def degreesInteger = Math.round(degrees)
float tempfloat;
tempfloat = (Math.round(degrees.toFloat() * 2)) / 2
log.debug "setHeatingSetpoint({$tempfloat} ${temperatureScale})"
sendEvent("name": "heatingSetpoint", "value": tempfloat)
def celsius = (getTemperatureScale() == "C") ? tempfloat : (fahrenheitToCelsius(tempfloat) as Float).round(2)
delayBetween([
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x12 0x29 {" + hex(celsius * 100) + "}",
zigbee.readAttribute(0x201, 0x12), //Read Heat Setpoint
], 100)
}
}
def setCoolingSetpoint(degrees) {
if (degrees != null) {
def degreesInteger = Math.round(degrees)
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "coolingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
}
}
def quickSetOutTemp(degrees) {
setOutdoorTemperature(degrees, 0)
}
def setOutdoorTemperature(degrees, delay = 0) {
setOutdoorTemperature(degrees.toDouble(), delay)
}
def setOutdoorTemperature(Double degrees, Integer delay = 0) {
def deviceScale
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
Integer tempToSend
def tempToSendInString
if (locationScale == "C")
{
deviceScale = 0
}
else
{
deviceScale = 1
}
if (degrees < 0)
{
tempToSend = -degrees*100 - 65536
tempToSend = -tempToSend
}
else
{
tempToSend = (degrees*100)
}
tempToSendInString = zigbee.convertToHexString(tempToSend, 4)
my_writeAttribute(0x201, 0x4001, 0x29, tempToSendInString, ["mfgCode": "0x1185"])
}
def increaseHeatSetpoint()
{
def currentMode = device.currentState("thermostatMode")?.value
if (currentMode != "off")
{
float currentSetpoint = device.currentValue("heatingSetpoint")
def locationScale = getTemperatureScale()
float maxSetpoint
float step
if (locationScale == "C")
{
maxSetpoint = 30;
step = 0.5
}
else
{
maxSetpoint = 86
step = 1
}
if (currentSetpoint < maxSetpoint)
{
currentSetpoint = currentSetpoint + step
quickSetHeat(currentSetpoint)
}
}
}
def decreaseHeatSetpoint()
{
def currentMode = device.currentState("thermostatMode")?.value
if (currentMode != "off")
{
float currentSetpoint = device.currentValue("heatingSetpoint")
def locationScale = getTemperatureScale()
float minSetpoint
float step
if (locationScale == "C")
{
minSetpoint = 5;
step = 0.5
}
else
{
minSetpoint = 41
step = 1
}
if (currentSetpoint > minSetpoint)
{
currentSetpoint = currentSetpoint - step
quickSetHeat(currentSetpoint)
}
}
}
def modes() {
["comfort", "eco", "off"]
}
def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "comfort"
def modeOrder = modes()
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(currentMode)
def modeNumber;
Integer setpointModeNumber;
def modeToSendInString;
if (nextMode == "comfort")
{
modeNumber = 04
setpointModeNumber = 04
}
else if (nextMode == "eco")
{
modeNumber = 04
setpointModeNumber = 05
}
else
{
modeNumber = 00
setpointModeNumber = 00
}
if (supportedModes?.contains(currentMode)) {
while (!supportedModes.contains(nextMode) && nextMode != "comfort") {
nextMode = next(nextMode)
}
}
state.lastTriedMode = nextMode
modeToSendInString = zigbee.convertToHexString(setpointModeNumber, 2)
delayBetween([
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x001C 0x30 {$modeNumber}",
my_writeAttribute(0x201, 0x401C, 0x30, modeToSendInString, ["mfgCode": "0x1185"]),
poll()
], 1000)
}
def setThermostatMode() {
log.debug "switching thermostatMode"
def currentMode = device.currentState("thermostatMode")?.value
def modeOrder = modes()
def index = modeOrder.indexOf(currentMode)
def next = index >= 0 && index < modeOrder.size() - 1 ? modeOrder[index + 1] : modeOrder[0]
log.debug "switching mode from $currentMode to $next"
"$next"()
}
def setThermostatFanMode() {
log.debug "Switching fan mode"
def currentFanMode = device.currentState("thermostatFanMode")?.value
log.debug "switching fan from current mode: $currentFanMode"
def returnCommand
switch (currentFanMode) {
case "fanAuto":
returnCommand = fanOn()
break
case "fanOn":
returnCommand = fanAuto()
break
}
if(!currentFanMode) { returnCommand = fanAuto() }
returnCommand
}
def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})"
"$value"()
}
def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})"
"$value"()
}
def off() {
log.debug "off"
sendEvent("name":"thermostatMode", "value":"off")
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x1C 0x30 {00}"
}
def cool() {
log.debug "cool"
sendEvent("name":"thermostatMode", "value":"cool")
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x1C 0x30 {03}"
}
def heat() {
log.debug "heat"
sendEvent("name":"thermostatMode", "value":"heat")
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x1C 0x30 {04}"
}
def emergencyHeat() {
log.debug "emergencyHeat"
sendEvent("name":"thermostatMode", "value":"emergency heat")
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x1C 0x30 {05}"
}
def on() {
fanOn()
}
def fanOn() {
log.debug "fanOn"
sendEvent("name":"thermostatFanMode", "value":"fanOn")
"st wattr 0x${device.deviceNetworkId} 0x19 0x202 0 0x30 {04}"
}
def auto() {
fanAuto()
}
def fanAuto() {
log.debug "fanAuto"
sendEvent("name":"thermostatFanMode", "value":"fanAuto")
"st wattr 0x${device.deviceNetworkId} 1 0x202 0 0x30 {05}"
}
def configure() {
log.debug "binding to Thermostat cluster"
delayBetween([
"zdo bind 0x${device.deviceNetworkId} 1 0x19 0x201 {${device.zigbeeId}} {}",
//Cluster ID (0x0201 = Thermostat Cluster), Attribute ID, Data Type, Payload (Min report, Max report, On change trigger)
zigbee.configureReporting(0x0201, 0x0000, 0x29, 10, 60, 50), //Attribute ID 0x0000 = local temperature, Data Type: S16BIT
zigbee.configureReporting(0x0201, 0x0012, 0x29, 1, 0, 50), //Attribute ID 0x0012 = occupied heat setpoint, Data Type: S16BIT
zigbee.configureReporting(0x0201, 0x001C, 0x30, 1, 0, 1), //Attribute ID 0x001C = system mode, Data Type: 8 bits enum
zigbee.configureReporting(0x0201, 0x401C, 0x30, 1, 0, 1), //Attribute ID 0x401C = manufacturer specific setpoint mode, Data Type: 8 bits enum
zigbee.configureReporting(0x0201, 0x0008, 0x20, 300, 900, 5), //Attribute ID 0x0008 = pi heating demand, Data Type: U8BIT
//Cluster ID (0x0204 = Thermostat Ui Conf Cluster), Attribute ID, Data Type, Payload (Min report, Max report, On change trigger)
zigbee.configureReporting(0x0204, 0x0000, 0x30, 1, 0, 1), //Attribute ID 0x0000 = temperature display mode, Data Type: 8 bits enum
zigbee.configureReporting(0x0204, 0x0001, 0x30, 1, 0, 1), //Attribute ID 0x0001 = keypad lockout, Data Type: 8 bits enum
//Read the configured variables
zigbee.readAttribute(0x201, 0x0000), //Read Local Temperature
zigbee.readAttribute(0x201, 0x0012), //Read Heat Setpoint
zigbee.readAttribute(0x201, 0x001C), //Read System Mode
my_readAttribute(0x201, 0x401C, ["mfgCode": "0x1185"]), //Read Manufacturer Specific Setpoint Mode
zigbee.readAttribute(0x201, 0x0008), //Read PI Heating State
zigbee.readAttribute(0x204, 0x0000), //Read Temperature Display Mode
zigbee.readAttribute(0x204, 0x0001), //Read Keypad Lockout
], 200)
}
def updated() {
log.debug "updated called"
response(parameterSetting())
}
def parameterSetting() {
def format = null
def lockmode = null
def valid_format = 0
def valid_lock = 0
log.info "unitformat : $settings.unitformat"
if (settings.unitformat == "Celsius") {
format = 0x00
valid_format = 1
}
else if (settings.unitformat == "Fahrenheit") {
format = 0x01
valid_format = 1
}
log.info "lock : $settings.lock"
if (settings.lock == "Yes") {
lockmode = 0x01
valid_lock = 1
}
else if (settings.lock == "No") {
lockmode = 0x00
valid_lock = 1
}
if ((valid_format == 1) && (valid_lock == 1))
{
log.info "both valid"
delayBetween([
zigbee.writeAttribute(0x204, 0x00, 0x30, format), //Write Unit Format Mode
zigbee.writeAttribute(0x204, 0x01, 0x30, lockmode), //Write Lock Mode
poll(),
], 200)
}
else if (valid_format == 1)
{
log.info "format valid"
delayBetween([
zigbee.writeAttribute(0x204, 0x00, 0x30, format), //Write Unit Format Mode
poll(),
], 200)
}
else if (valid_lock == 1)
{
log.info "lock valid"
delayBetween([
zigbee.writeAttribute(0x204, 0x01, 0x30, lockmode), //Write Lock Mode
poll(),
], 200)
}
else
{
log.info "nothing valid"
}
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
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 my_readAttribute(cluster, attributeId, Map additional=null)
{
if (additional?.get("mfgCode")) {
[ "zcl mfg-code ${additional['mfgCode']}", "delay 200",
"zcl global read ${cluster} ${attributeId}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}" ]
} else {
zigbee.readAttribute(cluster, attributeId)
}
}
def my_writeAttribute(cluster, attributeId, dataType, value, Map additional=null)
{
value = swapEndianHex(value)
if (additional ?.get("mfgCode")) {
[ "zcl mfg-code ${additional['mfgCode']}", "delay 200",
"zcl global write ${cluster} ${attributeId} ${dataType} {${value}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}" ]
} else {
zigbee.writeAttribute(cluster, attributeId, dataType, value)
}
}

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Bose SoundTouch (Connect)
*

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Hue Service Manager
*

View File

@@ -0,0 +1,447 @@
/**
* JSON Complete API
*
* Copyright 2017 Paul Lovelace
*
*/
definition(
name: "JSON Complete API",
namespace: "smartthings",
author: "Ashok Malhotra",
description: "API for JSON with complete set of devices",
category: "SmartThings Labs",
iconUrl: "https://raw.githubusercontent.com/pdlove/homebridge-smartthings/master/smartapps/JSON%401.png",
iconX2Url: "https://raw.githubusercontent.com/pdlove/homebridge-smartthings/master/smartapps/JSON%402.png",
iconX3Url: "https://raw.githubusercontent.com/pdlove/homebridge-smartthings/master/smartapps/JSON%403.png",
oauth: true)
preferences {
page(name: "copyConfig")
}
//When adding device groups, need to add here
def copyConfig() {
if (!state.accessToken) {
createAccessToken()
}
dynamicPage(name: "copyConfig", title: "Configure Devices", install:true, uninstall:true) {
section("Select devices to include in the /devices API call") {
paragraph "Version 0.5.5"
input "deviceList", "capability.refresh", title: "Most Devices", multiple: true, required: false
input "sensorList", "capability.sensor", title: "Sensor Devices", multiple: true, required: false
input "switchList", "capability.switch", title: "All Switches", multiple: true, required: false
//paragraph "Devices Selected: ${deviceList ? deviceList?.size() : 0}\nSensors Selected: ${sensorList ? sensorList?.size() : 0}\nSwitches Selected: ${switchList ? switchList?.size() : 0}"
}
section("Configure Pubnub") {
input "pubnubSubscribeKey", "text", title: "PubNub Subscription Key", multiple: false, required: false
input "pubnubPublishKey", "text", title: "PubNub Publish Key", multiple: false, required: false
input "subChannel", "text", title: "Channel (Can be anything)", multiple: false, required: false
}
section() {
paragraph "View this SmartApp's configuration to use it in other places."
href url:"${apiServerUrl("/api/smartapps/installations/${app.id}/config?access_token=${state.accessToken}")}", style:"embedded", required:false, title:"Config", description:"Tap, select, copy, then click \"Done\""
}
section() {
paragraph "View the JSON generated from the installed devices."
href url:"${apiServerUrl("/api/smartapps/installations/${app.id}/devices?access_token=${state.accessToken}")}", style:"embedded", required:false, title:"Device Results", description:"View accessories JSON"
}
section() {
paragraph "Enter the name you would like shown in the smart app list"
label title:"SmartApp Label (optional)", required: false
}
}
}
def renderDevices() {
def deviceData = []
deviceList.each {
try {
deviceData << [name: it.displayName,
basename: it.name,
deviceid: it.id,
status: it.status,
manufacturerName: it.getManufacturerName(),
modelName: it.getModelName(),
lastTime: it.getLastActivity(),
capabilities: deviceCapabilityList(it),
commands: deviceCommandList(it),
attributes: deviceAttributeList(it)
]
} catch (e) {
log.error("Error Occurred Parsing Device "+it.displayName+", Error " + e)
}
}
sensorList.each {
try {
deviceData << [name: it.displayName,
basename: it.name,
deviceid: it.id,
status: it.status,
manufacturerName: it.getManufacturerName(),
modelName: it.getModelName(),
lastTime: it.getLastActivity(),
capabilities: deviceCapabilityList(it),
commands: deviceCommandList(it),
attributes: deviceAttributeList(it)
]
} catch (e) {
log.error("Error Occurred Parsing Device "+it.displayName+", Error " + e)
}
}
switchList.each {
try {
deviceData << [name: it.displayName,
basename: it.name,
deviceid: it.id,
status: it.status,
manufacturerName: it.getManufacturerName(),
modelName: it.getModelName(),
lastTime: it.getLastActivity(),
capabilities: deviceCapabilityList(it),
commands: deviceCommandList(it),
attributes: deviceAttributeList(it)
]
} catch (e) {
log.error("Error Occurred Parsing Device "+it.displayName+", Error " + e)
}
}
return deviceData
}
def findDevice(paramid) {
def device = deviceList.find { it.id == paramid }
if (device) return device
device = sensorList.find { it.id == paramid }
if (device) return device
device = switchList.find { it.id == paramid }
return device
}
//No more individual device group definitions after here.
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
if(!state.accessToken) {
createAccessToken()
}
registerAll()
state.subscriptionRenewed = 0
subscribe(location, null, HubResponseEvent, [filterEvents:false])
log.debug "0.5.5"
}
def authError() {
[error: "Permission denied"]
}
def renderConfig() {
def configJson = new groovy.json.JsonOutput().toJson([
description: "JSON API",
platforms: [
[
platform: "SmartThings",
name: "SmartThings",
app_url: apiServerUrl("/api/smartapps/installations/"),
app_id: app.id,
access_token: state.accessToken
]
],
])
def configString = new groovy.json.JsonOutput().prettyPrint(configJson)
render contentType: "text/plain", data: configString
}
def renderLocation() {
[
latitude: location.latitude,
longitude: location.longitude,
mode: location.mode,
name: location.name,
temperature_scale: location.temperatureScale,
zip_code: location.zipCode,
hubIP: location.hubs[0].localIP,
smartapp_version: '0.5.5'
]
}
def CommandReply(statusOut, messageOut) {
def replyData =
[
status: statusOut,
message: messageOut
]
def replyJson = new groovy.json.JsonOutput().toJson(replyData)
render contentType: "application/json", data: replyJson
}
def deviceCommand() {
log.info("Command Request")
def device = findDevice(params.id)
def command = params.command
if (!device) {
log.error("Device Not Found")
CommandReply("Failure", "Device Not Found")
} else if (!device.hasCommand(command)) {
log.error("Device "+device.displayName+" does not have the command "+command)
CommandReply("Failure", "Device "+device.displayName+" does not have the command "+command)
} else {
def value1 = request.JSON?.value1
def value2 = request.JSON?.value2
try {
if (value2) {
device."$command"(value1,value2)
} else if (value1) {
device."$command"(value1)
} else {
device."$command"()
}
log.info("Command Successful for Device "+device.displayName+", Command "+command)
CommandReply("Success", "Device "+device.displayName+", Command "+command)
} catch (e) {
log.error("Error Occurred For Device "+device.displayName+", Command "+command)
CommandReply("Failure", "Error Occurred For Device "+device.displayName+", Command "+command)
}
}
}
def deviceAttribute() {
def device = findDevice(params.id)
def attribute = params.attribute
if (!device) {
httpError(404, "Device not found")
} else {
def currentValue = device.currentValue(attribute)
[currentValue: currentValue]
}
}
def deviceQuery() {
def device = findDevice(params.id)
if (!device) {
device = null
httpError(404, "Device not found")
}
if (result) {
def jsonData =
[
name: device.displayName,
deviceid: device.id,
capabilities: deviceCapabilityList(device),
commands: deviceCommandList(device),
attributes: deviceAttributeList(device)
]
def resultJson = new groovy.json.JsonOutput().toJson(jsonData)
render contentType: "application/json", data: resultJson
}
}
def deviceCapabilityList(device) {
def i=0
device.capabilities.collectEntries { capability->
[
(capability.name):1
]
}
}
def deviceCommandList(device) {
def i=0
device.supportedCommands.collectEntries { command->
[
(command.name): (command.arguments)
]
}
}
def deviceAttributeList(device) {
device.supportedAttributes.collectEntries { attribute->
try {
[
(attribute.name): device.currentValue(attribute.name)
]
} catch(e) {
[
(attribute.name): null
]
}
}
}
def getAllData() {
//Since we're about to send all of the data, we'll count this as a subscription renewal and clear out pending changes.
state.subscriptionRenewed = now()
state.devchanges = []
def deviceData =
[ location: renderLocation(),
deviceList: renderDevices() ]
def deviceJson = new groovy.json.JsonOutput().toJson(deviceData)
render contentType: "application/json", data: deviceJson
}
def startSubscription() {
//This simply registers the subscription.
state.subscriptionRenewed = now()
def deviceJson = new groovy.json.JsonOutput().toJson([status: "Success"])
render contentType: "application/json", data: deviceJson
}
def endSubscription() {
//Because it takes too long to register for an api command, we don't actually unregister.
//We simply blank the devchanges and change the subscription renewal to two hours ago.
state.devchanges = []
state.subscriptionRenewed = 0
def deviceJson = new groovy.json.JsonOutput().toJson([status: "Success"])
render contentType: "application/json", data: deviceJson
}
def registerAll() {
//This has to be done at startup because it takes too long for a normal command.
log.debug "Registering All Events"
state.devchanges = []
registerChangeHandler(deviceList)
registerChangeHandler(sensorList)
registerChangeHandler(switchList)
}
def registerChangeHandler(myList) {
myList.each { myDevice ->
def theAtts = myDevice.supportedAttributes
theAtts.each {att ->
subscribe(myDevice, att.name, changeHandler)
log.debug "Registering ${myDevice.displayName}.${att.name}"
}
}
}
def changeHandler(evt) {
//Send to Pubnub if we need to.
if (pubnubPublishKey!=null) {
def deviceData = [device: evt.deviceId, attribute: evt.name, value: evt.value, date: evt.date]
def changeJson = new groovy.json.JsonOutput().toJson(deviceData)
def changeData = URLEncoder.encode(changeJson)
def uri = "http://pubsub.pubnub.com/publish/${pubnubPublishKey}/${pubnubSubscribeKey}/0/${subChannel}/0/${changeData}"
log.debug "${uri}"
httpGet(uri)
}
if (state.directIP!="") {
//Send Using the Direct Mechanism
def deviceData = [device: evt.deviceId, attribute: evt.name, value: evt.value, date: evt.date]
//How do I control the port?!?
log.debug "Sending Update to ${state.directIP}:${state.directPort}"
def result = new physicalgraph.device.HubAction(
method: "GET",
path: "/update",
headers: [
HOST: "${state.directIP}:${state.directPort}",
change_device: evt.deviceId,
change_attribute: evt.name,
change_value: evt.value,
change_date: evt.date
]
)
sendHubCommand(result)
}
//Only add to the state's devchanges if the endpoint has renewed in the last 10 minutes.
if (state.subscriptionRenewed>(now()-(1000*60*10))) {
if (evt.isStateChange()) {
state.devchanges << [device: evt.deviceId, attribute: evt.name, value: evt.value, date: evt.date]
}
} else if (state.subscriptionRenewed>0) { //Otherwise, clear it
log.debug "Endpoint Subscription Expired. No longer storing changes for devices."
state.devchanges=[]
state.subscriptionRenewed=0
}
}
def getChangeEvents() {
//Store the changes so we can swap it out very quickly and eliminate the possibility of losing any.
//This is mainly to make this thread safe because I'm willing to bet that a change event can fire
//while generating/sending the JSON.
def oldchanges = state.devchanges
state.devchanges=[]
state.subscriptionRenewed = now()
if (oldchanges.size()==0) {
def deviceJson = new groovy.json.JsonOutput().toJson([status: "None"])
render contentType: "application/json", data: deviceJson
} else {
def changeJson = new groovy.json.JsonOutput().toJson([status: "Success", attributes:oldchanges])
render contentType: "application/json", data: changeJson
}
}
def enableDirectUpdates() {
log.debug("Command Request")
state.directIP = params.ip
state.directPort = params.port
log.debug("Trying ${state.directIP}:${state.directPort}")
def result = new physicalgraph.device.HubAction(
method: "GET",
path: "/initial",
headers: [
HOST: "${state.directIP}:${state.directPort}"
],
query: deviceData
)
sendHubCommand(result)
}
def HubResponseEvent(evt) {
log.debug(evt.description)
}
def locationHandler(evt) {
def description = evt.description
def hub = evt?.hubId
log.debug "cp desc: " + description
if (description.count(",") > 4)
{
def bodyString = new String(description.split(',')[5].split(":")[1].decodeBase64())
log.debug(bodyString)
}
}
def getSubscriptionService() {
def replyData =
[
pubnub_publishkey: pubnubPublishKey,
pubnub_subscribekey: pubnubSubscribeKey,
pubnub_channel: subChannel
]
def replyJson = new groovy.json.JsonOutput().toJson(replyData)
render contentType: "application/json", data: replyJson
}
mappings {
if (!params.access_token || (params.access_token && params.access_token != state.accessToken)) {
path("/devices") { action: [GET: "authError"] }
path("/config") { action: [GET: "authError"] }
path("/location") { action: [GET: "authError"] }
path("/:id/command/:command") { action: [POST: "authError"] }
path("/:id/query") { action: [GET: "authError"] }
path("/:id/attribute/:attribute") { action: [GET: "authError"] }
path("/subscribe") { action: [GET: "authError"] }
path("/getUpdates") { action: [GET: "authError"] }
path("/unsubscribe") { action: [GET: "authError"] }
path("/startDirect/:ip/:port") { action: [GET: "authError"] }
path("/getSubcriptionService") { action: [GET: "authError"] }
} else {
path("/devices") { action: [GET: "getAllData"] }
path("/config") { action: [GET: "renderConfig"] }
path("/location") { action: [GET: "renderLocation"] }
path("/:id/command/:command") { action: [POST: "deviceCommand"] }
path("/:id/query") { action: [GET: "deviceQuery"] }
path("/:id/attribute/:attribute") { action: [GET: "deviceAttribute"] }
path("/subscribe") { action: [GET: "startSubscription"] }
path("/getUpdates") { action: [GET: "getChangeEvents"] }
path("/unsubscribe") { action: [GET: "endSubscription"] }
path("/startDirect/:ip/:port") { action: [GET: "enableDirectUpdates"] }
path("/getSubcriptionService") { action: [GET: "getSubscriptionService"] }
}
}

View File

@@ -1,3 +1,5 @@
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
/**
* Copyright 2015 SmartThings
*