Compare commits

..

5 Commits

Author SHA1 Message Date
dsainteclaire
27d9bdd86c Merge pull request #2231 from marstorp/dvcsmp2973
DVCSMP-2973 CT100/Honeywell Z-wave NullPointerException
2017-08-10 16:52:35 -07:00
Ingvar Marstorp
2b09b7c574 Update zwave-thermostat.groovy 2017-08-10 12:23:50 -07:00
Ingvar Marstorp
eb9b55e01c Update ct100-thermostat.groovy 2017-08-10 12:19:12 -07:00
marstorp
10fef276db DVCSMP-2973 CT100/Honeywell Z-wave NullPointerException
CT100/Honeywell Z-wave NullPointerException when changing mode
Changed name of private method setThermostatMode(data) to setGetThermostatMode(data)
and method setThermostatFanMode(data) to setGetThermostatFanMode(data) so that platform
calls the exposed methods setThermostatMode(String value) setThermostatFanMode(String value)
as these validates the argument against the supported modes and then calls the private methods.
Also removed some of the leftshift operators that showed errors in Sumo logs.
2017-08-10 11:42:05 -07:00
Vinay Rao
2ea99a1b9e Merge pull request #2226 from SmartThingsCommunity/master
Rolling up master to staging
2017-08-08 13:48:18 -07:00
7 changed files with 49 additions and 1554 deletions

View File

