Hi Ryan. Thanks for taking the time to reply. I’m using the Smartthings Classic app. The automation is the section where you set up routine to execute e.g. Good Morning, Good Night. You say what you want to happen and what time.
In my case I have my curtains set up with a custom device drive with both Window Shade and Garage Door capabilities. In the automation I select “open or close garage doors” as the action, then pick my device “curtains” and Action Close. I’ve also tried it as Window Shades, but nothing seems to happen.
This is my device handler. It invokes a php script on the raspberry Pi passing it command CurtainsOpen or CurtainsClose:
/**
* Curtains on pi
* Adapted from: https://github.com/JZ-SmartThings/SmartThings/blob/master/Devices/Generic%20HTTP%20Device/GenericHTTPDevice.groovy
*
* 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.
*/
import groovy.json.JsonSlurper
metadata {
definition (name: "Curtains", author: "Dominic McCormick", namespace:"regfixit") {
capability "Window Shade"
capability "Garage Door Control"
attribute "lastmoved", "string"
capability "Refresh"
command "open"
command "close"
}
preferences {
input("DeviceIP", "string", title:"Device IP Address", description: "Please enter your device's IP Address", required: true, displayDuringSetup: true)
input("DevicePort", "string", title:"Device Port", description: "Empty assumes port 80.", required: false, displayDuringSetup: true)
input("DevicePath", "string", title:"URL Path", description: "Rest of the URL, include forward slash.", displayDuringSetup: true)
input(name: "DevicePostGet", type: "enum", title: "POST or GET", options: ["POST","GET"], defaultValue: "POST", required: false, displayDuringSetup: true)
input("UseOffVoiceCommandForCustom", "bool", title:"Use the OFF voice command (e.g. by Alexa) to control the Custom command? Assumed ON if MainTrigger is Momentary setting below is ON.", description: "", defaultValue: false, required: false, displayDuringSetup: true)
input("UseJSON", "bool", title:"Use JSON instead of HTML?", description: "", defaultValue: false, required: false, displayDuringSetup: true)
section() {
input("HTTPAuth", "bool", title:"Requires User Auth?", description: "Choose if the HTTP requires basic authentication", defaultValue: false, required: true, displayDuringSetup: true)
input("HTTPUser", "string", title:"HTTP User", description: "Enter your basic username", required: false, displayDuringSetup: true)
input("HTTPPassword", "string", title:"HTTP Password", description: "Enter your basic password", required: false, displayDuringSetup: true)
}
}
simulator {
}
tiles(scale: 2) {
valueTile("lastmoved", "device.lastmoved", width: 4, height: 3, decoration: "flat") {
state "default", label:'${currentValue}', backgroundColor:"#ffffff"
}
standardTile("Curtains", "device.GarageDoorControl", width: 2, height: 3, decoration: "flat") {
state "open", label:'OPEN' , action: "close", icon: "st.Home.home9", backgroundColor:"#33cc33", nextState: "closing"
state "closed", label: 'CLOSED', action: "open", icon: "st.Home.home9", backgroundColor: "#0066ff", nextState: "opening"
state "opening", label: 'OPENING', icon: "st.Home.home9", backgroundColor: "#ff9933", nextState: "opening"
state "closing", label: 'CLOSING', icon: "st.Home.home9", backgroundColor: "#cc0000", nextState: "closing"
}
main "Curtains"
details(["lastmoved", "Curtains"])
}
}
def open() {
log.debug "Executing 'open'"
runCmd('CurtainsOpen=')
}
def close() {
log.debug "Executing 'close'"
runCmd('CurtainsClose=')
}
def refresh() {
def FullCommand = 'Refresh='
if (DeviceMainPin) {FullCommand=FullCommand+"&MainPin="+DeviceMainPin} //else {FullCommand=FullCommand+"&MainPin=4"}
if (DeviceCustomPin) {FullCommand=FullCommand+"&CustomPin="+DeviceCustomPin} //else {FullCommand=FullCommand+"&CustomPin=21"}
if (UseJSON==true) { FullCommand=FullCommand+"&UseJSON=" }
runCmd(FullCommand)
}
def runCmd(String varCommand) {
def host = DeviceIP
def hosthex = convertIPtoHex(host).toUpperCase()
def LocalDevicePort = ''
if (DevicePort==null) { LocalDevicePort = "80" } else { LocalDevicePort = DevicePort }
def porthex = convertPortToHex(LocalDevicePort).toUpperCase()
device.deviceNetworkId = "$hosthex:$porthex"
def userpassascii = "${HTTPUser}:${HTTPPassword}"
def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
log.debug "The device id configured is: $device.deviceNetworkId"
def headers = [:]
headers.put("HOST", "$host:$LocalDevicePort")
headers.put("Content-Type", "application/x-www-form-urlencoded")
if (HTTPAuth) {
headers.put("Authorization", userpass)
}
log.debug "The Header is $headers"
def path = ''
def body = ''
log.debug "Uses which method: $DevicePostGet"
def method = "POST"
try {
if (DevicePostGet.toUpperCase() == "GET") {
method = "GET"
path = varCommand
if (path.substring(0,1) != "/") { path = "/" + path }
log.debug "GET path is: $path"
} else {
path = DevicePath
body = varCommand
log.debug "POST body is: $body"
}
log.debug "The method is $method"
}
catch (Exception e) {
settings.DevicePostGet = "POST"
log.debug e
log.debug "You must not have set the preference for the DevicePOSTGET option"
}
try {
def hubAction = new physicalgraph.device.HubAction(
method: method,
path: path,
body: body,
headers: headers
)
hubAction.options = [outputMsgToS3:false]
log.debug hubAction
hubAction
}
catch (Exception e) {
log.debug "Hit Exception $e on $hubAction"
}
}
def parse(String description) {
// log.debug "Parsing '${description}'"
def whichTile = ''
def map = [:]
def retResult = []
def descMap = parseDescriptionAsMap(description)
def jsonlist = [:]
def bodyReturned = ' '
def headersReturned = ' '
if (descMap["body"] && descMap["headers"]) {
bodyReturned = new String(descMap["body"].decodeBase64())
headersReturned = new String(descMap["headers"].decodeBase64())
}
// log.debug "BODY---" + bodyReturned
// log.debug "HEADERS---" + headersReturned
if (descMap["body"]) {
if (headersReturned.contains("application/json")) {
def body = new String(descMap["body"].decodeBase64())
def slurper = new JsonSlurper()
jsonlist = slurper.parseText(body)
//log.debug "JSONLIST---" + jsonlist."CPU"
jsonlist.put ("Date", new Date().format("yyyy-MM-dd h:mm:ss a", location.timeZone))
} else if (headersReturned.contains("text/html")) {
jsonlist.put ("Date", new Date().format("yyyy-MM-dd h:mm:ss a", location.timeZone))
def data=bodyReturned.eachLine { line ->
if (line.contains('CurtainsOpen=Success')) { jsonlist.put ("CurtainsOpen", "Success") }
if (line.contains('CurtainsClose=Success')) { jsonlist.put ("CurtainsClose", "Success") }
}
}
}
if (descMap["body"] && (headersReturned.contains("application/json") || headersReturned.contains("text/html"))) {
//putImageInS3(descMap)
if (jsonlist."Refresh"=="Authentication Required!") {
sendEvent(name: "refreshTriggered", value: "Use Authentication Credentials", unit: "")
whichTile = 'refresh'
}
if (jsonlist."Refresh"=="Success") {
sendEvent(name: "refreshTriggered", value: jsonlist."Date", unit: "")
whichTile = 'refresh'
}
if (jsonlist."CurtainsOpen"=="Success") {
sendEvent(name: "GarageDoorControl", value: "open", isStateChange: true)
sendEvent(name: "lastmoved", value: "Opened at: " + jsonlist."Date", unit: "")
whichTile = 'curtainsopen'
}
if (jsonlist."CurtainsClose"=="Success") {
sendEvent(name: "GarageDoorControl", value: "closed", isStateChange: true)
sendEvent(name: "lastmoved", value: "Closed at: " + jsonlist."Date", unit: "")
whichTile = 'curtainsclose'
}
if (jsonlist."CustomTrigger"=="Authentication Required!") {
sendEvent(name: "customTriggered", value: "Use Authentication Credentials", unit: "")
}
if (jsonlist."CustomTrigger"=="Success") {
sendEvent(name: "customswitch", value: "on", isStateChange: true)
sendEvent(name: "customTriggered", value: "MOMENTARY @ " + jsonlist."Date", unit: "")
whichTile = 'customoff'
}
if (jsonlist."CustomTriggerOn"=="Success" && jsonlist."CustomPinStatus"==1) {
sendEvent(name: "customTriggered", value: "ON @ " + jsonlist."Date", unit: "")
whichTile = 'customon'
}
if (jsonlist."CustomTriggerOn"=="Authentication Required!") {
sendEvent(name: "customTriggered", value: "Use Authentication Credentials", unit: "")
}
if (jsonlist."CustomTriggerOff"=="Success" && jsonlist."CustomPinStatus"==0) {
sendEvent(name: "customTriggered", value: "OFF @ " + jsonlist."Date", unit: "")
whichTile = 'customoff'
}
if (jsonlist."CustomTriggerOff"=="Authentication Required!") {
sendEvent(name: "customTriggered", value: "Use Authentication Credentials", unit: "")
}
if (jsonlist."CustomPinStatus"==1) {
sendEvent(name: "customswitch", value: "on", isStateChange: true)
sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
whichTile = 'customon'
}
else if (jsonlist."CustomPinStatus"==0) {
sendEvent(name: "customswitch", value: "off", isStateChange: true)
sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
whichTile = 'customoff'
}
if (jsonlist."MainTrigger"=="Authentication Required!") {
sendEvent(name: "mainTriggered", value: "Use Authentication Credentials", unit: "")
}
if (jsonlist."MainTrigger"=="Success") {
sendEvent(name: "switch", value: "on", isStateChange: true)
sendEvent(name: "mainTriggered", value: "MOMENTARY @ " + jsonlist."Date", unit: "")
whichTile = 'mainoff'
}
if (jsonlist."MainTriggerOn"=="Success" && jsonlist."MainPinStatus"==1) {
sendEvent(name: "mainTriggered", value: "ON @ " + jsonlist."Date", unit: "")
whichTile = 'mainon'
}
if (jsonlist."MainTriggerOn"=="Authentication Required!") {
sendEvent(name: "mainTriggered", value: "Use Authentication Credentials", unit: "")
}
if (jsonlist."MainTriggerOff"=="Success" && jsonlist."MainPinStatus"==0) {
sendEvent(name: "mainTriggered", value: "OFF @ " + jsonlist."Date", unit: "")
whichTile = 'mainoff'
}
if (jsonlist."MainTriggerOff"=="Authentication Required!") {
sendEvent(name: "mainTriggered", value: "Use Authentication Credentials", unit: "")
whichTile = 'mainoff'
}
if (jsonlist."MainPinStatus"==1) {
sendEvent(name: "switch", value: "on", isStateChange: true)
sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
whichTile = 'mainon'
}
else if (jsonlist."MainPinStatus"==0) {
sendEvent(name: "switch", value: "off", isStateChange: true)
sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
whichTile = 'mainoff'
}
if (jsonlist."CPU") {
sendEvent(name: "cpuUsage", value: jsonlist."CPU".replace("=","\n").replace("%",""), unit: "")
}
if (jsonlist."Space Used") {
sendEvent(name: "spaceUsed", value: jsonlist."Space Used".replace("=","\n").replace("%",""), unit: "")
}
if (jsonlist."UpTime") {
sendEvent(name: "upTime", value: jsonlist."UpTime".replace("=","\n"), unit: "")
}
if (jsonlist."CPU Temp") {
sendEvent(name: "cpuTemp", value: jsonlist."CPU Temp".replace("=","\n").replace("\'","°").replace("C ","C="), unit: "")
}
if (jsonlist."Free Mem") {
sendEvent(name: "freeMem", value: jsonlist."Free Mem".replace("=","\n"), unit: "")
}
if (jsonlist."Temperature") {
sendEvent(name: "temperature", value: jsonlist."Temperature".replace("=","\n").replace("\'","°").replace("C ","C="), unit: "")
//String s = jsonlist."Temperature"
//for(int i = 0; i < s.length(); i++) {
// int c = s.charAt(i);
// log.trace "'${c}'\n"
//}
}
if (jsonlist."Humidity") {
sendEvent(name: "humidity", value: jsonlist."Humidity".replace("=","\n"), unit: "")
}
if (jsonlist."RebootNow") {
whichTile = 'RebootNow'
}
}
log.debug jsonlist
//RESET THE DEVICE ID TO GENERIC/RANDOM NUMBER. THIS ALLOWS MULTIPLE DEVICES TO USE THE SAME ID/IP
device.deviceNetworkId = "ID_WILL_BE_CHANGED_AT_RUNTIME_" + (Math.abs(new Random().nextInt()) % 99999 + 1)
//RETURN BUTTONS TO CORRECT STATE
log.debug 'whichTile: ' + whichTile
switch (whichTile) {
case 'refresh':
sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
def result = createEvent(name: "refreshswitch", value: "default", isStateChange: true)
//log.debug "refreshswitch returned ${result?.descriptionText}"
return result
case 'customoff':
sendEvent(name: "customswitch", value: "off", isStateChange: true)
def result = createEvent(name: "customswitch", value: "off", isStateChange: true)
return result
case 'customon':
sendEvent(name: "customswitch", value: "on", isStateChange: true)
def result = createEvent(name: "customswitch", value: "on", isStateChange: true)
return result
case 'mainoff':
def result = createEvent(name: "switch", value: "off", isStateChange: true)
return result
case 'curtainsopen':
def result = createEvent(name: "GarageDoorControl", value: "open", isStateChange: true)
return result
case 'curtainsclose':
def result = createEvent(name: "GarageDoorControl", value: "closed", isStateChange: true)
return result
case 'mainon':
def result = createEvent(name: "switch", value: "on", isStateChange: true)
return result
case 'RebootNow':
sendEvent(name: "rebootnow", value: "default", isStateChange: true)
def result = createEvent(name: "rebootnow", value: "default", isStateChange: true)
return result
default:
sendEvent(name: "refreshswitch", value: "default", isStateChange: true)
def result = createEvent(name: "refreshswitch", value: "default", isStateChange: true)
//log.debug "refreshswitch returned ${result?.descriptionText}"
return result
}
}
def parseDescriptionAsMap(description) {
description.split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
private String convertIPtoHex(ipAddress) {
String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join()
//log.debug "IP address entered is $ipAddress and the converted hex code is $hex"
return hex
}
private String convertPortToHex(port) {
String hexport = port.toString().format( '%04x', port.toInteger() )
//log.debug hexport
return hexport
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
//log.debug("Convert hex to ip: $hex")
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress() {
def parts = device.deviceNetworkId.split(":")
//log.debug device.deviceNetworkId
def ip = convertHexToIP(parts[0])
def port = convertHexToInt(parts[1])
return ip + ":" + port
}