Compare commits

..

3 Commits

Author SHA1 Message Date
Matt Pennig
16d207f805 naming revisions 2016-03-08 11:25:03 -06:00
Matt Pennig
8a9d420d0c cleaning up after ourselves 2016-01-22 12:45:56 -06:00
Matt Pennig
353493bd34 Adding simulated Media Player device type handler 2016-01-20 14:10:40 -06:00
17 changed files with 213 additions and 2098 deletions

View File

@@ -1,128 +0,0 @@
/**
* Simple Sync
*
* Copyright 2015 Roomie Remote, Inc.
*
* Date: 2015-09-22
*/
metadata
{
definition (name: "Simple Sync", namespace: "roomieremote-agent", author: "Roomie Remote, Inc.")
{
capability "Media Controller"
}
// simulator metadata
simulator
{
}
// UI tile definitions
tiles
{
standardTile("mainTile", "device.status", width: 1, height: 1, icon: "st.Entertainment.entertainment11")
{
state "default", label: "Simple Sync", icon: "st.Home.home2", backgroundColor: "#55A7FF"
}
def detailTiles = ["mainTile"]
main "mainTile"
details(detailTiles)
}
}
def parse(String description)
{
def results = []
try
{
def msg = parseLanMessage(description)
if (msg.headers && msg.body)
{
switch (msg.headers["X-Roomie-Echo"])
{
case "getAllActivities":
handleGetAllActivitiesResponse(msg)
break
}
}
}
catch (Throwable t)
{
sendEvent(name: "parseError", value: "$t", description: description)
throw t
}
results
}
def handleGetAllActivitiesResponse(response)
{
def body = parseJson(response.body)
if (body.status == "success")
{
def json = new groovy.json.JsonBuilder()
def root = json activities: body.data
def data = json.toString()
sendEvent(name: "activities", value: data)
}
}
def getAllActivities(evt)
{
def host = getHostAddress(device.deviceNetworkId)
def action = new physicalgraph.device.HubAction(method: "GET",
path: "/api/v1/activities",
headers: [HOST: host, "X-Roomie-Echo": "getAllActivities"])
action
}
def startActivity(evt)
{
def uuid = evt
def host = getHostAddress(device.deviceNetworkId)
def activity = new groovy.json.JsonSlurper().parseText(device.currentValue('activities') ?: "{ 'activities' : [] }").activities.find { it.uuid == uuid }
def toggle = activity["toggle"]
def jsonMap = ["activity_uuid": uuid]
if (toggle != null)
{
jsonMap << ["toggle_state": toggle ? "on" : "off"]
}
def json = new groovy.json.JsonBuilder(jsonMap)
def jsonBody = json.toString()
def headers = [HOST: host, "Content-Type": "application/json"]
def action = new physicalgraph.device.HubAction(method: "POST",
path: "/api/v1/runactivity",
body: jsonBody,
headers: headers)
action
}
def getHostAddress(d)
{
def parts = d.split(":")
def ip = convertHexToIP(parts[0])
def port = convertHexToInt(parts[1])
return ip + ":" + port
}
def String convertHexToIP(hex)
{
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
def Integer convertHexToInt(hex)
{
Integer.parseInt(hex,16)
}

View File

@@ -1,153 +0,0 @@
/**
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
capability "Tone"
capability "Actuator"
capability "Presence Sensor"
capability "Sensor"
capability "Battery"
capability "Configuration"
fingerprint inClusters: "0000,0001,0003,000F,0020", outClusters: "0003,0019",
manufacturer: "SmartThings", model: "tagv4", deviceJoinName: "Arrival Sensor"
}
preferences {
section {
image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.device-gse.smartthings.com/Arrival/Arrival1.png",
"http://cdn.device-gse.smartthings.com/Arrival/Arrival2.png"
])
}
section {
input "checkInterval", "enum", title: "Presence timeout (minutes)",
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
}
}
tiles {
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
}
standardTile("beep", "device.beep", decoration: "flat") {
state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""
}
main "presence"
details(["presence", "beep", "battery"])
}
}
def updated() {
startTimer()
}
def configure() {
def cmds = zigbee.configureReporting(0x0001, 0x0020, 0x20, 20, 20, 0x01)
log.debug "configure -- cmds: ${cmds}"
return cmds
}
def beep() {
log.debug "Sending Identify command to beep the sensor for 5 seconds"
return zigbee.command(0x0003, 0x00, "0500")
}
def parse(String description) {
state.lastCheckin = now()
handlePresenceEvent(true)
if (description?.startsWith('read attr -')) {
handleReportAttributeMessage(description)
}
}
private handleReportAttributeMessage(String description) {
def descMap = zigbee.parseDescriptionAsMap(description)
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
}
}
private handleBatteryEvent(rawValue) {
def linkText = getLinkText(device)
def eventMap = [
name: 'battery',
value: '--'
]
def volts = rawValue / 10
if (volts > 0){
def minVolts = 2.0
def maxVolts = 2.8
if (volts < minVolts)
volts = minVolts
else if (volts > maxVolts)
volts = maxVolts
def pct = (volts - minVolts) / (maxVolts - minVolts)
eventMap.value = Math.round(pct * 100)
eventMap.descriptionText = "${linkText} battery was ${eventMap.value}%"
}
log.debug "Creating battery event: ${eventMap}"
sendEvent(eventMap)
}
private handlePresenceEvent(present) {
def wasPresent = device.currentState("presence")?.value == "present"
if (!wasPresent && present) {
log.debug "Sensor is present"
startTimer()
} else if (!present) {
log.debug "Sensor is not present"
stopTimer()
}
def linkText = getLinkText(device)
def eventMap = [
name: "presence",
value: present ? "present" : "not present",
linkText: linkText,
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
]
log.debug "Creating presence event: ${eventMap}"
sendEvent(eventMap)
}
private startTimer() {
log.debug "Scheduling periodic timer"
schedule("0 * * * * ?", checkPresenceCallback)
}
private stopTimer() {
log.debug "Stopping periodic timer"
unschedule()
}
def checkPresenceCallback() {
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
log.debug "Sensor checked in ${timeSinceLastCheckin} seconds ago"
if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false)
}
}

View File

@@ -67,7 +67,7 @@ metadata {
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up"
}
valueTile("thermostatSetpoint", "device.thermostatSetpoint", width: 1, height: 1, decoration: "flat") {
state "thermostatSetpoint", label:'${currentValue}'
state "thermostatSetpoint", label:'${currentValue}°'
}
valueTile("currentStatus", "device.thermostatStatus", height: 1, width: 2, decoration: "flat") {
state "thermostatStatus", label:'${currentValue}', backgroundColor:"#ffffff"

View File

@@ -204,10 +204,8 @@ private List parseReportAttributeMessage(String description) {
}
result << getAccelerationResult(descMap.value)
}
else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) {
// The size is checked to ensure the attribute report contains X, Y and Z values
// If all three axis are not included then the attribute report is ignored
result << parseAxis(descMap.value)
else if (descMap.cluster == "FC02" && descMap.attrId == "0012") {
result << parseAxis(descMap.value)
}
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
result << getBatteryResult(Integer.parseInt(descMap.value, 16))
@@ -373,50 +371,21 @@ def getTemperature(value) {
def refresh() {
log.debug "Refreshing Values "
def refreshCmds = [
def refreshCmds = []
/* sensitivity - default value (8) */
if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings "
refreshCmds = refreshCmds + [
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
Separating these out in a separate if-else because I do not want to touch Centralite part
as of now.
*/
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
} else {
refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
]
}
//Common refresh commands
refreshCmds = refreshCmds + [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
"zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global read 0xFC02 0x0010",
"send 0x${device.deviceNetworkId} 1 1","delay 400"
]
return refreshCmds + enrollResponse()
}
@@ -478,34 +447,35 @@ def enrollResponse() {
]
}
private Map parseAxis(String description) {
def hexToSignedInt = { hexVal ->
def unsignedVal = hexToInt(hexVal)
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
}
def z = hexToSignedInt(description[0..3])
def y = hexToSignedInt(description[10..13])
def x = hexToSignedInt(description[20..23])
def xyzResults = [x: x, y: y, z: z]
if (device.getDataValue("manufacturer") == "SmartThings") {
// This mapping matches the current behavior of the Device Handler for the Centralite sensors
xyzResults.x = z
xyzResults.y = y
xyzResults.z = -x
} else {
// The axises reported by the Device Handler differ from the axises reported by the sensor
// This may change in the future
xyzResults.x = z
xyzResults.y = x
xyzResults.z = y
}
log.debug "parseAxis -- ${xyzResults}"
if (garageSensor == "Yes")
garageEvent(xyzResults.z)
log.debug "parseAxis"
def xyzResults = [x: 0, y: 0, z: 0]
def parts = description.split("2900")
parts[0] = "12" + parts[0]
parts.each { part ->
part = part.trim()
if (part.startsWith("12")) {
def unsignedX = hexToInt(part.split("12")[1].trim())
def signedX = unsignedX > 32767 ? unsignedX - 65536 : unsignedX
xyzResults.x = signedX
log.debug "X Part: ${signedX}"
}
else if (part.startsWith("13")) {
def unsignedY = hexToInt(part.split("13")[1].trim())
def signedY = unsignedY > 32767 ? unsignedY - 65536 : unsignedY
xyzResults.y = signedY
log.debug "Y Part: ${signedY}"
}
else if (part.startsWith("14")) {
def unsignedZ = hexToInt(part.split("14")[1].trim())
def signedZ = unsignedZ > 32767 ? unsignedZ - 65536 : unsignedZ
xyzResults.z = signedZ
log.debug "Z Part: ${signedZ}"
if (garageSensor == "Yes")
garageEvent(signedZ)
}
}
getXyzResult(xyzResults, description)
}
@@ -583,4 +553,3 @@ private byte[] reverseArray(byte[] array) {
return array
}