@@ -1,197 +0,0 @@
/**
* iHomeSmartPlug iSP5
*
* Copyright 2016 EVRYTHNG LTD
*
* 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.
*
* Last reviewed:20.07.2017
* - Added capabilities: Health Check, Outlet, Light
* - Added ocfDeviceType
* - Changed background colour of tiles
* - Added lifecycle functions
* - Added ping method
*/
import groovy.json.JsonOutput
metadata {
definition (name: "iHomeSmartPlug-iSP5", namespace: "ihome_devices", author: "iHome", ocfDeviceType: "oic.d.smartplug") {
capability "Actuator" //The device is an actuator (provides actions)
capability "Sensor" //The device s a sensor (provides properties)
capability "Refresh" //Enable the refresh by the user
capability "Switch" //Device complies to the SmartThings switch capability
capability "Health Check"
capability "Outlet" //Needed for Google Home
capability "Light" //Needed for Google Home
attribute "firmware","string" //Mapping the custom property firmware
attribute "model","string" //Mapping the custom property model (model of the plug)
attribute "status", "string" //Mapping the status of the last call to the cloud in a message to the user
}
tiles(scale: 2){
multiAttributeTile(name:"control", type:"generic", width:6, height:4) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState( "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "off")
attributeState( "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState: "on")
}
tileAttribute ("device.status", key: "SECONDARY_CONTROL") {
attributeState "status", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state ("default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh")
}
valueTile("firmware", "device.firmware", width:2, height:2, decoration: "flat") {
state "firmware", label:'Firmware v${currentValue}'
}
valueTile("model", "device.model", width:2, height:2, decoration: "flat") {
state "model", label:'${currentValue}'
}
main (["control"])
details (["control","refresh","firmware","model"])
}
}
def initialize() {
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def installed() {
log.debug "installed()"
initialize()
}
def updated() {
log.debug "updated()"
initialize()
}
/*
* This method creates the internal SmartThings events to handle changes in the properties of the plug
*/
def updateProperties(Map properties) {
log.debug "Updating plug's properties: ${properties}"
def connected = (properties["~connected"]?.value == true)
if (connected == true){ //only update if plug is connected
//update status message
sendEvent(name: "status", value: parent.getConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getConnectedMessage()}"
//update currentpowerstate1
def currentpowerstate1 = properties["currentpowerstate1"].value
if (currentpowerstate1 != null){
log.info "Updating ${device.displayName}: property currentpowerstate1 set to value: ${currentpowerstate1}"
currentpowerstate1 = "${currentpowerstate1}"
if (currentpowerstate1 == "1") {
sendEvent(name: "switch", value: "on")
}
else if (currentpowerstate1 == "0") {
sendEvent(name: "switch", value: "off")
}
}
//update firmware version
def appfwversion = properties["appfwversion"].value
if (appfwversion != null){
log.info "Updating ${device.displayName}: property appfwversion set to value: ${appfwversion}"
appfwversion = "${appfwversion}"
sendEvent(name: "firmware", value: appfwversion)
}
//update model
log.info "Updating ${device.displayName}: property model set to value: iSP5"
sendEvent(name:"model", value:"iSP5")
} else { //the plug is not connected
//update status message
sendEvent(name: "status", value: parent.getPlugNotConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getPlugNotConnectedMessage()}"
}
}
// Process the polling error, changing the status message
def pollError(){
log.info "Error retrieving info from the cloud"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
/*
* This method handles the switch.on function by updating the corresponding property in the cloud
*/
def on() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn on if the device is off
if (device.currentState("switch")?.value.toLowerCase().startsWith("off")){
log.info "Updating ${device.displayName} in the cloud: property targetpowerstate1 set to value: 1"
def propertyUpdateJSON = "[{\"key\":\"targetpowerstate1\", \"value\":\"1\"}]"
def success = parent.propertyUpdate(device.deviceNetworkId, propertyUpdateJSON)
if(success){
log.info "Updating ${device.displayName}: sending switch.on command"
sendEvent(name: "switch", value: "on")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the switch.off function by updating the corresponding property in the cloud
*/
def off() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn off if the device is on
if (device.currentState("switch")?.value.toLowerCase().startsWith("on")){
log.info "Updating ${device.displayName} in the cloud: property targetpowerstate1 set to value: 0"
def propertyUpdateJSON = "[{\"key\":\"targetpowerstate1\", \"value\":\"0\"}]"
def success = parent.propertyUpdate(device.deviceNetworkId, propertyUpdateJSON)
if (success){
log.info "Updating ${device.displayName}: sending switch.off command"
sendEvent(name: "switch", value: "off")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the refresh capability
*/
def refresh() {
parent.pollChildren(device.deviceNetworkId)
}
def ping() {
refresh()
}

View File

@@ -1,201 +0,0 @@
/**
* iHomeSmartPlug iSP6
*
* Copyright 2016 EVRYTHNG LTD
*
* 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.
*
* Last reviewed:20.07.2017
* - Added capabilities: Health Check, Outlet, Light
* - Added ocfDeviceType
* - Changed background colour of tiles
* - Added lifecycle functions
* - Added ping method
*/
import groovy.json.JsonOutput
metadata {
definition (name: "iHomeSmartPlug-iSP6", namespace: "ihome_devices", author: "iHome", ocfDeviceType: "oic.d.smartplug") {
capability "Actuator" //The device is an actuator (provides actions)
capability "Sensor" //The device s a sensor (provides properties)
capability "Refresh" //Enable the refresh by the user
capability "Switch" //Device complies to the SmartThings switch capability
capability "Health Check"
capability "Outlet" //Needed for Google Home
capability "Light" //Needed for Google Home
attribute "firmware","string" //Mapping the custom property firmware
attribute "model","string" //Mapping the custom property model (model of the plug)
attribute "status", "string" //Mapping the status of the last call to the cloud
}
tiles(scale: 2){
multiAttributeTile(name:"control", type:"generic", width:6, height:4) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState( "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "off")
attributeState( "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState: "on")
}
tileAttribute ("device.status", key: "SECONDARY_CONTROL") {
attributeState "status", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state ("default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh")
}
valueTile("firmware", "device.firmware", width:2, height:2, decoration: "flat") {
state "firmware", label:'Firmware v${currentValue}'
}
valueTile("model", "device.model", width:2, height:2, decoration: "flat") {
state "model", label:'${currentValue}'
}
main (["control"])
details (["control","refresh","firmware","model"])
}
}
def initialize() {
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def installed() {
log.debug "installed()"
initialize()
}
def updated() {
log.debug "updated()"
initialize()
}
/*
* This method creates the internal SmartThings events to handle changes in the properties of the plug
*/
def updateProperties(Map properties) {
log.debug "Updating plug's properties: ${properties}"
def connected = (properties["~connected"]?.value == true)
if (connected == true){ //only update if plug is connected
//update status message
sendEvent(name: "status", value: parent.getConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getConnectedMessage()}"
//update currentpowerstate1
def currentpowerstate1 = properties["currentpowerstate1"].value
if (currentpowerstate1 != null){
log.info "Updating ${device.displayName}: property currentpowerstate1 set to value: ${currentpowerstate1}"
currentpowerstate1 = "${currentpowerstate1}"
if (currentpowerstate1 == "1") {
sendEvent(name: "switch", value: "on")
}
else if (currentpowerstate1 == "0") {
sendEvent(name: "switch", value: "off")
}
}
//update firmware version
def appfwversion = properties["appfwversion"].value
if (appfwversion != null){
log.info "Updating ${device.displayName}: property appfwversion set to value: ${appfwversion}"
appfwversion = "${appfwversion}"
sendEvent(name: "firmware", value: appfwversion)
}
//update model
log.info "Updating ${device.displayName}: property model set to value: iSP6"
sendEvent(name:"model", value:"iSP6")
} else { //the plug is not connected
//update status message
sendEvent(name: "status", value: parent.getPlugNotConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getPlugNotConnectedMessage()}"
}
}
// Process the polling error, changing the status message
def pollError(){
log.info "Error retrieving info from the cloud"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
/*
* This method handles the switch.on function by updating the corresponding property in the cloud
*/
def on() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn on if the device is off
if (device.currentState("switch")?.value.toLowerCase().startsWith("off")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOn"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOn\"}"
def success = parent.sendAction("_turnOn", actionJSON)
if(success){
log.info "Updating ${device.displayName}: sending switch.on command"
sendEvent(name: "switch", value: "on")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the switch.off function by updating the corresponding property in the cloud
*/
def off() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn off only if the device is on
if (device.currentState("switch")?.value.toLowerCase().startsWith("on")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOff"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOff\"}"
def success = parent.sendAction("_turnOff", actionJSON)
if (success) {
log.info "Updating ${device.displayName}: sending switch.off command"
sendEvent(name: "switch", value: "off")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the refresh capability
*/
def refresh() {
parent.pollChildren(device.deviceNetworkId)
}
def ping() {
refresh()
}

View File

@@ -1,201 +0,0 @@
/**
* iHomeSmartPlug iSP6X
*
* Copyright 2016 EVRYTHNG LTD
*
* 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.
*
* Last reviewed:20.07.2017
* - Added capabilities: Health Check, Outlet, Light
* - Added ocfDeviceType
* - Changed background colour of tiles
* - Added lifecycle functions
* - Added ping method
*/
import groovy.json.JsonOutput
metadata {
definition (name: "iHomeSmartPlug-iSP6X", namespace: "ihome_devices", author: "iHome", ocfDeviceType: "oic.d.smartplug") {
capability "Actuator" //The device is an actuator (provides actions)
capability "Sensor" //The device s a sensor (provides properties)
capability "Refresh" //Enable the refresh by the user
capability "Switch" //Device complies to the SmartThings switch capability
capability "Health Check"
capability "Outlet" //Needed for Google Home
capability "Light" //Needed for Google Home
attribute "firmware","string" //Mapping the custom property firmware
attribute "model","string" //Mapping the custom property model (model of the plug)
attribute "status", "string" //Mapping the status of the last call to the cloud
}
tiles(scale: 2){
multiAttributeTile(name:"control", type:"generic", width:6, height:4) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState( "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "off")
attributeState( "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState: "on")
}
tileAttribute ("device.status", key: "SECONDARY_CONTROL") {
attributeState "status", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state ("default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh")
}
valueTile("firmware", "device.firmware", width:2, height:2, decoration: "flat") {
state "firmware", label:'Firmware v${currentValue}'
}
valueTile("model", "device.model", width:2, height:2, decoration: "flat") {
state "model", label:'${currentValue}'
}
main (["control"])
details (["control","refresh","firmware","model"])
}
}
def initialize() {
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def installed() {
log.debug "installed()"
initialize()
}
def updated() {
log.debug "updated()"
initialize()
}
/*
* This method creates the internal SmartThings events to handle changes in the properties of the plug
*/
def updateProperties(Map properties) {
log.debug "Updating plug's properties: ${properties}"
def connected = (properties["~connected"]?.value == true)
if (connected == true){ //only update if plug is connected
//update status message
sendEvent(name: "status", value: parent.getConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getConnectedMessage()}"
//update currentpowerstate1
def currentpowerstate1 = properties["currentpowerstate1"].value
if (currentpowerstate1 != null){
log.info "Updating ${device.displayName}: property currentpowerstate1 set to value: ${currentpowerstate1}"
currentpowerstate1 = "${currentpowerstate1}"
if (currentpowerstate1 == "1") {
sendEvent(name: "switch", value: "on")
}
else if (currentpowerstate1 == "0") {
sendEvent(name: "switch", value: "off")
}
}
//update firmware version
def appfwversion = properties["appfwversion"].value
if (appfwversion != null){
log.info "Updating ${device.displayName}: property appfwversion set to value: ${appfwversion}"
appfwversion = "${appfwversion}"
sendEvent(name: "firmware", value: appfwversion)
}
//update model
log.info "Updating ${device.displayName}: property model set to value: iSP6X"
sendEvent(name:"model", value:"iSP6X")
} else { //the plug is not connected
//update status message
sendEvent(name: "status", value: parent.getPlugNotConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getPlugNotConnectedMessage()}"
}
}
// Process the polling error, changing the status message
def pollError(){
log.info "Error retrieving info from the cloud"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
/*
* This method handles the switch.on function by updating the corresponding property in the cloud
*/
def on() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn on if the device is off
if (device.currentState("switch")?.value.toLowerCase().startsWith("off")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOn"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOn\"}"
def success = parent.sendAction("_turnOn", actionJSON)
if(success){
log.info "Updating ${device.displayName}: sending switch.on command"
sendEvent(name: "switch", value: "on")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the switch.off function by updating the corresponding property in the cloud
*/
def off() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn off only if the device is on
if (device.currentState("switch")?.value.toLowerCase().startsWith("on")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOff"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOff\"}"
def success = parent.sendAction("_turnOff", actionJSON)
if (success) {
log.info "Updating ${device.displayName}: sending switch.off command"
sendEvent(name: "switch", value: "off")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the refresh capability
*/
def refresh() {
parent.pollChildren(device.deviceNetworkId)
}
def ping() {
refresh()
}

View File

@@ -1,201 +0,0 @@
/**
* iHomeSmartPlug iSP8
*
* Copyright 2016 EVRYTHNG LTD
*
* 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.
*
* Last reviewed:20.07.2017
* - Added capabilities: Health Check, Outlet, Light
* - Added ocfDeviceType
* - Changed background colour of tiles
* - Added lifecycle functions
* - Added ping method
*/
import groovy.json.JsonOutput
metadata {
definition (name: "iHomeSmartPlug-iSP8", namespace: "ihome_devices", author: "iHome", ocfDeviceType: "oic.d.smartplug") {
capability "Actuator" //The device is an actuator (provides actions)
capability "Sensor" //The device s a sensor (provides properties)
capability "Refresh" //Enable the refresh by the user
capability "Switch" //Device complies to the SmartThings switch capability
capability "Health Check"
capability "Outlet" //Needed for Google Home
capability "Light" //Needed for Google Home
attribute "firmware","string" //Mapping the custom property firmware
attribute "model","string" //Mapping the custom property model (model of the plug)
attribute "status", "string" //Mapping the status of the last call to the cloud
}
tiles(scale: 2){
multiAttributeTile(name:"control", type:"generic", width:6, height:4) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState( "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "off")
attributeState( "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00A0DC", nextState: "on")
}
tileAttribute ("device.status", key: "SECONDARY_CONTROL") {
attributeState "status", label:'${currentValue}'
}
}
standardTile("refresh", "device.refresh", width:2, height:2, inactiveLabel: false, decoration: "flat") {
state ("default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh")
}
valueTile("firmware", "device.firmware", width:2, height:2, decoration: "flat") {
state "firmware", label:'Firmware v${currentValue}'
}
valueTile("model", "device.model", width:2, height:2, decoration: "flat") {
state "model", label:'${currentValue}'
}
main (["control"])
details (["control","refresh","firmware","model"])
}
}
def initialize() {
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "cloud", scheme:"untracked"]), displayed: false)
}
def installed() {
log.debug "installed()"
initialize()
}
def updated() {
log.debug "updated()"
initialize()
}
/*
* This method creates the internal SmartThings events to handle changes in the properties of the plug
*/
def updateProperties(Map properties) {
log.debug "Updating plug's properties: ${properties}"
def connected = (properties["~connected"]?.value == true)
if (connected == true){ //only update if plug is connected
//update status message
sendEvent(name: "status", value: parent.getConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getConnectedMessage()}"
//update currentpowerstate1
def currentpowerstate1 = properties["currentpowerstate1"].value
if (currentpowerstate1 != null){
log.info "Updating ${device.displayName}: property currentpowerstate1 set to value: ${currentpowerstate1}"
currentpowerstate1 = "${currentpowerstate1}"
if (currentpowerstate1 == "1") {
sendEvent(name: "switch", value: "on")
}
else if (currentpowerstate1 == "0") {
sendEvent(name: "switch", value: "off")
}
}
//update firmware version
def appfwversion = properties["appfwversion"].value
if (appfwversion != null){
log.info "Updating ${device.displayName}: property appfwversion set to value: ${appfwversion}"
appfwversion = "${appfwversion}"
sendEvent(name: "firmware", value: appfwversion)
}
//update model
log.info "Updating ${device.displayName}: property model set to value: iSP8"
sendEvent(name:"model", value:"iSP8")
} else { //the plug is not connected
//update status message
sendEvent(name: "status", value: parent.getPlugNotConnectedMessage())
log.info "Updating ${device.displayName}: property status set to: ${parent.getPlugNotConnectedMessage()}"
}
}
// Process the polling error, changing the status message
def pollError(){
log.info "Error retrieving info from the cloud"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
/*
* This method handles the switch.on function by updating the corresponding property in the cloud
*/
def on() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn on if the device is off
if (device.currentState("switch")?.value.toLowerCase().startsWith("off")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOn"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOn\"}"
def success = parent.sendAction("_turnOn", actionJSON)
if(success){
log.info "Updating ${device.displayName}: sending switch.on command"
sendEvent(name: "switch", value: "on")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the switch.off function by updating the corresponding property in the cloud
*/
def off() {
//update the status of the plug before attempting to change it
refresh()
if (device.currentState("status")?.value == parent.getConnectedMessage()) {//only update if the plug is connected
//Turn off only if the device is on
if (device.currentState("switch")?.value.toLowerCase().startsWith("on")){
log.info "Updating ${device.displayName} in the cloud: sending action _turnOff"
def actionJSON = "{\"thng\":\"${device.deviceNetworkId}\", \"type\":\"_turnOff\"}"
def success = parent.sendAction("_turnOff", actionJSON)
if (success) {
log.info "Updating ${device.displayName}: sending switch.off command"
sendEvent(name: "switch", value: "off")
sendEvent(name: "status", value: parent.getConnectedMessage())
} else {
log.info "Cloud property update error, skipping event"
sendEvent(name: "status", value: parent.getConnectionErrorMessage())
}
}
}
}
/*
* This method handles the refresh capability
*/
def refresh() {
parent.pollChildren(device.deviceNetworkId)
}
def ping() {
refresh()
}

View File

@@ -18,6 +18,7 @@ metadata {
command "raiseHeatingSetpoint"
command "lowerCoolSetpoint"
command "raiseCoolSetpoint"
command "poll"
fingerprint deviceId: "0x08", inClusters: "0x43,0x40,0x44,0x31,0x80,0x85,0x60"
fingerprint mfr:"0098", prod:"6401", model:"0107", deviceJoinName: "2Gig CT100 Programmable Thermostat"
@@ -101,9 +102,8 @@ metadata {
def installed() {
// Configure device
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format())
cmds << new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
def cmds = [new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format()),
new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())]
sendHubCommand(cmds)
runIn(3, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
}
@@ -123,7 +123,7 @@ def initialize() {
// 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])
// Poll device for additional data that will be updated by refresh tile
poll()
pollDevice()
}
def parse(String description)
@@ -150,7 +150,6 @@ def parse(String description)
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiInstanceCmdEncap cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 3])
log.debug ("multiinstancev1.MultiInstanceCmdEncap: command from instance ${cmd.instance}: ${encapsulatedCommand}")
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand)
}
@@ -341,7 +340,7 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSuppo
if(cmd.auxiliaryemergencyHeat) { supportedModes << "emergency heat" }
state.supportedModes = supportedModes
sendEvent(name: "supportedThermostatModes", value: supportedModes, isStateChange: true, displayed: false)
sendEvent(name: "supportedThermostatModes", value: supportedModes, displayed: false)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) {
@@ -351,7 +350,7 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanMod
if(cmd.circulation) { supportedFanModes << "circulate" }
state.supportedFanModes = supportedFanModes
sendEvent(name: "supportedThermostatFanModes", value: supportedFanModes, isStateChange: true, displayed: false)
sendEvent(name: "supportedThermostatFanModes", value: supportedFanModes, displayed: false)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
@@ -377,7 +376,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "ManufacturerSpecificReport ${cmd}: value:${cmd}"
if (cmd.manufacturerName) {
updateDataValue("manufacturer", cmd.manufacturerName)
}
@@ -389,6 +387,11 @@ log.debug "ManufacturerSpecificReport ${cmd}: value:${cmd}"
}
}
def poll() {
// Call refresh which will cap the polling to once every 2 minutes
refresh()
}
def refresh() {
// Only allow refresh every 2 minutes to prevent flooding the Zwave network
def timeNow = now()
@@ -397,11 +400,11 @@ def refresh() {
// refresh will request battery, prevent multiple request by setting lastbatt now
state.lastbatt = timeNow
// use runIn with overwrite to prevent multiple DTH instances run before state.refreshTriggeredAt has been saved
runIn(2, "poll", [overwrite: true])
runIn(2, "pollDevice", [overwrite: true])
}
}
def poll() {
def pollDevice() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
@@ -614,7 +617,7 @@ def updateThermostatSetpoint(setpoint, value) {
* */
def ping() {
log.debug "ping() called"
// Just get Operating State as it is not reported when it chnages and there's no need to flood more commands
// Just get Operating State as it is not reported when it changes and there's no need to flood more commands
sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()))
}
@@ -624,7 +627,7 @@ def switchMode() {
if (supportedModes) {
def next = { supportedModes[supportedModes.indexOf(it) + 1] ?: supportedModes[0] }
def nextMode = next(currentMode)
runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
runIn(2, "setGetThermostatMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.warn "supportedModes not defined"
getSupportedModes()
@@ -635,7 +638,7 @@ def switchToMode(nextMode) {
def supportedModes = state.supportedModes
if (supportedModes) {
if (supportedModes.contains(nextMode)) {
runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
runIn(2, "setGetThermostatMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
}
@@ -657,7 +660,7 @@ def switchFanMode() {
if (supportedFanModes) {
def next = { supportedFanModes[supportedFanModes.indexOf(it) + 1] ?: supportedFanModes[0] }
def nextMode = next(currentMode)
runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
runIn(2, "setGetThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.warn "supportedFanModes not defined"
getSupportedFanModes()
@@ -668,7 +671,7 @@ def switchToFanMode(nextMode) {
def supportedFanModes = state.supportedFanModes
if (supportedFanModes) {
if (supportedFanModes.contains(nextMode)) {
runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
runIn(2, "setGetThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("FanMode $nextMode is not supported by ${device.displayName}")
}
@@ -679,8 +682,7 @@ def switchToFanMode(nextMode) {
}
def getSupportedFanModes() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
def cmds = [new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())]
sendHubCommand(cmds)
}
@@ -696,10 +698,9 @@ def setThermostatMode(String value) {
switchToMode(value)
}
def setThermostatMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
def setGetThermostatMode(data) {
def cmds = [new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[data.nextMode]).format()),
new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())]
sendHubCommand(cmds)
}
@@ -713,10 +714,9 @@ def setThermostatFanMode(String value) {
switchToFanMode(value)
}
def setThermostatFanMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
def setGetThermostatFanMode(data) {
def cmds = [new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[data.nextMode]).format()),
new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())]
sendHubCommand(cmds)
}

View File

@@ -28,6 +28,7 @@ metadata {
command "raiseHeatingSetpoint"
command "lowerCoolSetpoint"
command "raiseCoolSetpoint"
command "poll"
fingerprint deviceId: "0x08"
fingerprint inClusters: "0x43,0x40,0x44,0x31"
@@ -91,7 +92,7 @@ metadata {
standardTile("raiseCoolSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
state "heatingSetpoint", action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-right"
}
valueTile("thermostatOperatingState", "device.thermostatOperatingState", width: 2, height:1, decoration: "flat") {
standardTile("thermostatOperatingState", "device.thermostatOperatingState", width: 2, height:1, decoration: "flat") {
state "thermostatOperatingState", label:'${currentValue}', backgroundColor:"#ffffff"
}
standardTile("refresh", "device.thermostatMode", width:2, height:1, inactiveLabel: false, decoration: "flat") {
@@ -105,9 +106,8 @@ metadata {
def installed() {
// Configure device
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format())
cmds << new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
def cmds = [new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format()),
new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())]
sendHubCommand(cmds)
runIn(3, "initialize", [overwrite: true]) // Allow configure command to be sent and acknowledged before proceeding
}
@@ -129,7 +129,7 @@ def initialize() {
if (getDataValue("manufacturer") != "Honeywell") {
runEvery5Minutes("poll") // This is not necessary for Honeywell Z-wave, but could be for other Z-wave thermostats
}
poll()
pollDevice()
}
def parse(String description)
@@ -319,17 +319,22 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
}
// Command Implementations
def poll() {
// Call refresh which will cap the polling to once every 2 minutes
refresh()
}
def refresh() {
// Only allow refresh every 2 minutes to prevent flooding the Zwave network
def timeNow = now()
if (!state.refreshTriggeredAt || (2 * 60 * 1000 < (timeNow - state.refreshTriggeredAt))) {
state.refreshTriggeredAt = timeNow
// use runIn with overwrite to prevent multiple DTH instances run before state.refreshTriggeredAt has been saved
runIn(2, "poll", [overwrite: true])
runIn(2, "pollDevice", [overwrite: true])
}
}
def poll() {
def pollDevice() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
@@ -505,7 +510,7 @@ def switchMode() {
if (supportedModes) {
def next = { supportedModes[supportedModes.indexOf(it) + 1] ?: supportedModes[0] }
def nextMode = next(currentMode)
runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
runIn(2, "setGetThermostatMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.warn "supportedModes not defined"
getSupportedModes()
@@ -516,7 +521,7 @@ def switchToMode(nextMode) {
def supportedModes = state.supportedModes
if (supportedModes) {
if (supportedModes.contains(nextMode)) {
runIn(2, "setThermostatMode", [data: [nextMode: nextMode], overwrite: true])
runIn(2, "setGetThermostatMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
}
@@ -538,7 +543,7 @@ def switchFanMode() {
if (supportedFanModes) {
def next = { supportedFanModes[supportedFanModes.indexOf(it) + 1] ?: supportedFanModes[0] }
def nextMode = next(currentMode)
runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
runIn(2, "setGetThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.warn "supportedFanModes not defined"
getSupportedFanModes()
@@ -549,7 +554,7 @@ def switchToFanMode(nextMode) {
def supportedFanModes = state.supportedFanModes
if (supportedFanModes) {
if (supportedFanModes.contains(nextMode)) {
runIn(2, "setThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
runIn(2, "setGetThermostatFanMode", [data: [nextMode: nextMode], overwrite: true])
} else {
log.debug("FanMode $nextMode is not supported by ${device.displayName}")
}
@@ -560,8 +565,7 @@ def switchToFanMode(nextMode) {
}
def getSupportedFanModes() {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
def cmds = [new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())]
sendHubCommand(cmds)
}
@@ -577,10 +581,9 @@ def setThermostatMode(String value) {
switchToMode(value)
}
def setThermostatMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
def setGetThermostatMode(data) {
def cmds = [new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[data.nextMode]).format()),
new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())]
sendHubCommand(cmds)
}
@@ -594,10 +597,9 @@ def setThermostatFanMode(String value) {
switchToFanMode(value)
}
def setThermostatFanMode(data) {
def cmds = []
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[data.nextMode]).format())
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
def setGetThermostatFanMode(data) {
def cmds = [new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[data.nextMode]).format()),
new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())]
sendHubCommand(cmds)
}
@@ -663,7 +665,7 @@ def getTempInDeviceScale(temp, scale) {
if (temp && scale) {
def deviceScale = (state.scale == 1) ? "F" : "C"
return (deviceScale == scale) ? temp :
(deviceScale == "F" ? celsiusToFahrenheit(temp) : roundC(fahrenheitToCelsius(temp)))
(deviceScale == "F" ? celsiusToFahrenheit(temp).toDouble().round(0).toInteger() : roundC(fahrenheitToCelsius(temp)))
}
return 0
}

View File

@@ -1,707 +0,0 @@
/**
* iHome (Connect)
*
* Copyright 2016 EVRYTHNG LTD.
*
* 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.
*
* Last reviewed: 11.08.2017
* - Use of variable serverUrl in the URLs.
* Reviewed:20.07.2017
* - Merged content with the old version modified by SmartThings
* - Removed selection of plugs, all available plugs are imported by default
* - Added location selection
* - Added DeviceWatch-DeviceStatus event
* - Added unschedule call on initialising
* - Changed from schedule to runEvery5Minutes
* - Updated refreshThngs method to support add/delete plugs automatically when they are added/removed in iHome app
* Reviewed: 04.07.2017
* - Added support for iSP6X
* - Reimplemented the import with filtering using the new tag "Active" (removed serial and use thngId)
* Review: 20.04.2017
* - Added filter by deactive property
* - Removed duplicates by creation date
*
*/
include 'localization'
definition(
name: "iHome Control (Connect)",
namespace: "ihome_control",
author: "iHome",
description: "Control your iHome Control devices within the SmartThings app!",
category: "Convenience",
iconUrl: "https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControlicon.png",
iconX2Url: "https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControlicon.png",
iconX3Url: "https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControlicon.png",
singleInstance: true
)
{
appSetting "clientId" //Client Id of the SmartThings App in the iHome System
appSetting "clientSecret" //Client Secret of the SmartThings app in the iHome System
appSetting "iHomeServer" //URL of the iHome API
appSetting "serverUrl" //Base URL of the server hosting the redirection URI
appSetting "evrythngServer" //URL of the EVRYTHNG API (cloud control)
}
preferences {
page(name: "iHomeAuth", content:"authenticationPage", install: false)
page(name: "iHomeConnectDevices", title: "Import your iHome devices", content:"connectPage", install:false)
}
private getVendorName() { "iHome" }
/**********************************************************************************************
*
* AUTHENTICATION
*
* This block contains all the functions needed to carry out the OAuth Authentication
*
**********************************************************************************************/
/*
* Authentication endpoints (needed for OAuth)
*/
mappings {
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
path("/oauth/callback") {action: [GET: "callback"]}
}
/*
* Authentication Page
* Implements OAuth authentication with the Authorization Code Grant flow
*/
def authenticationPage()
{
log.debug "Checking authorisation..."
//Check first if the authorisation was already done before
if(state.iHomeAccessToken == null)
{
log.debug "iHome token not found, starting authorisation request"
//Check if the internal OAuth tokens have been created already
if (!state.accessToken){
log.debug "Creating access token for the callback"
createAccessToken()
}
//Create the OAuth URL of the authorisation server
def redirectUrl = "${appSettings.serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${getApiServerUrl()}"
log.debug "Redirecting to OAuth URL initializer: ${redirectUrl}"
//Display the connect your account section, it will redirect to the OAuth Authentication Server
return dynamicPage(name: "iHomeAuth", title:"iHome Control", install:false) {
section ("") {
paragraph "Welcome! In order to connect SmartThings to your ${vendorName} devices, you need to have already set up your devices using the ${vendorName} app."
href (url:redirectUrl,
style:"embedded",
required:true,
image:"https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControl_icon.png",
title:"Connect your iHome Account",
description:""
)
}
}
}
else
{
log.debug "iHome token found. Loading connect page"
loadThngs()
return connectPage()
}
}
/*
* Authentication OAuth URL
* Creates the OAuth compliant URL to the Authorisation Server
*/
def oauthInitUrl() {
log.debug "Creating OAuth URL..."
// Generate a random ID to use as a our state value. This value will be used to verify the response we get back from the 3rd party service.
state.oauthState = UUID.randomUUID().toString()
def oauthParams = [
response_type: "code",
client_id: appSettings.clientId,
state: state.oauthState,
redirect_uri: "${appSettings.serverUrl}/oauth/callback"
]
redirect(location: "${appSettings.iHomeServer}/oauth/authorize?${toQueryString(oauthParams)}")
}
/*
* Helper class to provide feedback to the user about the authentication process
*
*/
def connectionStatus(message, redirectUrl = null) {
def redirectHtml = ""
if (redirectUrl) {
redirectHtml = """
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
}
def html = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>SmartThings Connection</title>
<style type="text/css">
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
/* width: 440px;
padding: 40px;
/!*background: #eee;*!/*/
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 30px;
}
p {
font-size: 2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
padding: 0 40px;
margin-bottom: 0;
}
/*
p:last-child {
margin-top: 0px;
}
*/
span {
font-family: 'Swiss 721 W01 Light';
}
.image{
width: 20%;
/*height: 70px;*/
}
</style>
</head>
<body>
<div class="container">
<img class="image" src="https://www.ihomeaudio.com/media/uploads/product/logos/iH_iHomeControlicon.png" alt="iHome icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
<img class="image" src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
${message}
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
/*
* Handler of the OAuth redirection URI
*
*/
def callback() {
log.debug "OAuth callback received..."
//OAuth server returns a code in the URL as a parameter
state.iHomeAccessCode = params.code
log.debug "Received authorization code ${state.iHomeAccessCode}"
def oauthState = params.state
def successMessage = """
<p>Your iHome Account is now connected to SmartThings!</p>
<p>Click 'Done' in the top corner to complete the setup.</p>
"""
def errorMessage = """
<p>Your iHome Account couldn't be connected to SmartThings!</p>
<p>Click 'Done' in the top corner and try again.</p>
"""
// Validate the response from the 3rd party by making sure oauthState == state.oauthInitState as expected
if (oauthState == state.oauthState){
if (state.iHomeAccessCode == null) {
log.debug "OAuth error: Access code is not present"
connectionStatus(errorMessage)
}
else {
getAccessToken();
if (state.iHomeAccessToken){
getEVTApiKey();
if(state.evtApiKey){
connectionStatus(successMessage)
}
else{
log.debug "OAuth error: EVT API KEY could not be retrieved"
connectionStatus(errorMessage)
}
}
else {
log.debug "OAuth error: Access Token could not be retrieved"
connectionStatus(errorMessage)
}
}
}
else{
log.debug "OAuth error: initial state does not match"
connectionStatus(errorMessage)
}
}
/**
* Exchanges the authorization code for an access token
*/
def getAccessToken(){
log.debug "Getting iHome access token..."
def tokenParams = [
grant_type: "authorization_code",
code: state.iHomeAccessCode,
client_id: appSettings.clientId,
client_secret: appSettings.clientSecret,
redirect_uri: "${appSettings.serverUrl}/oauth/callback"
]
def tokenUrl = "${appSettings.iHomeServer}/oauth/token/?" + toQueryString(tokenParams)
log.debug "Invoking token URL: ${tokenUrl}"
try{
def jsonMap
httpPost(uri:tokenUrl) { resp ->
if(resp.status == 200)
{
jsonMap = resp.data
if (resp.data) {
state.iHomeRefreshToken = resp?.data?.refresh_token
state.iHomeAccessToken = resp?.data?.access_token
log.debug "Access token received ${state.iHomeAccessToken}"
}
}
}
}catch (groovyx.net.http.HttpResponseException e) {
log.warn "Error! Status Code was: ${e}"
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection timed out, not much we can do here."
}
}
def getEVTApiKey() {
log.debug "Getting api key from the cloud"
def apiKeyParams = [
uri: "${appSettings.iHomeServer}/v3/evrythng/",
headers: [
"Accept": "application/json",
"Authorization": "Bearer ${state.iHomeAccessToken}"]
]
try {
def jsonMap
httpGet(apiKeyParams)
{ resp ->
if(resp.status == 200)
{
jsonMap = resp.data
if (resp.data)
{
state.evtUserId = resp?.data?.evrythng_user_id
state.evtApiKey = resp?.data?.evrythng_api_key
log.debug "Api key received: ${state.evtUserId}/${state.evtApiKey}"
//Preload thngs after getting the api key
loadThngs()
}
}
}
}catch (groovyx.net.http.HttpResponseException e) {
log.warn "Error! Status Code was: ${e.statusCode}"
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection timed out, not much we can do here"
}
}
/*
* Maps the map to query parameters for the URL
*
*/
def toQueryString(Map m)
{
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
}
/**********************************************************************************************
* IMPORT
* This block contains all the functions needed to import the plugs from the cloud into SmartThings
**********************************************************************************************/
def loadThngs()
{
//Products in production account
state.product = [:]
state.product["UCfXBRHnse5Rpw7PySPYNq7b"] = "iHomeSmartPlug-iSP5"
state.product["Ugrtyq8pAFVEqGSAAptgtqkc"] = "iHomeSmartPlug-iSP6"
state.product["UXNtyNexVyRrWpAQeNHq9xad"] = "iHomeSmartPlug-iSP8"
state.product["UF4NsmAEM3PhY6wwRgehdg5n"] = "iHomeSmartPlug-iSP6X"
//Save the all the plugs in the state for later use
state.thngs = [:]
log.debug "Loading available devices..."
def thngs = getThngs()
thngs.each { thng ->
//Check that the plug is compatible with a Device Type
log.debug "Checking if ${thng.id} is a compatible Device Type"
if (state.product[thng.product])
{
thng.st_devicetype = state.product[thng.product]
state.thngs["${thng.id}"] = thng
log.info "Found compatible device ${state.thngs["${thng.id}"].name}"
}
}
}
/*
* Import thngs page
* Loads the thngs available from the user, checks that they have a DeviceType associated
* and presents a list to the user
*
*/
def connectPage()
{
return dynamicPage(name: "iHomeConnectDevices", uninstall: true, install:true) {
section(""){
input "selectedLocationId", "enum", required:false, title:"", multiple:false, options:["Default Location"], defaultValue: "Default Location", submitOnChange: true
paragraph "Devices will be added automatically from your ${vendorName} account. To add or delete devices please use the Official ${vendorName} App."
}
}
}
/*
* Gets the thngs from the cloud
* This is used as the discovery process
*/
def getThngs(){
log.debug "Getting available devices..."
def url = "${appSettings.evrythngServer}/thngs?filter=tags=Active"
try {
httpGet(uri: url, headers: ["Accept": "application/json", "Authorization": state.evtApiKey]) {response ->
if (response.status == 200) {
log.debug "GET on /thngs was succesful"
log.debug "Response to GET /thngs ${response.data}"
return response.data
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.warn "Error! Status Code was: ${e.statusCode}"
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection timed out, not much we can do here"
}
}
/*
* Gets a thng by id from EVRYTHNG
* Used for updates
*/
def getThng(thngId){
log.trace "Getting device information..."
def url = "${appSettings.evrythngServer}/thngs/" + thngId
try {
httpGet(uri: url, headers: ["Accept": "application/json", "Authorization": state.evtApiKey]) {response ->
if (response.status == 200) {
log.debug "GET on /thngs was succesful: ${response.data}"
def isAlive = response.data.properties["~connected"]
def d = getChildDevice(thngId)
d?.sendEvent(name: "DeviceWatch-DeviceStatus", value: isAlive? "online":"offline", displayed: false, isStateChange: true)
return response.data
}
else{
log.warn "Error! Status Code was: ${response.status}"
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.warn "Error! Status Code was: ${e.statusCode}"
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection timed out, not much we can do here"
}
}
/*
* Adds all the available devices to SmartThings
* Invoked by the lifecycle initialise
*/
def importThngs() {
def thngsToImport = []
state.thngs.each { thng ->
thngsToImport.add(thng.key)
}
log.debug "Adding all available plugs...${thngsToImport}"
//Remove unselected plugs
log.debug "Checking to delete ${state.imported}"
state.imported.each{ id ->
if(thngsToImport){
if (thngsToImport.contains(id)){
log.debug "${id} is already imported"
} else{
log.debug "Removing device not longer available: ${id}"
// Error can occur if device has already been deleted or is in-use by SmartApps. Should it be force-deleted? + deleteChildDevice(thng)
try {
deleteChildDevice(id)
} catch (Exception e) {
log.error "Error deleting device with DNI $thng: $e"
}
}
} else {
log.trace "Removing unselected device with id: ${id}"
try {
deleteChildDevice(id)
}
catch(Exception error){
log.error "Error deleting device with id -> ${id}: $error"
}
}
}
state.imported = [];
thngsToImport.each { id ->
log.debug "Importing plug with id: ${id} and serial: ${state.thngs["${id}"].identifiers.serial_num}"
def smartThing = getChildDevice(id)
if(!smartThing) {
def newSmartThing = state.thngs.find { it.key == id }
log.debug "Creating SmartThing: ${newSmartThing}"
smartThing = addChildDevice("ihome_devices",
newSmartThing.value.st_devicetype,
newSmartThing.value.id,
null,
[label:"${newSmartThing.value.name}"])
log.info "Created ${smartThing.displayName} with id ${smartThing.deviceNetworkId}"
}
else {
log.trace "${smartThing.displayName} with id ${id} already exists, skipping creation"
}
//save plug in state
state.imported.add(id);
//We need to get the current status of the plug
pollChildren(smartThing.deviceNetworkId)
}
}
/**********************************************************************************************
*
* LIFECYCLE
*
**********************************************************************************************/
def installed() {
log.debug "Application installed..."
initialize()
}
def updated() {
log.debug "Application updated..."
unsubscribe()
initialize()
}
def initialize() {
log.debug "Application initialising..."
importThngs()
unschedule()
//Refresh every five minutes for external changes in the thngs
runEvery5Minutes("refreshThngs")
}
def uninstalled() {
log.debug "Removing installed plugs..."
getChildDevices().each {
log.debug "Deleting ${it.deviceNetworkId}"
try {
deleteChildDevice(it.deviceNetworkId)
}
catch (e) {
log.warn "Error deleting device, ignoring ${e}"
}
}
}
/**********************************************************************************************
* Properties and Actions UPDATES
* This block contains the functionality to update property values and send actions in EVRYTHNG cloud
* This methods are generic based on the EVRYTHNG API specification and are invoked from
* the specific Device Type that handles the properties and action types
**********************************************************************************************/
/*
* Updates a property in EVRYTHNG
*/
def propertyUpdate(thngId, propertyUpdate){
def url = "${appSettings.evrythngServer}/thngs/${thngId}/properties"
def params = [
uri: url,
headers: [
"Authorization": state.evtApiKey
],
body: propertyUpdate
]
log.debug "Sending property update to the cloud: ${params}"
try {
httpPutJson(params) { resp ->
if (resp.status == 200) {
log.debug "Response from the cloud: ${resp}"
return true
}
else {
log.debug "Response status from the cloud not valid: ${resp}"
return false
}
}
}
catch (e) {
log.debug "Something went wrong with the property update: ${e}"
return false
}
}
/*
* Sends an action to EVRYTHNG
*/
def sendAction(actionType, actionPayload){
def url = "${appSettings.evrythngServer}/actions/${actionType}"
def params = [
uri: url,
headers: [
"Authorization": state.evtApiKey
],
body: actionPayload
]
log.debug "Sending action to the cloud: ${params}"
try {
httpPostJson(params) { resp ->
if (resp.status == 201) {
log.debug "Response from the cloud: ${resp}"
return true
}
else {
log.debug "Response status from the cloud not valid: ${resp}"
return false
}
}
}
catch (e) {
log.debug "Something went wrong with sending the action: ${e}"
return false
}
}
/**
* Handler of the refreshing of all the imported things
*/
def refreshThngs(){
log.debug "Refreshing thngs"
//loading thngs to get plugs recently added or removed
loadThngs()
//import the plugs into SmartThings
importThngs()
}
/*
* Utility function to poll for a Thng and update its properties
*/
def pollChildren(thngId){
//get plug device
def smartThing = getChildDevice(thngId)
if (smartThing){
//Get plug's latest state from the cloud
log.debug "Getting updates for ${thngId}"
def plug = getThng(thngId)
if (plug == null){
smartThing.pollError()
}
else
{
//Update name
smartThing.label = plug.name
smartThing.name = plug.name
//Update properties
smartThing.updateProperties(plug.properties)
}
}
}
/* Status messages for all types of plugs */
def getConnectedMessage(){
return "Connected"
}
def getConnectionErrorMessage(){
return "Connection error. Please try again."
}
def getPlugNotConnectedMessage(){
return "Your plug seems to be disconnected."
}