Compare commits

..

1 Commits

Author SHA1 Message Date
Will
63800a7097 MSA-2161: Updated version: see change log in files for details 2017-08-11 08:12:24 -07:00
6 changed files with 1507 additions and 427 deletions

View File

@@ -0,0 +1,197 @@
/**
* 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

@@ -0,0 +1,201 @@
/**
* 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

@@ -0,0 +1,201 @@
/**
* 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

@@ -0,0 +1,201 @@
/**
* 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

@@ -1,427 +0,0 @@
/**
* Aeon HEMv2
*
* Copyright 2014 Barry A. Burke
*
* 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.
*
*
* Aeon Home Energy Meter v2 (US)
*
* Author: Barry A. Burke
* Contributors: Brock Haymond: UI updates
*
* Genesys: Based off of Aeon Smart Meter Code sample provided by SmartThings (2013-05-30). Built on US model
* may also work on international versions (currently reports total values only)
*
* History:
*
* 2014-06-13: Massive OverHaul
* - Fixed Configuration (original had byte order of bitstrings backwards
* - Increased reporting frequency to 10s - note that values won't report unless they change
* (they will also report if they exceed limits defined in the settings - currently just using
* the defaults).
* - Added support for Volts & Amps monitoring (was only Power and Energy)
* - Added flexible tile display. Currently only used to show High and Low values since last
* reset (with time stamps).
* - All tiles are attributes, so that their values are preserved when you're not 'watching' the
* meter display
* - Values are formatted to Strings in zwaveEvent parser so that we don't lose decimal values
* in the tile label display conversion
* - Updated fingerprint to match Aeon Home Energy Monitor v2 deviceId & clusters
* - Added colors for Watts and Amps display
* - Changed time format to 24 hour
* 2014-06-17: Tile Tweaks
* - Reworked "decorations:" - current values are no longer "flat"
* - Added colors to current Watts (0-18000) & Amps (0-150)
* - Changed all colors to use same blue-green-orange-red as standard ST temperature guages
* 2014-06-18: Cost calculations
* - Added $/kWh preference
* 2014-09-07: Bug fix & Cleanup
* - Fixed "Unexpected Error" on Refresh tile - (added Refresh Capability)
* - Cleaned up low values - reset to ridiculously high value instead of null
* - Added poll() command/capability (just does a refresh)
*
* 2014-09-19 GUI Tweaks, HEM v1 alterations (from Brock Haymond)
* - Reworked all tiles for look, color, text formatting, & readability
*/
metadata {
// Automatically generated. Make future change here.
definition (
name: "Aeon HEMv2",
namespace: "smartthings",
category: "Green Living",
author: "Barry A. Burke"
)
{
capability "Energy Meter"
capability "Power Meter"
capability "Configuration"
capability "Sensor"
capability "Refresh"
capability "Polling"
capability "Battery"
attribute "energy", "string"
attribute "power", "string"
attribute "volts", "string"
attribute "amps", "string"
attribute "energyDisp", "string"
attribute "energyOne", "string"
attribute "energyTwo", "string"
attribute "powerDisp", "string"
attribute "powerOne", "string"
attribute "powerTwo", "string"
attribute "voltsDisp", "string"
attribute "voltsOne", "string"
attribute "voltsTwo", "string"
attribute "ampsDisp", "string"
attribute "ampsOne", "string"
attribute "ampsTwo", "string"
command "reset"
command "configure"
// v1 fingerprint deviceId: "0x2101", inClusters: " 0x70,0x31,0x72,0x86,0x32,0x80,0x85,0x60"
fingerprint deviceId: "0x3101", inClusters: "0x70,0x32,0x60,0x85,0x56,0x72,0x86"
}
// simulator metadata
simulator {
for (int i = 0; i <= 10000; i += 1000) {
status "power ${i} W": new physicalgraph.zwave.Zwave().meterV1.meterReport(
scaledMeterValue: i, precision: 3, meterType: 33, scale: 2, size: 4).incomingMessage()
}
for (int i = 0; i <= 100; i += 10) {
status "energy ${i} kWh": new physicalgraph.zwave.Zwave().meterV1.meterReport(
scaledMeterValue: i, precision: 3, meterType: 33, scale: 0, size: 4).incomingMessage()
}
// TODO: Add data feeds for Volts and Amps
}
// tile definitions
tiles {
// Watts row
valueTile("powerDisp", "device.powerDisp") {
state (
"default",
label:'${currentValue}',
foregroundColors:[
[value: 1, color: "#000000"],
[value: 10000, color: "#ffffff"]
],
foregroundColor: "#000000",
backgroundColors:[
[value: "0 Watts", color: "#153591"],
[value: "500 Watts", color: "#1e9cbb"],
[value: "1000 Watts", color: "#90d2a7"],
[value: "1500 Watts", color: "#44b621"],
[value: "2000 Watts", color: "#f1d801"],
[value: "2500 Watts", color: "#d04e00"],
[value: "3000 Watts", color: "#bc2323"]
/*
[value: "0 Watts", color: "#153591"],
[value: "3000 Watts", color: "#1e9cbb"],
[value: "6000 Watts", color: "#90d2a7"],
[value: "9000 Watts", color: "#44b621"],
[value: "12000 Watts", color: "#f1d801"],
[value: "15000 Watts", color: "#d04e00"],
[value: "18000 Watts", color: "#bc2323"]
*/
]
)
}
valueTile("powerOne", "device.powerOne", decoration: "flat") {
state("default", label:'${currentValue}')
}
valueTile("powerTwo", "device.powerTwo", decoration: "flat") {
state("default", label:'${currentValue}')
}
// Power row
valueTile("energyDisp", "device.energyDisp") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
valueTile("energyOne", "device.energyOne") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
valueTile("energyTwo", "device.energyTwo") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
// Volts row
valueTile("voltsDisp", "device.voltsDisp") {
state "default", label: '${currentValue}', backgroundColors:[
[value: "115.6 Volts", color: "#bc2323"],
[value: "117.8 Volts", color: "#D04E00"],
[value: "120.0 Volts", color: "#44B621"],
[value: "122.2 Volts", color: "#D04E00"],
[value: "124.4 Volts", color: "#bc2323"]
]
}
valueTile("voltsOne", "device.voltsOne", decoration: "flat") {
state "default", label:'${currentValue}'
}
valueTile("voltsTwo", "device.voltsTwo", decoration: "flat") {
state "default", label:'${currentValue}'
}
// Amps row
valueTile("ampsDisp", "device.ampsDisp") {
state "default", label: '${currentValue}' , foregroundColor: "#000000", color: "#000000", backgroundColors:[
[value: "0 Amps", color: "#153591"],
[value: "25 Amps", color: "#1e9cbb"],
[value: "50 Amps", color: "#90d2a7"],
[value: "75 Amps", color: "#44b621"],
[value: "100 Amps", color: "#f1d801"],
[value: "125 Amps", color: "#d04e00"],
[value: "150 Amps", color: "#bc2323"]
]
}
valueTile("ampsOne", "device.ampsOne", decoration: "flat") {
state "default", label:'${currentValue}'
}
valueTile("ampsTwo", "device.ampsTwo", decoration: "flat") {
state "default", label:'${currentValue}'
}
// Controls row
standardTile("reset", "device.energy", inactiveLabel: false) {
state "default", label:'reset', action:"reset", icon: "st.Health & Wellness.health7"
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat" ) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.power", inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configure", icon:"st.secondary.configure"
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
state "battery", label:'${currentValue}% battery', unit:""
}
// TODO: Add configurable delay button - Cycle through 10s, 30s, 1m, 5m, 60m, off?
main (["powerDisp","energyDisp","ampsDisp","voltsDisp"])
details([
"energyOne","energyDisp","energyTwo",
"powerOne","powerDisp","powerTwo",
//"ampsOne","ampsDisp","ampsTwo", // Comment out these two lines for HEMv!
//"voltsOne","voltsDisp","voltsTwo", // Comment out these two lines for HEMv1
"reset","refresh", "battery", "configure"
])
}
preferences {
input "kWhCost", "string", title: "\$/kWh (0.16)", defaultValue: "0.16" as String
}
}
def parse(String description) {
// log.debug "Parse received ${description}"
def result = null
def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
if (result) log.debug "Parse returned ${result}"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.meterv1.MeterReport cmd) {
// log.debug "zwaveEvent received ${cmd}"
def dispValue
def newValue
def timeString = new Date().format("h:mm a", location.timeZone)
if (cmd.meterType == 33) {
if (cmd.scale == 0) {
newValue = cmd.scaledMeterValue
if (newValue != state.energyValue) {
dispValue = String.format("%5.2f",newValue)+"\nkWh"
sendEvent(name: "energyDisp", value: dispValue as String, unit: "")
state.energyValue = newValue
BigDecimal costDecimal = newValue * ( kWhCost as BigDecimal)
def costDisplay = String.format("%3.2f",costDecimal)
sendEvent(name: "energyTwo", value: "Cost\n\$${costDisplay}", unit: "")
[name: "energy", value: newValue, unit: "kWh"]
}
} else if (cmd.scale == 1) {
newValue = cmd.scaledMeterValue
if (newValue != state.energyValue) {
dispValue = String.format("%5.2f",newValue)+"\nkVAh"
sendEvent(name: "energyDisp", value: dispValue as String, unit: "")
state.energyValue = newValue
[name: "energy", value: newValue, unit: "kVAh"]
}
}
else if (cmd.scale==2) {
newValue = Math.round( cmd.scaledMeterValue ) // really not worth the hassle to show decimals for Watts
if (newValue != state.powerValue) {
dispValue = newValue+"\nWatts"
sendEvent(name: "powerDisp", value: dispValue as String, unit: "")
if (newValue < state.powerLow) {
dispValue = "Low\n"+newValue+" W\n"+timeString
sendEvent(name: "powerOne", value: dispValue as String, unit: "")
state.powerLow = newValue
}
if (newValue > state.powerHigh) {
dispValue = "High\n"+newValue+" W\n"+timeString
sendEvent(name: "powerTwo", value: dispValue as String, unit: "")
state.powerHigh = newValue
}
state.powerValue = newValue
[name: "power", value: newValue, unit: "W"]
}
}
}
else if (cmd.meterType == 161) {
if (cmd.scale == 0) {
newValue = cmd.scaledMeterValue
if (newValue != state.voltsValue) {
dispValue = String.format("%5.2f", newValue)+"\nVolts"
sendEvent(name: "voltsDisp", value: dispValue as String, unit: "")
if (newValue < state.voltsLow) {
dispValue = "Low\n"+String.format("%5.2f", newValue)+" V\n"+timeString
sendEvent(name: "voltsOne", value: dispValue as String, unit: "")
state.voltsLow = newValue
}
if (newValue > state.voltsHigh) {
dispValue = "High\n"+String.format("%5.2f", newValue)+" V\n"+timeString
sendEvent(name: "voltsTwo", value: dispValue as String, unit: "")
state.voltsHigh = newValue
}
state.voltsValue = newValue
[name: "volts", value: newValue, unit: "V"]
}
}
else if (cmd.scale==1) {
newValue = cmd.scaledMeterValue
if (newValue != state.ampsValue) {
dispValue = String.format("%5.2f", newValue)+"\nAmps"
sendEvent(name: "ampsDisp", value: dispValue as String, unit: "")
if (newValue < state.ampsLow) {
dispValue = "Low\n"+String.format("%5.2f", newValue)+" A\n"+timeString
sendEvent(name: "ampsOne", value: dispValue as String, unit: "")
state.ampsLow = newValue
}
if (newValue > state.ampsHigh) {
dispValue = "High\n"+String.format("%5.2f", newValue)+" A\n"+timeString
sendEvent(name: "ampsTwo", value: dispValue as String, unit: "")
state.ampsHigh = newValue
}
state.ampsValue = newValue
[name: "amps", value: newValue, unit: "A"]
}
}
}
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [:]
map.name = "battery"
map.unit = "%"
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
log.debug map
return map
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
log.debug "Unhandled event ${cmd}"
[:]
}
def refresh() {
delayBetween([
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
}
def poll() {
refresh()
}
def reset() {
log.debug "${device.name} reset"
state.powerHigh = 0
state.powerLow = 99999
state.ampsHigh = 0
state.ampsLow = 999
state.voltsHigh = 0
state.voltsLow = 999
def dateString = new Date().format("m/d/YY", location.timeZone)
def timeString = new Date().format("h:mm a", location.timeZone)
sendEvent(name: "energyOne", value: "Since\n"+dateString+"\n"+timeString, unit: "")
sendEvent(name: "powerOne", value: "", unit: "")
sendEvent(name: "voltsOne", value: "", unit: "")
sendEvent(name: "ampsOne", value: "", unit: "")
sendEvent(name: "ampsDisp", value: "", unit: "")
sendEvent(name: "voltsDisp", value: "", unit: "")
sendEvent(name: "powerDisp", value: "", unit: "")
sendEvent(name: "energyDisp", value: "", unit: "")
sendEvent(name: "energyTwo", value: "Cost\n--", unit: "")
sendEvent(name: "powerTwo", value: "", unit: "")
sendEvent(name: "voltsTwo", value: "", unit: "")
sendEvent(name: "ampsTwo", value: "", unit: "")
// No V1 available
def cmd = delayBetween( [
zwave.meterV2.meterReset().format(),
zwave.meterV2.meterGet(scale: 0).format()
])
cmd
}
def configure() {
// TODO: Turn on reporting for each leg of power - display as alternate view (Currently those values are
// returned as zwaveEvents...they probably aren't implemented in the core Meter device yet.
def cmd = delayBetween([
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: 1).format(), // Enable selective reporting
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: 50).format(), // Don't send unless watts have increased by 50
zwave.configurationV1.configurationSet(parameterNumber: 8, size: 2, scaledConfigurationValue: 10).format(), // Or by 10% (these 3 are the default values
zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 10).format(), // Average Watts & Amps
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 30).format(), // Every 30 Seconds
zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 4).format(), // Average Voltage
zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 150).format(), // every 2.5 minute
zwave.configurationV1.configurationSet(parameterNumber: 103, size: 4, scaledConfigurationValue: 1).format(), // Total kWh (cumulative)
zwave.configurationV1.configurationSet(parameterNumber: 113, size: 4, scaledConfigurationValue: 300).format() // every 5 minutes
])
log.debug cmd
cmd
}

View File

@@ -0,0 +1,707 @@
/**
* 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."
}