View File

@@ -0,0 +1,119 @@
/**
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Simulated Media Player", namespace: "smartthings/testing", author: "SmartThings") {
capability "Actuator"
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Music Player"
}
tiles(scale: 2) {
multiAttributeTile(name: "mediaMulti", type:"mediaPlayer", width:6, height:4) {
tileAttribute("device.status", key: "PRIMARY_CONTROL") {
attributeState("paused", label:"Paused",)
attributeState("playing", label:"Playing")
attributeState("stopped", label:"Stopped")
}
tileAttribute("device.status", key: "MEDIA_STATUS") {
attributeState("paused", label:"Paused", action:"music Player.play", nextState: "playing")
attributeState("playing", label:"Playing", action:"music Player.pause", nextState: "paused")
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
}
tileAttribute("device.status", key: "PREVIOUS_TRACK") {
attributeState("default", action:"music Player.previousTrack")
}
tileAttribute("device.status", key: "NEXT_TRACK") {
attributeState("default", action:"music Player.nextTrack")
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState("level", action:"music Player.setLevel")
}
tileAttribute ("device.mute", key: "MEDIA_MUTED") {
attributeState("unmuted", action:"music Player.mute", nextState: "muted")
attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
}
tileAttribute("device.trackDescription", key: "MARQUEE") {
attributeState("default", label:"${currentValue}")
}
}
main "mediaMulti"
details(["mediaMulti"])
}
}
def installed() {
state.tracks = [
"Gangnam Style (강남스타일)\nPSY\nPsy 6 (Six Rules), Part 1",
"Careless Whisper\nWham!\nMake It Big",
"Never Gonna Give You Up\nRick Astley\nWhenever You Need Somebody",
"Shake It Off\nTaylor Swift\n1989",
"Ironic\nAlanis Morissette\nJagged Little Pill",
"Hotline Bling\nDrake\nHotline Bling - Single"
]
state.currentTrack = 0
sendEvent(name: "level", value: 72)
sendEvent(name: "mute", value: "unmuted")
sendEvent(name: "status", value: "stopped")
}
def parse(description) {
// No parsing will happen with this simulated device.
}
def play() {
sendEvent(name: "status", value: "playing")
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
}
def pause() {
sendEvent(name: "status", value: "paused")
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
}
def stop() {
sendEvent(name: "status", value: "stopped")
}
def previousTrack() {
state.currentTrack = state.currentTrack - 1
if (state.currentTrack < 0)
state.currentTrack = state.tracks.size()-1
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
}
def nextTrack() {
state.currentTrack = state.currentTrack + 1
if (state.currentTrack == state.tracks.size())
state.currentTrack = 0
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
}
def mute() {
sendEvent(name: "mute", value: "muted")
}
def unmute() {
sendEvent(name: "mute", value: "unmuted")
}
def setLevel(level) {
sendEvent(name: "level", value: level)
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2015 SmartThings
* Copyright 2014 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
@@ -15,7 +15,6 @@ metadata {
// Automatically generated. Make future change here.
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
capability "Thermostat"
capability "Relative Humidity Measurement"
command "tempUp"
command "tempDown"
@@ -23,40 +22,11 @@ metadata {
command "heatDown"
command "coolUp"
command "coolDown"
command "setTemperature", ["number"]
command "setTemperature", ["number"]
}
tiles(scale: 2) {
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("default", action: "setTemperature")
}
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
attributeState("default", label:'${currentValue}%', unit:"%")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
attributeState("cooling", backgroundColor:"#269bd2")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'${name}')
attributeState("heat", label:'${name}')
attributeState("cool", label:'${name}')
attributeState("auto", label:'${name}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("default", label:'${currentValue}', unit:"dF")
}
}
valueTile("temperature", "device.temperature", width: 2, height: 2) {
tiles {
valueTile("temperature", "device.temperature", width: 1, height: 1) {
state("temperature", label:'${currentValue}', unit:"dF",
backgroundColors:[
[value: 31, color: "#153591"],
@@ -69,51 +39,51 @@ metadata {
]
)
}
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("tempDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"tempDown"
}
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("tempUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"tempUp"
}
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
}
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("heatDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"heatDown"
}
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("heatUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"heatUp"
}
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
}
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("coolDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'down', action:"coolDown"
}
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("coolUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
state "default", label:'up', action:"coolUp"
}
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
state "off", label:'${name}', action:"thermostat.heat", backgroundColor:"#ffffff"
state "heat", label:'${name}', action:"thermostat.cool", backgroundColor:"#ffa81e"
state "cool", label:'${name}', action:"thermostat.auto", backgroundColor:"#269bd2"
state "auto", label:'${name}', action:"thermostat.off", backgroundColor:"#79b821"
}
standardTile("fanMode", "device.thermostatFanMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
state "fanAuto", label:'${name}', action:"thermostat.fanOn", backgroundColor:"#ffffff"
state "fanOn", label:'${name}', action:"thermostat.fanCirculate", backgroundColor:"#ffffff"
state "fanCirculate", label:'${name}', action:"thermostat.fanAuto", backgroundColor:"#ffffff"
}
standardTile("operatingState", "device.thermostatOperatingState", width: 2, height: 2) {
standardTile("operatingState", "device.thermostatOperatingState") {
state "idle", label:'${name}', backgroundColor:"#ffffff"
state "heating", label:'${name}', backgroundColor:"#ffa81e"
state "cooling", label:'${name}', backgroundColor:"#269bd2"
}
main("thermostatMulti")
main("temperature","operatingState")
details([
"temperature","tempDown","tempUp",
"mode", "fanMode", "operatingState",
@@ -131,7 +101,6 @@ def installed() {
sendEvent(name: "thermostatMode", value: "off")
sendEvent(name: "thermostatFanMode", value: "fanAuto")
sendEvent(name: "thermostatOperatingState", value: "idle")
sendEvent(name: "humidity", value: 53, unit: "%")
}
def parse(String description) {

View File

@@ -29,7 +29,7 @@ metadata {
tiles {
valueTile("power", "device.power", canChangeIcon: true) {
valueTile("power", "device.power") {
state "power", label: '${currentValue} W'
}

View File

@@ -273,7 +273,7 @@ User-Agent: CyberGarage-HTTP/1.0
def poll() {
log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(30, setOffline)
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277

View File

@@ -77,8 +77,9 @@ def parse(String description) {
def result = []
def bodyString = msg.body
if (bodyString) {
unschedule("setOffline")
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) {
log.trace "Got TimeSyncRequest"
result << timeSyncResponse()
@@ -133,7 +134,7 @@ def refresh() {
def getStatus() {
log.debug "Executing WeMo Motion 'getStatus'"
if (device.currentValue("currentIP") != "Offline")
runIn(30, setOffline)
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277

View File

@@ -28,7 +28,6 @@
command "subscribe"
command "resubscribe"
command "unsubscribe"
command "setOffline"
}
// simulator metadata
@@ -208,7 +207,7 @@ def subscribe(ip, port) {
def existingIp = getDataValue("ip")
def existingPort = getDataValue("port")
if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip"
log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip)
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
@@ -276,7 +275,7 @@ def setOffline() {
def poll() {
log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(30, setOffline)
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277
@@ -291,4 +290,4 @@ User-Agent: CyberGarage-HTTP/1.0
</u:GetBinaryState>
</s:Body>
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,189 +0,0 @@
/**
* Vinli Home Beta
*
* Copyright 2015 Daniel
*
*/
definition(
name: "Vinli Home Connect",
namespace: "com.vinli.smartthings",
author: "Daniel",
description: "Allows Vinli users to connect their car to SmartThings",
category: "SmartThings Labs",
iconUrl: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_60.png",
iconX2Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_120.png",
iconX3Url: "https://d3azp77rte0gip.cloudfront.net/smartapps/baeb2e5d-ebd0-49fe-a4ec-e92417ae20bb/images/vinli_oauth_120.png",
oauth: true)
preferences {
section ("Allow external service to control these things...") {
input "switches", "capability.switch", multiple: true, required: true
input "locks", "capability.lock", multiple: true, required: true
}
}
mappings {
path("/devices") {
action: [
GET: "listAllDevices"
]
}
path("/switches") {
action: [
GET: "listSwitches"
]
}
path("/switches/:command") {
action: [
PUT: "updateSwitches"
]
}
path("/switches/:id/:command") {
action: [
PUT: "updateSwitch"
]
}
path("/locks/:command") {
action: [
PUT: "updateLocks"
]
}
path("/locks/:id/:command") {
action: [
PUT: "updateLock"
]
}
path("/devices/:id/:command") {
action: [
PUT: "commandDevice"
]
}
}
// returns a list of all devices
def listAllDevices() {
def resp = []
switches.each {
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub.name]
}
locks.each {
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub.name]
}
return resp
}
// returns a list like
// [[name: "kitchen lamp", value: "off"], [name: "bathroom", value: "on"]]
def listSwitches() {
def resp = []
switches.each {
resp << [name: it.displayName, value: it.currentValue("switch"), type: "switch", id: it.id]
}
return resp
}
void updateLocks() {
// use the built-in request object to get the command parameter
def command = params.command
if (command) {
// check that the switch supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
locks.each {
if (!it.hasCommand(command)) {
httpError(501, "$command is not a valid command for all switches specified")
}
}
// all switches have the comand
// execute the command on all switches
// (note we can do this on the array - the command will be invoked on every element
locks."$command"()
}
}
void updateLock() {
def command = params.command
locks.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all lock specified")
}
if (it.id == params.id) {
it."$command"()
}
}
}
void updateSwitch() {
def command = params.command
switches.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all switches specified")
}
if (it.id == params.id) {
it."$command"()
}
}
}
void commandDevice() {
def command = params.command
def devices = []
switches.each {
devices << it
}
locks.each {
devices << it
}
devices.each {
if (it.id == params.id) {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for specified device")
}
it."$command"()
}
}
}
void updateSwitches() {
// use the built-in request object to get the command parameter
def command = params.command
if (command) {
// check that the switch supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
switches.each {
if (!it.hasCommand(command)) {
httpError(400, "$command is not a valid command for all switches specified")
}
}
// all switches have the comand
// execute the command on all switches
// (note we can do this on the array - the command will be invoked on every element
switches."$command"()
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
}

View File

@@ -13,7 +13,7 @@ preferences{
input "lock1", "capability.lock", required: true
}
section("Select the door contact sensor:") {
input "contact", "capability.contactSensor", required: true
input "contact", "capability.contactSensor", required: true
}
section("Automatically lock the door when closed...") {
input "minutesLater", "number", title: "Delay (in minutes):", required: true
@@ -22,10 +22,9 @@ preferences{
input "secondsLater", "number", title: "Delay (in seconds):", required: true
}
section( "Notifications" ) {
input("recipients", "contact", title: "Send notifications to", required: false) {
input "phoneNumber", "phone", title: "Warn with text message (optional)", description: "Phone Number", required: false
}
}
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes", "No"]], required: false
input "phoneNumber", "phone", title: "Enter phone number to send text notification.", required: false
}
}
def installed(){
@@ -43,73 +42,55 @@ def initialize(){
subscribe(lock1, "lock", doorHandler, [filterEvents: false])
subscribe(lock1, "unlock", doorHandler, [filterEvents: false])
subscribe(contact, "contact.open", doorHandler)
subscribe(contact, "contact.closed", doorHandler)
subscribe(contact, "contact.closed", doorHandler)
}
def lockDoor(){
log.debug "Locking the door."
lock1.lock()
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!", recipients)
}
}
if (phoneNumber) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!")
}
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} locked after ${contact} was closed for ${minutesLater} minutes!" )
}
def unlockDoor(){
log.debug "Unlocking the door."
lock1.unlock()
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!", recipients)
}
}
if ( phoneNumber ) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!")
}
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened for ${secondsLater} seconds!" )
}
def doorHandler(evt){
if ((contact.latestValue("contact") == "open") && (evt.value == "locked")) { // If the door is open and a person locks the door then...
//def delay = (secondsLater) // runIn uses seconds
runIn( secondsLater, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged.
def delay = (secondsLater) // runIn uses seconds
runIn( delay, unlockDoor ) // ...schedule (in minutes) to unlock... We don't want the door to be closed while the lock is engaged.
}
else if ((contact.latestValue("contact") == "open") && (evt.value == "unlocked")) { // If the door is open and a person unlocks it then...
unschedule( unlockDoor ) // ...we don't need to unlock it later.
}
}
else if ((contact.latestValue("contact") == "closed") && (evt.value == "locked")) { // If the door is closed and a person manually locks it then...
unschedule( lockDoor ) // ...we don't need to lock it later.
}
else if ((contact.latestValue("contact") == "closed") && (evt.value == "unlocked")) { // If the door is closed and a person unlocks it then...
//def delay = (minutesLater * 60) // runIn uses seconds
runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
def delay = (minutesLater * 60) // runIn uses seconds
runIn( delay, lockDoor ) // ...schedule (in minutes) to lock.
}
else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "open")) { // If a person opens an unlocked door...
unschedule( lockDoor ) // ...we don't need to lock it later.
}
else if ((lock1.latestValue("lock") == "unlocked") && (evt.value == "closed")) { // If a person closes an unlocked door...
//def delay = (minutesLater * 60) // runIn uses seconds
runIn( (minutesLater * 60), lockDoor ) // ...schedule (in minutes) to lock.
}
def delay = (minutesLater * 60) // runIn uses seconds
runIn( delay, lockDoor ) // ...schedule (in minutes) to lock.
}
else { //Opening or Closing door when locked (in case you have a handle lock)
log.debug "Unlocking the door."
lock1.unlock()
if(location.contactBookEnabled) {
if ( recipients ) {
log.debug ( "Sending Push Notification..." )
sendNotificationToContacts( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!", recipients)
}
}
if ( phoneNumber ) {
log.debug("Sending text message...")
sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!")
}
}
log.debug "Unlocking the door."
lock1.unlock()
log.debug ( "Sending Push Notification..." )
if ( sendPushMessage != "No" ) sendPush( "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!" )
log.debug("Sending text message...")
if ( phoneNumber != "0" ) sendSms( phoneNumber, "${lock1} unlocked after ${contact} was opened or closed when ${lock1} was locked!" )
}
}

View File

@@ -1,383 +0,0 @@
/**
* Simple Sync Connect
*
* Copyright 2015 Roomie Remote, Inc.
*
* Date: 2015-09-22
*/
definition(
name: "Simple Sync Connect",
namespace: "roomieremote-raconnect",
author: "Roomie Remote, Inc.",
description: "Integrate SmartThings with your Simple Control activities via Simple Sync.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
preferences()
{
page(name: "mainPage", title: "Simple Sync Setup", content: "mainPage", refreshTimeout: 5)
page(name:"agentDiscovery", title:"Simple Sync Discovery", content:"agentDiscovery", refreshTimeout:5)
page(name:"manualAgentEntry")
page(name:"verifyManualEntry")
}
def mainPage()
{
if (canInstallLabs())
{
return agentDiscovery()
}
else
{
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
return dynamicPage(name:"mainPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
section("Upgrade")
{
paragraph "$upgradeNeeded"
}
}
}
}
def agentDiscovery(params=[:])
{
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
state.refreshCount = refreshCount + 1
def refreshInterval = refreshCount == 0 ? 2 : 5
if (!state.subscribe)
{
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
//ssdp request every fifth refresh
if ((refreshCount % 5) == 0)
{
discoverAgents()
}
def agentsDiscovered = agentsDiscovered()
return dynamicPage(name:"agentDiscovery", title:"Pair with Simple Sync", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
section("Pair with Simple Sync")
{
input "selectedAgent", "enum", required:true, title:"Select Simple Sync\n(${agentsDiscovered.size() ?: 0} found)", multiple:false, options:agentsDiscovered
href(name:"manualAgentEntry",
title:"Manually Configure Simple Sync",
required:false,
page:"manualAgentEntry")
}
}
}
def manualAgentEntry()
{
dynamicPage(name:"manualAgentEntry", title:"Manually Configure Simple Sync", nextPage:"verifyManualEntry", install:false, uninstall:true) {
section("Manually Configure Simple Sync")
{
paragraph "In the event that Simple Sync cannot be automatically discovered by your SmartThings hub, you may enter Simple Sync's IP address here."
input(name: "manualIPAddress", type: "text", title: "IP Address", required: true)
}
}
}
def verifyManualEntry()
{
def hexIP = convertIPToHexString(manualIPAddress)
def hexPort = convertToHexString(47147)
def uuid = "593C03D2-1DA9-4CDB-A335-6C6DC98E56C3"
def hubId = ""
for (hub in location.hubs)
{
if (hub.localIP != null)
{
hubId = hub.id
break
}
}
def manualAgent = [deviceType: "04",
mac: "unknown",
ip: hexIP,
port: hexPort,
ssdpPath: "/upnp/Roomie.xml",
ssdpUSN: "uuid:$uuid::urn:roomieremote-com:device:roomie:1",
hub: hubId,
verified: true,
name: "Simple Sync $manualIPAddress"]
state.agents[uuid] = manualAgent
addOrUpdateAgent(state.agents[uuid])
dynamicPage(name: "verifyManualEntry", title: "Manual Configuration Complete", nextPage: "", install:true, uninstall:true) {
section("")
{
paragraph("Tap Done to complete the installation process.")
}
}
}
def discoverAgents()
{
def urn = getURN()
sendHubCommand(new physicalgraph.device.HubAction("lan discovery $urn", physicalgraph.device.Protocol.LAN))
}
def agentsDiscovered()
{
def gAgents = getAgents()
def agents = gAgents.findAll { it?.value?.verified == true }
def map = [:]
agents.each
{
map["${it.value.uuid}"] = it.value.name
}
map
}
def getAgents()
{
if (!state.agents)
{
state.agents = [:]
}
state.agents
}
def installed()
{
initialize()
}
def updated()
{
initialize()
}
def initialize()
{
if (state.subscribe)
{
unsubscribe()
state.subscribe = false
}
if (selectedAgent)
{
addOrUpdateAgent(state.agents[selectedAgent])
}
}
def addOrUpdateAgent(agent)
{
def children = getChildDevices()
def dni = agent.ip + ":" + agent.port
def found = false
children.each
{
if ((it.getDeviceDataByName("mac") == agent.mac))
{
found = true
if (it.getDeviceNetworkId() != dni)
{
it.setDeviceNetworkId(dni)
}
}
else if (it.getDeviceNetworkId() == dni)
{
found = true
}
}
if (!found)
{
addChildDevice("roomieremote-agent", "Simple Sync", dni, agent.hub, [label: "Simple Sync"])
}
}
def locationHandler(evt)
{
def description = evt?.description
def urn = getURN()
def hub = evt?.hubId
def parsedEvent = parseEventMessage(description)
parsedEvent?.putAt("hub", hub)
//SSDP DISCOVERY EVENTS
if (parsedEvent?.ssdpTerm?.contains(urn))
{
def agent = parsedEvent
def ip = convertHexToIP(agent.ip)
def agents = getAgents()
agent.verified = true
agent.name = "Simple Sync $ip"
if (!agents[agent.uuid])
{
state.agents[agent.uuid] = agent
}
}
}
private def parseEventMessage(String description)
{
def event = [:]
def parts = description.split(',')
parts.each
{ part ->
part = part.trim()
if (part.startsWith('devicetype:'))
{
def valueString = part.split(":")[1].trim()
event.devicetype = valueString
}
else if (part.startsWith('mac:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.mac = valueString
}
}
else if (part.startsWith('networkAddress:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.ip = valueString
}
}
else if (part.startsWith('deviceAddress:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.port = valueString
}
}
else if (part.startsWith('ssdpPath:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.ssdpPath = valueString
}
}
else if (part.startsWith('ssdpUSN:'))
{
part -= "ssdpUSN:"
def valueString = part.trim()
if (valueString)
{
event.ssdpUSN = valueString
def uuid = getUUIDFromUSN(valueString)
if (uuid)
{
event.uuid = uuid
}
}
}
else if (part.startsWith('ssdpTerm:'))
{
part -= "ssdpTerm:"
def valueString = part.trim()
if (valueString)
{
event.ssdpTerm = valueString
}
}
else if (part.startsWith('headers'))
{
part -= "headers:"
def valueString = part.trim()
if (valueString)
{
event.headers = valueString
}
}
else if (part.startsWith('body'))
{
part -= "body:"
def valueString = part.trim()
if (valueString)
{
event.body = valueString
}
}
}
event
}
def getURN()
{
return "urn:roomieremote-com:device:roomie:1"
}
def getUUIDFromUSN(usn)
{
def parts = usn.split(":")
for (int i = 0; i < parts.size(); ++i)
{
if (parts[i] == "uuid")
{
return parts[i + 1]
}
}
}
def String convertHexToIP(hex)
{
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
def Integer convertHexToInt(hex)
{
Integer.parseInt(hex,16)
}
def String convertToHexString(n)
{
String hex = String.format("%X", n.toInteger())
}
def String convertIPToHexString(ipString)
{
String hex = ipString.tokenize(".").collect {
String.format("%02X", it.toInteger())
}.join()
}
def Boolean canInstallLabs()
{
return hasAllHubsOver("000.011.00603")
}
def Boolean hasAllHubsOver(String desiredFirmware)
{
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
def List getRealHubFirmwareVersions()
{
return location.hubs*.firmwareVersionString.findAll { it }
}

View File

@@ -1,296 +0,0 @@
/**
* Simple Sync Trigger
*
* Copyright 2015 Roomie Remote, Inc.
*
* Date: 2015-09-22
*/
definition(
name: "Simple Sync Trigger",
namespace: "roomieremote-ratrigger",
author: "Roomie Remote, Inc.",
description: "Trigger Simple Control activities when certain actions take place in your home.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
preferences {
page(name: "agentSelection", title: "Select your Simple Sync")
page(name: "refreshActivities", title: "Updating list of Simple Sync activities")
page(name: "control", title: "Run a Simple Control activity when something happens")
page(name: "timeIntervalInput", title: "Only during a certain time", install: true, uninstall: true) {
section {
input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false
}
}
}
def agentSelection()
{
if (agent)
{
state.refreshCount = 0
}
dynamicPage(name: "agentSelection", title: "Select your Simple Sync", nextPage: "control", install: false, uninstall: true) {
section {
input "agent", "capability.mediaController", title: "Simple Sync", required: true, multiple: false
}
}
}
def control()
{
def activities = agent.latestValue('activities')
if (!activities || !state.refreshCount)
{
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
state.refreshCount = refreshCount + 1
def refreshInterval = refreshCount == 0 ? 2 : 4
// Request activities every 5th attempt
if((refreshCount % 5) == 0)
{
agent.getAllActivities()
}
dynamicPage(name: "control", title: "Updating list of Simple Control activities", nextPage: "", refreshInterval: refreshInterval, install: false, uninstall: true) {
section("") {
paragraph "Retrieving activities from Simple Sync"
}
}
}
else
{
dynamicPage(name: "control", title: "Run a Simple Control activity when something happens", nextPage: "timeIntervalInput", install: false, uninstall: true) {
def anythingSet = anythingSet()
if (anythingSet) {
section("When..."){
ifSet "motion", "capability.motionSensor", title: "Motion Detected", required: false, multiple: true
ifSet "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true
ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
}
}
section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
ifUnset "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true
ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
}
section("Run this activity"){
input "activity", "enum", title: "Activity?", required: true, options: new groovy.json.JsonSlurper().parseText(activities ?: "[]").activities?.collect { ["${it.uuid}": it.name] }
}
section("More options", hideable: true, hidden: true) {
input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
}
section([mobileOnly:true]) {
label title: "Assign a name", required: false
mode title: "Set for specific mode(s)"
}
}
}
}
private anythingSet() {
for (name in ["motion","motionInactive","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","button1","triggerModes","timeOfDay"]) {
if (settings[name]) {
return true
}
}
return false
}
private ifUnset(Map options, String name, String capability) {
if (!settings[name]) {
input(options, name, capability)
}
}
private ifSet(Map options, String name, String capability) {
if (settings[name]) {
input(options, name, capability)
}
}
def installed() {
subscribeToEvents()
}
def updated() {
unsubscribe()
unschedule()
subscribeToEvents()
}
def subscribeToEvents() {
log.trace "subscribeToEvents()"
subscribe(app, appTouchHandler)
subscribe(contact, "contact.open", eventHandler)
subscribe(contactClosed, "contact.closed", eventHandler)
subscribe(acceleration, "acceleration.active", eventHandler)
subscribe(motion, "motion.active", eventHandler)
subscribe(motionInactive, "motion.inactive", eventHandler)
subscribe(mySwitch, "switch.on", eventHandler)
subscribe(mySwitchOff, "switch.off", eventHandler)
subscribe(arrivalPresence, "presence.present", eventHandler)
subscribe(departurePresence, "presence.not present", eventHandler)
subscribe(button1, "button.pushed", eventHandler)
if (triggerModes) {
subscribe(location, modeChangeHandler)
}
if (timeOfDay) {
schedule(timeOfDay, scheduledTimeHandler)
}
}
def eventHandler(evt) {
if (allOk) {
def lastTime = state[frequencyKey(evt)]
if (oncePerDayOk(lastTime)) {
if (frequency) {
if (lastTime == null || now() - lastTime >= frequency * 60000) {
startActivity(evt)
}
else {
log.debug "Not taking action because $frequency minutes have not elapsed since last action"
}
}
else {
startActivity(evt)
}
}
else {
log.debug "Not taking action because it was already taken today"
}
}
}
def modeChangeHandler(evt) {
log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
if (evt.value in triggerModes) {
eventHandler(evt)
}
}
def scheduledTimeHandler() {
eventHandler(null)
}
def appTouchHandler(evt) {
startActivity(evt)
}
private startActivity(evt) {
agent.startActivity(activity)
if (frequency) {
state.lastActionTimeStamp = now()
}
}
private frequencyKey(evt) {
//evt.deviceId ?: evt.value
"lastActionTimeStamp"
}
private dayString(Date date) {
def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
df.format(date)
}
private oncePerDayOk(Long lastTime) {
def result = true
if (oncePerDay) {
result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
log.trace "oncePerDayOk = $result"
}
result
}
// TODO - centralize somehow
private getAllOk() {
modeOk && daysOk && timeOk
}
private getModeOk() {
def result = !modes || modes.contains(location.mode)
log.trace "modeOk = $result"
result
}
private getDaysOk() {
def result = true
if (days) {
def df = new java.text.SimpleDateFormat("EEEE")
if (location.timeZone) {
df.setTimeZone(location.timeZone)
}
else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
}
def day = df.format(new Date())
result = days.contains(day)
}
log.trace "daysOk = $result"
result
}
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting).time
def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
log.trace "timeOk = $result"
result
}
private hhmm(time, fmt = "h:mm a")
{
def t = timeToday(time, location.timeZone)
def f = new java.text.SimpleDateFormat(fmt)
f.setTimeZone(location.timeZone ?: timeZone(time))
f.format(t)
}
private timeIntervalLabel()
{
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
}

View File

@@ -1,774 +0,0 @@
/**
* Simple Control
*
* Copyright 2015 Roomie Remote, Inc.
*
* Date: 2015-09-22
*/
definition(
name: "Simple Control",
namespace: "roomieremote-roomieconnect",
author: "Roomie Remote, Inc.",
description: "Integrate SmartThings with your Simple Control activities.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
preferences()
{
section("Allow Simple Control to Monitor and Control These Things...")
{
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
}
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
page(name:"agentDiscovery", title:"Simple Sync Discovery", content:"agentDiscovery", refreshTimeout:5)
page(name:"manualAgentEntry")
page(name:"verifyManualEntry")
}
mappings {
path("/devices") {
action: [
GET: "getDevices"
]
}
path("/:deviceType/devices") {
action: [
GET: "getDevices",
POST: "handleDevicesWithIDs"
]
}
path("/device/:deviceType/:id") {
action: [
GET: "getDevice",
POST: "updateDevice"
]
}
path("/subscriptions") {
action: [
GET: "listSubscriptions",
POST: "addSubscription", // {"deviceId":"xxx", "attributeName":"xxx","callbackUrl":"http://..."}
DELETE: "removeAllSubscriptions"
]
}
path("/subscriptions/:id") {
action: [
DELETE: "removeSubscription"
]
}
}
private getAllDevices()
{
//log.debug("getAllDevices()")
([] + switches + locks + thermostats + imageCaptures + relaySwitches + doorControls + colorControls + musicPlayers + speechSynthesizers + switchLevels + indicators + mediaControllers + tones + tvs + alarms + valves + motionSensors + presenceSensors + beacons + pushButtons + smokeDetectors + coDetectors + contactSensors + accelerationSensors + energyMeters + powerMeters + lightSensors + humiditySensors + temperatureSensors + speechRecognizers + stepSensors + touchSensors)?.findAll()?.unique { it.id }
}
def getDevices()
{
//log.debug("getDevices, params: ${params}")
allDevices.collect {
//log.debug("device: ${it}")
deviceItem(it)
}
}
def getDevice()
{
//log.debug("getDevice, params: ${params}")
def device = allDevices.find { it.id == params.id }
if (!device)
{
render status: 404, data: '{"msg": "Device not found"}'
}
else
{
deviceItem(device)
}
}
def handleDevicesWithIDs()
{
//log.debug("handleDevicesWithIDs, params: ${params}")
def data = request.JSON
def ids = data?.ids?.findAll()?.unique()
//log.debug("ids: ${ids}")
def command = data?.command
def arguments = data?.arguments
def type = params?.deviceType
//log.debug("device type: ${type}")
if (command)
{
def statusCode = 404
//log.debug("command ${command}, arguments ${arguments}")
for (devId in ids)
{
def device = allDevices.find { it.id == devId }
//log.debug("device: ${device}")
// Check if we have a device that responds to the specified command
if (validateCommand(device, type, command)) {
if (arguments) {
device."$command"(*arguments)
}
else {
device."$command"()
}
statusCode = 200
} else {
statusCode = 403
}
}
def responseData = "{}"
switch (statusCode)
{
case 403:
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
break
case 404:
responseData = '{"msg": "Device not found"}'
break
}
render status: statusCode, data: responseData
}
else
{
ids.collect {
def currentId = it
def device = allDevices.find { it.id == currentId }
if (device)
{
deviceItem(device)
}
}
}
}
private deviceItem(device) {
[
id: device.id,
label: device.displayName,
currentState: device.currentStates,
capabilities: device.capabilities?.collect {[
name: it.name
]},
attributes: device.supportedAttributes?.collect {[
name: it.name,
dataType: it.dataType,
values: it.values
]},
commands: device.supportedCommands?.collect {[
name: it.name,
arguments: it.arguments
]},
type: [
name: device.typeName,
author: device.typeAuthor
]
]
}
def updateDevice()
{
//log.debug("updateDevice, params: ${params}")
def data = request.JSON
def command = data?.command
def arguments = data?.arguments
def type = params?.deviceType
//log.debug("device type: ${type}")
//log.debug("updateDevice, params: ${params}, request: ${data}")
if (!command) {
render status: 400, data: '{"msg": "command is required"}'
} else {
def statusCode = 404
def device = allDevices.find { it.id == params.id }
if (device) {
// Check if we have a device that responds to the specified command
if (validateCommand(device, type, command)) {
if (arguments) {
device."$command"(*arguments)
}
else {
device."$command"()
}
statusCode = 200
} else {
statusCode = 403
}
}
def responseData = "{}"
switch (statusCode)
{
case 403:
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
break
case 404:
responseData = '{"msg": "Device not found"}'
break
}
render status: statusCode, data: responseData
}
}
/**
* Validating the command passed by the user based on capability.
* @return boolean
*/
def validateCommand(device, deviceType, command) {
//log.debug("validateCommand ${command}")
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
//log.debug("capabilityCommands: ${capabilityCommands}")
def currentDeviceCapability = getCapabilityName(deviceType)
//log.debug("currentDeviceCapability: ${currentDeviceCapability}")
if (capabilityCommands[currentDeviceCapability]) {
return command in capabilityCommands[currentDeviceCapability] ? true : false
} else {
// Handling other device types here, which don't accept commands
httpError(400, "Bad request.")
}
}
/**
* Need to get the attribute name to do the lookup. Only
* doing it for the device types which accept commands
* @return attribute name of the device type
*/
def getCapabilityName(type) {
switch(type) {
case "switches":
return "Switch"
case "locks":
return "Lock"
case "thermostats":
return "Thermostat"
case "doorControls":
return "Door Control"
case "colorControls":
return "Color Control"
case "musicPlayers":
return "Music Player"
case "switchLevels":
return "Switch Level"
default:
return type
}
}
/**
* Constructing the map over here of
* supported commands by device capability
* @return a map of device capability -> supported commands
*/
def getDeviceCapabilityCommands(deviceCapabilities) {
def map = [:]
deviceCapabilities.collect {
map[it.name] = it.commands.collect{ it.name.toString() }
}
return map
}
def listSubscriptions()
{
//log.debug "listSubscriptions()"
app.subscriptions?.findAll { it.deviceId }?.collect {
def deviceInfo = state[it.deviceId]
def response = [
id: it.id,
deviceId: it.deviceId,
attributeName: it.data,
handler: it.handler
]
//if (!selectedAgent) {
response.callbackUrl = deviceInfo?.callbackUrl
//}
response
} ?: []
}
def addSubscription() {
def data = request.JSON
def attribute = data.attributeName
def callbackUrl = data.callbackUrl
//log.debug "addSubscription, params: ${params}, request: ${data}"
if (!attribute) {
render status: 400, data: '{"msg": "attributeName is required"}'
} else {
def device = allDevices.find { it.id == data.deviceId }
if (device) {
//if (!selectedAgent) {
//log.debug "Adding callbackUrl: $callbackUrl"
state[device.id] = [callbackUrl: callbackUrl]
//}
//log.debug "Adding subscription"
def subscription = subscribe(device, attribute, deviceHandler)
if (!subscription || !subscription.eventSubscription) {
//log.debug("subscriptions: ${app.subscriptions}")
//for (sub in app.subscriptions)
//{
//log.debug("subscription.id ${sub.id} subscription.handler ${sub.handler} subscription.deviceId ${sub.deviceId}")
//log.debug(sub.properties.collect{it}.join('\n'))
//}
subscription = app.subscriptions?.find { it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' }
}
def response = [
id: subscription.id,
deviceId: subscription.device?.id,
attributeName: subscription.data,
handler: subscription.handler
]
//if (!selectedAgent) {
response.callbackUrl = callbackUrl
//}
response
} else {
render status: 400, data: '{"msg": "Device not found"}'
}
}
}
def removeSubscription()
{
def subscription = app.subscriptions?.find { it.id == params.id }
def device = subscription?.device
//log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}"
if (device) {
//log.debug "Removing subscription for device: ${device.id}"
state.remove(device.id)
unsubscribe(device)
}
render status: 204, data: "{}"
}
def removeAllSubscriptions()
{
for (sub in app.subscriptions)
{
//log.debug("Subscription: ${sub}")
//log.debug(sub.properties.collect{it}.join('\n'))
def handler = sub.handler
def device = sub.device
if (device && handler == 'deviceHandler')
{
//log.debug(device.properties.collect{it}.join('\n'))
//log.debug("Removing subscription for device: ${device}")
state.remove(device.id)
unsubscribe(device)
}
}
}
def deviceHandler(evt) {
def deviceInfo = state[evt.deviceId]
//if (selectedAgent) {
// sendToRoomie(evt, agentCallbackUrl)
//} else if (deviceInfo) {
if (deviceInfo)
{
if (deviceInfo.callbackUrl) {
sendToRoomie(evt, deviceInfo.callbackUrl)
} else {
log.warn "No callbackUrl set for device: ${evt.deviceId}"
}
} else {
log.warn "No subscribed device found for device: ${evt.deviceId}"
}
}
def sendToRoomie(evt, String callbackUrl) {
def callback = new URI(callbackUrl)
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
sendHubCommand(new physicalgraph.device.HubAction(
method: "POST",
path: path,
headers: [
"Host": host,
"Content-Type": "application/json"
],
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
))
}
def mainPage()
{
if (canInstallLabs())
{
return agentDiscovery()
}
else
{
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
return dynamicPage(name:"mainPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
section("Upgrade")
{
paragraph "$upgradeNeeded"
}
}
}
}
def agentDiscovery(params=[:])
{
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
state.refreshCount = refreshCount + 1
def refreshInterval = refreshCount == 0 ? 2 : 5
if (!state.subscribe)
{
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
//ssdp request every fifth refresh
if ((refreshCount % 5) == 0)
{
discoverAgents()
}
def agentsDiscovered = agentsDiscovered()
return dynamicPage(name:"agentDiscovery", title:"Pair with Simple Sync", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
section("Pair with Simple Sync")
{
input "selectedAgent", "enum", required:false, title:"Select Simple Sync\n(${agentsDiscovered.size() ?: 0} found)", multiple:false, options:agentsDiscovered
href(name:"manualAgentEntry",
title:"Manually Configure Simple Sync",
required:false,
page:"manualAgentEntry")
}
section("Allow Simple Control to Monitor and Control These Things...")
{
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
}
}
}
def manualAgentEntry()
{
dynamicPage(name:"manualAgentEntry", title:"Manually Configure Simple Sync", nextPage:"verifyManualEntry", install:false, uninstall:true) {
section("Manually Configure Simple Sync")
{
paragraph "In the event that Simple Sync cannot be automatically discovered by your SmartThings hub, you may enter Simple Sync's IP address here."
input(name: "manualIPAddress", type: "text", title: "IP Address", required: true)
}
}
}
def verifyManualEntry()
{
def hexIP = convertIPToHexString(manualIPAddress)
def hexPort = convertToHexString(47147)
def uuid = "593C03D2-1DA9-4CDB-A335-6C6DC98E56C3"
def hubId = ""
for (hub in location.hubs)
{
if (hub.localIP != null)
{
hubId = hub.id
break
}
}
def manualAgent = [deviceType: "04",
mac: "unknown",
ip: hexIP,
port: hexPort,
ssdpPath: "/upnp/Roomie.xml",
ssdpUSN: "uuid:$uuid::urn:roomieremote-com:device:roomie:1",
hub: hubId,
verified: true,
name: "Simple Sync $manualIPAddress"]
state.agents[uuid] = manualAgent
addOrUpdateAgent(state.agents[uuid])
dynamicPage(name: "verifyManualEntry", title: "Manual Configuration Complete", nextPage: "", install:true, uninstall:true) {
section("")
{
paragraph("Tap Done to complete the installation process.")
}
}
}
def discoverAgents()
{
def urn = getURN()
sendHubCommand(new physicalgraph.device.HubAction("lan discovery $urn", physicalgraph.device.Protocol.LAN))
}
def agentsDiscovered()
{
def gAgents = getAgents()
def agents = gAgents.findAll { it?.value?.verified == true }
def map = [:]
agents.each
{
map["${it.value.uuid}"] = it.value.name
}
map
}
def getAgents()
{
if (!state.agents)
{
state.agents = [:]
}
state.agents
}
def installed()
{
initialize()
}
def updated()
{
initialize()
}
def initialize()
{
if (state.subscribe)
{
unsubscribe()
state.subscribe = false
}
if (selectedAgent)
{
addOrUpdateAgent(state.agents[selectedAgent])
}
}
def addOrUpdateAgent(agent)
{
def children = getChildDevices()
def dni = agent.ip + ":" + agent.port
def found = false
children.each
{
if ((it.getDeviceDataByName("mac") == agent.mac))
{
found = true
if (it.getDeviceNetworkId() != dni)
{
it.setDeviceNetworkId(dni)
}
}
else if (it.getDeviceNetworkId() == dni)
{
found = true
}
}
if (!found)
{
addChildDevice("roomieremote-agent", "Simple Sync", dni, agent.hub, [label: "Simple Sync"])
}
}
def locationHandler(evt)
{
def description = evt?.description
def urn = getURN()
def hub = evt?.hubId
def parsedEvent = parseEventMessage(description)
parsedEvent?.putAt("hub", hub)
//SSDP DISCOVERY EVENTS
if (parsedEvent?.ssdpTerm?.contains(urn))
{
def agent = parsedEvent
def ip = convertHexToIP(agent.ip)
def agents = getAgents()
agent.verified = true
agent.name = "Simple Sync $ip"
if (!agents[agent.uuid])
{
state.agents[agent.uuid] = agent
}
}
}
private def parseEventMessage(String description)
{
def event = [:]
def parts = description.split(',')
parts.each
{ part ->
part = part.trim()
if (part.startsWith('devicetype:'))
{
def valueString = part.split(":")[1].trim()
event.devicetype = valueString
}
else if (part.startsWith('mac:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.mac = valueString
}
}
else if (part.startsWith('networkAddress:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.ip = valueString
}
}
else if (part.startsWith('deviceAddress:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.port = valueString
}
}
else if (part.startsWith('ssdpPath:'))
{
def valueString = part.split(":")[1].trim()
if (valueString)
{
event.ssdpPath = valueString
}
}
else if (part.startsWith('ssdpUSN:'))
{
part -= "ssdpUSN:"
def valueString = part.trim()
if (valueString)
{
event.ssdpUSN = valueString
def uuid = getUUIDFromUSN(valueString)
if (uuid)
{
event.uuid = uuid
}
}
}
else if (part.startsWith('ssdpTerm:'))
{
part -= "ssdpTerm:"
def valueString = part.trim()
if (valueString)
{
event.ssdpTerm = valueString
}
}
else if (part.startsWith('headers'))
{
part -= "headers:"
def valueString = part.trim()
if (valueString)
{
event.headers = valueString
}
}
else if (part.startsWith('body'))
{
part -= "body:"
def valueString = part.trim()
if (valueString)
{
event.body = valueString
}
}
}
event
}
def getURN()
{
return "urn:roomieremote-com:device:roomie:1"
}
def getUUIDFromUSN(usn)
{
def parts = usn.split(":")
for (int i = 0; i < parts.size(); ++i)
{
if (parts[i] == "uuid")
{
return parts[i + 1]
}
}
}
def String convertHexToIP(hex)
{
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
def Integer convertHexToInt(hex)
{
Integer.parseInt(hex,16)
}
def String convertToHexString(n)
{
String hex = String.format("%X", n.toInteger())
}
def String convertIPToHexString(ipString)
{
String hex = ipString.tokenize(".").collect {
String.format("%02X", it.toInteger())
}.join()
}
def Boolean canInstallLabs()
{
return hasAllHubsOver("000.011.00603")
}
def Boolean hasAllHubsOver(String desiredFirmware)
{
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
def List getRealHubFirmwareVersions()
{
return location.hubs*.firmwareVersionString.findAll { it }
}