mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-16 13:10:51 +00:00
Compare commits
34 Commits
MSA-685-7
...
master_old
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01a36696d8 | ||
|
|
797def2935 | ||
|
|
1034cd06e6 | ||
|
|
c37729242e | ||
|
|
d29c3ec557 | ||
|
|
17be85b846 | ||
|
|
da06104563 | ||
|
|
5d37ac8515 | ||
|
|
2a739fda07 | ||
|
|
f627fb4fac | ||
|
|
1d30a718b2 | ||
|
|
303ca7117c | ||
|
|
1c96645b9f | ||
|
|
af4dc0640a | ||
|
|
80b46153dc | ||
|
|
b92cd9c637 | ||
|
|
1039b65c81 | ||
|
|
4e203e8e13 | ||
|
|
61398105d1 | ||
|
|
505efc5463 | ||
|
|
3ddc82f996 | ||
|
|
13a324069d | ||
|
|
2fd5859326 | ||
|
|
bbedbddf9d | ||
|
|
e424e7abdd | ||
|
|
47fbdabf6b | ||
|
|
7defe1cc61 | ||
|
|
a78459347b | ||
|
|
c473745e47 | ||
|
|
fc587ef15a | ||
|
|
0f3b730f26 | ||
|
|
11df2f31b3 | ||
|
|
5e93bca030 | ||
|
|
4d243bf44d |
@@ -1,296 +0,0 @@
|
|||||||
/**
|
|
||||||
* obything Music Player
|
|
||||||
*
|
|
||||||
* Copyright 2015 obycode
|
|
||||||
*/
|
|
||||||
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "obything Music Player", namespace: "com.obycode", author: "obycode") {
|
|
||||||
capability "Music Player"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Switch"
|
|
||||||
|
|
||||||
// These strings are comma separated lists of names
|
|
||||||
attribute "playlists", "json_object"
|
|
||||||
attribute "speakers", "json_object"
|
|
||||||
attribute "playlistDescription", "string"
|
|
||||||
|
|
||||||
// playPlaylist(String uri, speakers=null, volume=null, resume=false, restore=false)
|
|
||||||
command "playPlaylist", ["string", "string", "number", "number", "number"]
|
|
||||||
// playTrack(String uri, speakers=null, volume=null, resume=false, restore=false, playlist=null)
|
|
||||||
command "playTrack", ["string", "string", "number", "number", "number", "string"]
|
|
||||||
|
|
||||||
command "update", ["string"]
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles {
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"richmusic", type:"lighting", width:6, height:4) {
|
|
||||||
tileAttribute("device.status", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "paused", label: 'Paused', action:"music Player.play", icon:"http://obything.obycode.com/icons/obything-device.png", backgroundColor:"#D0D0D0"
|
|
||||||
attributeState "stopped", label: 'Stopped', action:"music Player.play", icon:"http://obything.obycode.com/icons/obything-device.png", backgroundColor:"#D0D0D0"
|
|
||||||
attributeState "playing", label:'Playing', action:"music Player.pause", icon:"http://obything.obycode.com/icons/obything-device.png", backgroundColor:"#4C4CFF"
|
|
||||||
}
|
|
||||||
tileAttribute("device.trackDescription", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "default", label:'${currentValue}'
|
|
||||||
}
|
|
||||||
tileAttribute("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"music Player.setLevel", range:"(0..100)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("nextTrack", "device.status", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "next", label:'', action:"music Player.nextTrack", icon:"st.sonos.next-btn", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("playpause", "device.status", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "default", label:'', action:"music Player.play", icon:"st.sonos.play-btn", backgroundColor:"#ffffff"
|
|
||||||
state "playing", label:'', action:"music Player.pause", icon:"st.sonos.pause-btn", backgroundColor:"#ffffff"
|
|
||||||
state "paused", label:'', action:"music Player.play", icon:"st.sonos.play-btn", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("previousTrack", "device.status", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "previous", label:'', action:"music Player.previousTrack", icon:"st.sonos.previous-btn", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.status", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("mute", "device.mute", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "unmuted", label:"Mute", action:"music Player.mute", icon:"st.custom.sonos.unmuted", backgroundColor:"#ffffff"
|
|
||||||
state "muted", label:"Unmute", action:"music Player.unmute", icon:"st.custom.sonos.muted", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 2, width: 4) {
|
|
||||||
state "level", label:"Volume", action:"music Player.setLevel", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
valueTile("currentPlaylist", "device.playlistDescription", height:2, width:6, decoration: "flat") {
|
|
||||||
state "default", label:'${currentValue}', backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "richmusic"
|
|
||||||
details(["richmusic",
|
|
||||||
"previousTrack","nextTrack","refresh",
|
|
||||||
"levelSliderControl","mute",
|
|
||||||
"currentPlaylist"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
// log.debug "parse called with $description"
|
|
||||||
def map = stringToMap(description)
|
|
||||||
if (map.headers && map.body) { //got device info response
|
|
||||||
if (map.body) {
|
|
||||||
def bodyString = new String(map.body.decodeBase64())
|
|
||||||
def slurper = new JsonSlurper()
|
|
||||||
def result = slurper.parseText(bodyString)
|
|
||||||
parseMessage(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseMessage(message) {
|
|
||||||
log.debug "message is $message"
|
|
||||||
if (message.containsKey("volume")) {
|
|
||||||
log.debug "setting volume to ${message.volume}"
|
|
||||||
sendEvent(name: "level", value: message.volume)
|
|
||||||
}
|
|
||||||
if (message.containsKey("mute")) {
|
|
||||||
log.debug "setting mute to ${message.mute}"
|
|
||||||
sendEvent(name: "mute", value: message.mute)
|
|
||||||
}
|
|
||||||
if (message.containsKey("status")) {
|
|
||||||
log.debug "setting status to ${message.status}"
|
|
||||||
sendEvent(name: "status", value: message.status)
|
|
||||||
}
|
|
||||||
if (message.containsKey("trackData")) {
|
|
||||||
def json = new groovy.json.JsonBuilder(message.trackData)
|
|
||||||
log.debug "setting trackData to ${json.toString()}"
|
|
||||||
sendEvent(name: "trackData", value: json.toString())
|
|
||||||
}
|
|
||||||
if (message.containsKey("trackDescription")) {
|
|
||||||
log.debug "setting trackDescription info to ${message.trackDescription}"
|
|
||||||
sendEvent(name: "trackDescription", value: message.trackDescription)
|
|
||||||
}
|
|
||||||
if (message.containsKey("playlistData")) {
|
|
||||||
def json = new groovy.json.JsonBuilder(message.playlistData)
|
|
||||||
log.debug "setting playlistData to ${json.toString()}"
|
|
||||||
sendEvent(name: "playlistData", value: json.toString())
|
|
||||||
}
|
|
||||||
if (message.containsKey("playlistDescription")) {
|
|
||||||
log.debug "setting playlistDescription info to ${message.playlistDescription}"
|
|
||||||
sendEvent(name: "playlistDescription", value: message.playlistDescription)
|
|
||||||
}
|
|
||||||
if (message.containsKey("playlists")) {
|
|
||||||
def json = new groovy.json.JsonBuilder(message.playlists)
|
|
||||||
log.debug "setting playlists to ${json.toString()}"
|
|
||||||
sendEvent(name: "playlists",value: json.toString())
|
|
||||||
}
|
|
||||||
if (message.containsKey("speakers")) {
|
|
||||||
def json = new groovy.json.JsonBuilder(message.speakers)
|
|
||||||
log.debug "setting speakers to ${json.toString()}"
|
|
||||||
sendEvent(name: "speakers",value: json.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by service manager to send updates from device
|
|
||||||
def update(message) {
|
|
||||||
log.debug "update: $message"
|
|
||||||
parseMessage(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
// Refresh to get current state
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle commands
|
|
||||||
def refresh() {
|
|
||||||
log.debug "Executing 'refresh'"
|
|
||||||
getInfo("command=refresh")
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
log.debug "Executing 'on' (play)"
|
|
||||||
sendCommand("command=play")
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "Executing 'off' (pause)"
|
|
||||||
sendCommand("command=pause")
|
|
||||||
}
|
|
||||||
|
|
||||||
def play() {
|
|
||||||
log.debug "Executing 'play'"
|
|
||||||
sendCommand("command=play")
|
|
||||||
}
|
|
||||||
|
|
||||||
def pause() {
|
|
||||||
log.debug "Executing 'pause'"
|
|
||||||
sendCommand("command=pause")
|
|
||||||
}
|
|
||||||
|
|
||||||
def stop() {
|
|
||||||
log.debug "Executing 'stop'"
|
|
||||||
sendCommand("command=stop")
|
|
||||||
}
|
|
||||||
|
|
||||||
def nextTrack() {
|
|
||||||
log.debug "Executing 'nextTrack'"
|
|
||||||
sendCommand("command=next")
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
log.debug "Executing 'setLevel' to $value"
|
|
||||||
sendCommand("command=volume&level=$value")
|
|
||||||
}
|
|
||||||
|
|
||||||
// def playText(String msg) {
|
|
||||||
// log.debug "Executing 'playText'"
|
|
||||||
// sendCommand("say=$msg")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
def mute() {
|
|
||||||
log.debug "Executing 'mute'"
|
|
||||||
sendCommand("command=mute")
|
|
||||||
}
|
|
||||||
|
|
||||||
def previousTrack() {
|
|
||||||
log.debug "Executing 'previousTrack'"
|
|
||||||
sendCommand("command=previous")
|
|
||||||
}
|
|
||||||
|
|
||||||
def unmute() {
|
|
||||||
log.debug "Executing 'unmute'"
|
|
||||||
sendCommand("command=unmute")
|
|
||||||
}
|
|
||||||
|
|
||||||
def playPlaylist(String uri, speakers=null, volume=null) {
|
|
||||||
log.trace "playPlaylist($uri, $speakers, $volume, $resume, $restore)"
|
|
||||||
def command = "command=playlist&name=${uri}"
|
|
||||||
if (speakers) {
|
|
||||||
command += "&speakers=${speakers}"
|
|
||||||
}
|
|
||||||
if (volume) {
|
|
||||||
command += "&volume=${volume}"
|
|
||||||
}
|
|
||||||
sendCommand(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
def playTrack(String uri, speakers=null, volume=null, resume=false, restore=false, playlist=null) {
|
|
||||||
log.trace "playTrack($uri, $speakers, $volume, $resume, $restore, $playlist)"
|
|
||||||
def command = "command=track&url=${uri}"
|
|
||||||
if (speakers) {
|
|
||||||
command += "&speakers=${speakers}"
|
|
||||||
}
|
|
||||||
if (volume) {
|
|
||||||
command += "&volume=${volume}"
|
|
||||||
}
|
|
||||||
if (resume) {
|
|
||||||
command += "&resume="
|
|
||||||
}
|
|
||||||
else if (restore) {
|
|
||||||
command += "&restore="
|
|
||||||
}
|
|
||||||
if (playlist) {
|
|
||||||
command += "&playlist=$playlist"
|
|
||||||
}
|
|
||||||
sendCommand(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// def speak(text) {
|
|
||||||
// def url = textToSpeech(text)
|
|
||||||
// sendCommand("playTrack&track=${url.uri}&resume")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// def beep() {
|
|
||||||
// sendCommand("beep")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Private functions used internally
|
|
||||||
private Integer convertHexToInt(hex) {
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String convertHexToIP(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(":")
|
|
||||||
def ip = convertHexToIP(parts[0])
|
|
||||||
def port = convertHexToInt(parts[1])
|
|
||||||
return ip + ":" + port
|
|
||||||
}
|
|
||||||
|
|
||||||
private sendCommand(command) {
|
|
||||||
def path = "/player&" + command
|
|
||||||
|
|
||||||
def result = new physicalgraph.device.HubAction(
|
|
||||||
method: "POST",
|
|
||||||
path: path,
|
|
||||||
headers: [
|
|
||||||
HOST: getHostAddress()
|
|
||||||
],
|
|
||||||
)
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
private getInfo(command) {
|
|
||||||
def path = "/player&" + command
|
|
||||||
|
|
||||||
def result = new physicalgraph.device.HubAction(
|
|
||||||
method: "GET",
|
|
||||||
path: path,
|
|
||||||
headers: [
|
|
||||||
HOST: getHostAddress()
|
|
||||||
],
|
|
||||||
)
|
|
||||||
result
|
|
||||||
}
|
|
||||||
@@ -17,44 +17,50 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles {
|
tiles(scale: 2) {
|
||||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
state "unreachable", label: "?", action:"refresh.refresh", icon:"st.switches.light.off", backgroundColor:"#666666"
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
|
||||||
state "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
state "turningOn", label:'Turning on', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
state "turningOff", label:'Turning off', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
|
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
|
attributeState "level", action:"switch level.setLevel"
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||||
|
attributeState "color", action:"setColor"
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAttribute ("device.model", key: "SECONDARY_CONTROL") {
|
||||||
|
attributeState "model", label: '${currentValue}'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
|
||||||
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") {
|
valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:''
|
state "default", label:''
|
||||||
}
|
}
|
||||||
|
|
||||||
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
|
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 2, width: 4, inactiveLabel: false, range:"(2700..9000)") {
|
||||||
state "color", action:"setColor"
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
|
|
||||||
state "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
valueTile("level", "device.level", inactiveLabel: false, icon: "st.illuminance.illuminance.light", decoration: "flat") {
|
|
||||||
state "level", label: '${currentValue}%'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..9000)") {
|
|
||||||
state "colorTemp", action:"color temperature.setColorTemperature"
|
state "colorTemp", action:"color temperature.setColorTemperature"
|
||||||
}
|
}
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
|
|
||||||
|
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", height: 2, width: 2) {
|
||||||
state "colorTemp", label: '${currentValue}K'
|
state "colorTemp", label: '${currentValue}K'
|
||||||
}
|
}
|
||||||
|
|
||||||
main(["switch"])
|
main "switch"
|
||||||
details(["switch", "refresh", "level", "levelSliderControl", "rgbSelector", "colorTempSliderControl", "colorTemp"])
|
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +76,7 @@ def parse(String description) {
|
|||||||
def setHue(percentage) {
|
def setHue(percentage) {
|
||||||
log.debug "setHue ${percentage}"
|
log.debug "setHue ${percentage}"
|
||||||
parent.logErrors(logObject: log) {
|
parent.logErrors(logObject: log) {
|
||||||
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "hue:${percentage * 3.6}"])
|
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "hue:${percentage * 3.6}", power: "on"])
|
||||||
if (resp.status < 300) {
|
if (resp.status < 300) {
|
||||||
sendEvent(name: "hue", value: percentage)
|
sendEvent(name: "hue", value: percentage)
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
@@ -83,7 +89,7 @@ def setHue(percentage) {
|
|||||||
def setSaturation(percentage) {
|
def setSaturation(percentage) {
|
||||||
log.debug "setSaturation ${percentage}"
|
log.debug "setSaturation ${percentage}"
|
||||||
parent.logErrors(logObject: log) {
|
parent.logErrors(logObject: log) {
|
||||||
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "saturation:${percentage / 100}"])
|
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "saturation:${percentage / 100}", power: "on"])
|
||||||
if (resp.status < 300) {
|
if (resp.status < 300) {
|
||||||
sendEvent(name: "saturation", value: percentage)
|
sendEvent(name: "saturation", value: percentage)
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
@@ -114,7 +120,7 @@ def setColor(Map color) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
parent.logErrors(logObject:log) {
|
parent.logErrors(logObject:log) {
|
||||||
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: attrs.join(" ")])
|
def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"])
|
||||||
if (resp.status < 300) {
|
if (resp.status < 300) {
|
||||||
sendEvent(name: "color", value: color.hex)
|
sendEvent(name: "color", value: color.hex)
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
@@ -135,9 +141,10 @@ def setLevel(percentage) {
|
|||||||
return off() // if the brightness is set to 0, just turn it off
|
return off() // if the brightness is set to 0, just turn it off
|
||||||
}
|
}
|
||||||
parent.logErrors(logObject:log) {
|
parent.logErrors(logObject:log) {
|
||||||
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", ["color": "brightness:${percentage / 100}"])
|
def resp = parent.apiPUT("/lights/${selector()}/state", ["brightness": percentage / 100, "power": "on"])
|
||||||
if (resp.status < 300) {
|
if (resp.status < 300) {
|
||||||
sendEvent(name: "level", value: percentage)
|
sendEvent(name: "level", value: percentage)
|
||||||
|
sendEvent(name: "switch.setLevel", value: percentage)
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
} else {
|
} else {
|
||||||
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
||||||
@@ -148,7 +155,7 @@ def setLevel(percentage) {
|
|||||||
def setColorTemperature(kelvin) {
|
def setColorTemperature(kelvin) {
|
||||||
log.debug "Executing 'setColorTemperature' to ${kelvin}"
|
log.debug "Executing 'setColorTemperature' to ${kelvin}"
|
||||||
parent.logErrors() {
|
parent.logErrors() {
|
||||||
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "kelvin:${kelvin}"])
|
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "kelvin:${kelvin}", power: "on"])
|
||||||
if (resp.status < 300) {
|
if (resp.status < 300) {
|
||||||
sendEvent(name: "colorTemperature", value: kelvin)
|
sendEvent(name: "colorTemperature", value: kelvin)
|
||||||
sendEvent(name: "color", value: "#ffffff")
|
sendEvent(name: "color", value: "#ffffff")
|
||||||
@@ -163,7 +170,7 @@ def setColorTemperature(kelvin) {
|
|||||||
def on() {
|
def on() {
|
||||||
log.debug "Device setOn"
|
log.debug "Device setOn"
|
||||||
parent.logErrors() {
|
parent.logErrors() {
|
||||||
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "on"]) != null) {
|
if (parent.apiPUT("/lights/${selector()}/state", [power: "on"]) != null) {
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,7 +179,7 @@ def on() {
|
|||||||
def off() {
|
def off() {
|
||||||
log.debug "Device setOff"
|
log.debug "Device setOff"
|
||||||
parent.logErrors() {
|
parent.logErrors() {
|
||||||
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "off"]) != null) {
|
if (parent.apiPUT("/lights/${selector()}/state", [power: "off"]) != null) {
|
||||||
sendEvent(name: "switch", value: "off")
|
sendEvent(name: "switch", value: "off")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,19 +187,26 @@ def off() {
|
|||||||
|
|
||||||
def poll() {
|
def poll() {
|
||||||
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
|
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
|
||||||
def resp = parent.apiGET("/lights/${device.deviceNetworkId}")
|
def resp = parent.apiGET("/lights/${selector()}")
|
||||||
if (resp.status != 200) {
|
if (resp.status == 404) {
|
||||||
|
sendEvent(name: "switch", value: "unreachable")
|
||||||
|
return []
|
||||||
|
} else if (resp.status != 200) {
|
||||||
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
|
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
def data = resp.data
|
def data = resp.data[0]
|
||||||
|
log.debug("Data: ${data}")
|
||||||
|
|
||||||
sendEvent(name: "level", value: sprintf("%.1f", (data.brightness ?: 1) * 100))
|
sendEvent(name: "label", value: data.label)
|
||||||
|
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
|
||||||
|
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
|
||||||
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
|
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
|
||||||
sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int))
|
sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int))
|
||||||
sendEvent(name: "hue", value: data.color.hue / 3.6)
|
sendEvent(name: "hue", value: data.color.hue / 3.6)
|
||||||
sendEvent(name: "saturation", value: data.color.saturation * 100)
|
sendEvent(name: "saturation", value: data.color.saturation * 100)
|
||||||
sendEvent(name: "colorTemperature", value: data.color.kelvin)
|
sendEvent(name: "colorTemperature", value: data.color.kelvin)
|
||||||
|
sendEvent(name: "model", value: "${data.product.company} ${data.product.name}")
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -201,3 +215,11 @@ def refresh() {
|
|||||||
log.debug "Executing 'refresh'"
|
log.debug "Executing 'refresh'"
|
||||||
poll()
|
poll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def selector() {
|
||||||
|
if (device.deviceNetworkId.contains(":")) {
|
||||||
|
return device.deviceNetworkId
|
||||||
|
} else {
|
||||||
|
return "id:${device.deviceNetworkId}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,41 +16,44 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles {
|
tiles(scale: 2) {
|
||||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
state "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666"
|
||||||
state "turningOn", label:'Turning on', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
state "turningOff", label:'Turning off', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
state "unreachable", label: "?", action:"refresh.refresh", icon:"st.switches.light.off", backgroundColor:"#666666"
|
attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
|
attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
|
|
||||||
|
}
|
||||||
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
|
attributeState "level", action:"switch level.setLevel"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
|
||||||
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") {
|
valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:''
|
state "default", label:''
|
||||||
}
|
}
|
||||||
|
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
|
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 2, width: 4, inactiveLabel: false, range:"(2700..9000)") {
|
||||||
state "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
valueTile("level", "device.level", inactiveLabel: false, icon: "st.illuminance.illuminance.light", decoration: "flat") {
|
|
||||||
state "level", label: '${currentValue}%'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
|
|
||||||
state "colorTemp", action:"color temperature.setColorTemperature"
|
state "colorTemp", action:"color temperature.setColorTemperature"
|
||||||
}
|
}
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") {
|
|
||||||
|
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", height: 2, width: 2) {
|
||||||
state "colorTemp", label: '${currentValue}K'
|
state "colorTemp", label: '${currentValue}K'
|
||||||
}
|
}
|
||||||
|
|
||||||
main(["switch"])
|
main "switch"
|
||||||
details(["switch", "refresh", "level", "levelSliderControl", "colorTempSliderControl", "colorTemp"])
|
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -72,9 +75,10 @@ def setLevel(percentage) {
|
|||||||
return off() // if the brightness is set to 0, just turn it off
|
return off() // if the brightness is set to 0, just turn it off
|
||||||
}
|
}
|
||||||
parent.logErrors(logObject:log) {
|
parent.logErrors(logObject:log) {
|
||||||
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", ["color": "brightness:${percentage / 100}"])
|
def resp = parent.apiPUT("/lights/${selector()}/state", [brightness: percentage / 100, power: "on"])
|
||||||
if (resp.status < 300) {
|
if (resp.status < 300) {
|
||||||
sendEvent(name: "level", value: percentage)
|
sendEvent(name: "level", value: percentage)
|
||||||
|
sendEvent(name: "switch.setLevel", value: percentage)
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
} else {
|
} else {
|
||||||
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
||||||
@@ -85,7 +89,7 @@ def setLevel(percentage) {
|
|||||||
def setColorTemperature(kelvin) {
|
def setColorTemperature(kelvin) {
|
||||||
log.debug "Executing 'setColorTemperature' to ${kelvin}"
|
log.debug "Executing 'setColorTemperature' to ${kelvin}"
|
||||||
parent.logErrors() {
|
parent.logErrors() {
|
||||||
def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "kelvin:${kelvin}"])
|
def resp = parent.apiPUT("/lights/${selector()}/state", [color: "kelvin:${kelvin}", power: "on"])
|
||||||
if (resp.status < 300) {
|
if (resp.status < 300) {
|
||||||
sendEvent(name: "colorTemperature", value: kelvin)
|
sendEvent(name: "colorTemperature", value: kelvin)
|
||||||
sendEvent(name: "color", value: "#ffffff")
|
sendEvent(name: "color", value: "#ffffff")
|
||||||
@@ -100,7 +104,7 @@ def setColorTemperature(kelvin) {
|
|||||||
def on() {
|
def on() {
|
||||||
log.debug "Device setOn"
|
log.debug "Device setOn"
|
||||||
parent.logErrors() {
|
parent.logErrors() {
|
||||||
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "on"]) != null) {
|
if (parent.apiPUT("/lights/${selector()}/state", [power: "on"]) != null) {
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +113,7 @@ def on() {
|
|||||||
def off() {
|
def off() {
|
||||||
log.debug "Device setOff"
|
log.debug "Device setOff"
|
||||||
parent.logErrors() {
|
parent.logErrors() {
|
||||||
if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "off"]) != null) {
|
if (parent.apiPUT("/lights/${selector()}/state", [power: "off"]) != null) {
|
||||||
sendEvent(name: "switch", value: "off")
|
sendEvent(name: "switch", value: "off")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,16 +121,22 @@ def off() {
|
|||||||
|
|
||||||
def poll() {
|
def poll() {
|
||||||
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
|
log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}"
|
||||||
def resp = parent.apiGET("/lights/${device.deviceNetworkId}")
|
def resp = parent.apiGET("/lights/${selector()}")
|
||||||
if (resp.status != 200) {
|
if (resp.status == 404) {
|
||||||
|
sendEvent(name: "switch", value: "unreachable")
|
||||||
|
return []
|
||||||
|
} else if (resp.status != 200) {
|
||||||
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
|
log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}")
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
def data = resp.data
|
def data = resp.data[0]
|
||||||
|
|
||||||
sendEvent(name: "level", value: sprintf("%f", (data.brightness ?: 1) * 100))
|
sendEvent(name: "label", value: data.label)
|
||||||
|
sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100))
|
||||||
|
sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100))
|
||||||
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
|
sendEvent(name: "switch", value: data.connected ? data.power : "unreachable")
|
||||||
sendEvent(name: "colorTemperature", value: data.color.kelvin)
|
sendEvent(name: "colorTemperature", value: data.color.kelvin)
|
||||||
|
sendEvent(name: "model", value: data.product.name)
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -135,3 +145,11 @@ def refresh() {
|
|||||||
log.debug "Executing 'refresh'"
|
log.debug "Executing 'refresh'"
|
||||||
poll()
|
poll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def selector() {
|
||||||
|
if (device.deviceNetworkId.contains(":")) {
|
||||||
|
return device.deviceNetworkId
|
||||||
|
} else {
|
||||||
|
return "id:${device.deviceNetworkId}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
/**
|
|
||||||
* ObyThing Music Connect
|
|
||||||
*
|
|
||||||
* Copyright 2015 obycode
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
definition(
|
|
||||||
name: "obything Connect",
|
|
||||||
namespace: "com.obycode",
|
|
||||||
author: "obycode",
|
|
||||||
description: "Smart home, smart Mac. With obything.",
|
|
||||||
category: "Fun & Social",
|
|
||||||
iconUrl: "http://obything.obycode.com/icons/icon60.png",
|
|
||||||
iconX2Url: "http://obything.obycode.com/icons/icon120.png",
|
|
||||||
iconX3Url: "http://obything.obycode.com/icons/icon120.png",
|
|
||||||
oauth: [displayName: "obything", displayLink: "http://obything.obycode.com"])
|
|
||||||
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section("Smart home, smart Mac. With obything.") {
|
|
||||||
app(name: "childApps", appName: "obything Notify with Sound", namespace: "com.obycode", title: "Notify with Sound", multiple: true)
|
|
||||||
app(name: "childApps", appName: "obything Music Control", namespace: "com.obycode", title: "Music Control", multiple: true)
|
|
||||||
app(name: "childApps", appName: "obything Trigger Playlist", namespace: "com.obycode", title: "Trigger Playlists", multiple: true)
|
|
||||||
app(name: "childApps", appName: "obything Weather Forecast", namespace: "com.obycode", title: "Weather Forecast", multiple: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mappings {
|
|
||||||
path("/setup") {
|
|
||||||
action: [
|
|
||||||
POST: "setup",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/:uuid/:kind") {
|
|
||||||
action: [
|
|
||||||
POST: "createChild",
|
|
||||||
PUT: "updateChild",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
log.debug "Installed"
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated"
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
def uninstalled() {
|
|
||||||
logout()
|
|
||||||
}
|
|
||||||
|
|
||||||
def logout() {
|
|
||||||
revokeAccessToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapping handlers
|
|
||||||
|
|
||||||
// /setup POST
|
|
||||||
def setup() {
|
|
||||||
def ip = request.JSON?.ip
|
|
||||||
if (ip == null) {
|
|
||||||
return httpError(400, "IP not specified")
|
|
||||||
}
|
|
||||||
def port = request.JSON?.port
|
|
||||||
if (port == null) {
|
|
||||||
return httpError(400, "Port not specified")
|
|
||||||
}
|
|
||||||
def name = request.JSON?.name
|
|
||||||
if (name == null) {
|
|
||||||
return httpError(400, "Name not specified")
|
|
||||||
}
|
|
||||||
def uuid = request.JSON?.uuid
|
|
||||||
if (uuid == null) {
|
|
||||||
return httpError(400, "UUID not specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If machines is not initialized yet, set it up
|
|
||||||
if (state.machines == null) {
|
|
||||||
state.machines = [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this machine has already been initialized, just update it
|
|
||||||
if (state.machines[uuid]) {
|
|
||||||
state.machines[uuid]["ip"] = ip
|
|
||||||
state.machines[uuid]["port"] = port
|
|
||||||
state.machines[uuid]["name"] = name
|
|
||||||
log.debug "Updated machine"
|
|
||||||
|
|
||||||
def dead = []
|
|
||||||
// Update the child devices
|
|
||||||
def newHexIP = convertIPtoHex(ip)
|
|
||||||
state.machines[uuid]["children"].keySet().each {
|
|
||||||
def ids = state.machines[uuid]["children"][it]
|
|
||||||
def child = getChildDevice(ids.dni)
|
|
||||||
if (!child) {
|
|
||||||
dead.add(it)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Only change the IP; the label could've been manually changed and I'm
|
|
||||||
// not sure how to handle the port changing (its not allowed now anyway).
|
|
||||||
def oldHexPort = child.deviceNetworkId.split(':')[1]
|
|
||||||
def newDNI = "$newHexIP:$oldHexPort"
|
|
||||||
child.deviceNetworkId = newDNI
|
|
||||||
state.machines[uuid]["children"][it]["dni"] = newDNI
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dead.each {
|
|
||||||
state.machines[uuid]["children"].remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Otherwise, just create a new machine
|
|
||||||
else {
|
|
||||||
def machine = [ip:ip, port:port, name:name, children:[:]]
|
|
||||||
state.machines[uuid] = machine
|
|
||||||
log.debug "Added new machine"
|
|
||||||
}
|
|
||||||
|
|
||||||
sendCommand(state.machines[uuid], "/ping")
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeChildDevices(delete) {
|
|
||||||
delete.each {
|
|
||||||
deleteChildDevice(it.deviceNetworkId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /:uuid/:kind POST
|
|
||||||
def createChild() {
|
|
||||||
// Constants for the port offsets
|
|
||||||
final int iTunesService = 1
|
|
||||||
final int pandoraService = 2
|
|
||||||
final int spotifyService = 3
|
|
||||||
|
|
||||||
def machine = state.machines[params.uuid]
|
|
||||||
if (machine == null) {
|
|
||||||
return httpError(404, "Machine not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
def childName = machine["name"]
|
|
||||||
def portNum = machine["port"].toInteger()
|
|
||||||
switch (params.kind) {
|
|
||||||
case "itunes":
|
|
||||||
childName = childName + " iTunes"
|
|
||||||
portNum += iTunesService
|
|
||||||
break
|
|
||||||
case "pandora":
|
|
||||||
childName = childName + " Pandora"
|
|
||||||
portNum += pandoraService
|
|
||||||
break
|
|
||||||
case "spotify":
|
|
||||||
childName = childName + " Spotify"
|
|
||||||
portNum += spotifyService
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return httpError(400, "Unknown or unspecified device type")
|
|
||||||
}
|
|
||||||
|
|
||||||
def hexIP = convertIPtoHex(machine["ip"])
|
|
||||||
def hexPort = convertPortToHex(portNum.toString())
|
|
||||||
def childId = "$hexIP:$hexPort"
|
|
||||||
|
|
||||||
// If this child already exists, re-associate with it
|
|
||||||
def existing = getChildDevice(childId)
|
|
||||||
if (existing) {
|
|
||||||
log.debug "Found existing device: $existing"
|
|
||||||
state.machines[params.uuid]["children"][params.kind] = [id:existing.id, dni:childId]
|
|
||||||
}
|
|
||||||
// otherwise, create it
|
|
||||||
else {
|
|
||||||
def d = addChildDevice("com.obycode", "obything Music Player", childId, location.hubs[0].id, [name:"obything Music Player", label:childName, completedSetup:true])
|
|
||||||
log.debug "Created child device: $d"
|
|
||||||
state.machines[params.uuid]["children"][params.kind] = [id:d.id, dni:d.deviceNetworkId]
|
|
||||||
}
|
|
||||||
|
|
||||||
return [dni:childId]
|
|
||||||
}
|
|
||||||
|
|
||||||
// /:uuid/:kind PUT
|
|
||||||
def updateChild() {
|
|
||||||
def machine = state.machines[params.uuid]
|
|
||||||
if (machine == null) {
|
|
||||||
return httpError(404, "Machine not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
def child = machine["children"][params.kind]
|
|
||||||
if (child == null) {
|
|
||||||
return httpError(404, "Device not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
def device = getChildDevice(child.dni)
|
|
||||||
if (device == null) {
|
|
||||||
return httpError(404, "Device not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
device.update(request.JSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
private String convertIPtoHex(ipAddress) {
|
|
||||||
String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02X', it.toInteger() ) }.join()
|
|
||||||
return hex
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private String convertPortToHex(port) {
|
|
||||||
String hexport = port.toString().format( '%04X', port.toInteger() )
|
|
||||||
return hexport
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendCommand(machine, path, command = null) {
|
|
||||||
def fullPath = path
|
|
||||||
if (command) {
|
|
||||||
fullPath = fullPath + "?" + command
|
|
||||||
}
|
|
||||||
sendHubCommand(new physicalgraph.device.HubAction("GET " + fullPath + """ HTTP/1.1\r\nHOST: """ + machine["ip"] + ":" + machine["port"] + """\r\n\r\n""", physicalgraph.device.Protocol.LAN))
|
|
||||||
}
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
/**
|
|
||||||
* Music Control
|
|
||||||
*
|
|
||||||
* Author: obything
|
|
||||||
*
|
|
||||||
* Date: 2015-11-09
|
|
||||||
*/
|
|
||||||
definition(
|
|
||||||
name: "obything Music Control",
|
|
||||||
namespace: "com.obycode",
|
|
||||||
author: "obycode, based on Music Control by SmartThings",
|
|
||||||
description: "Play or pause your music when certain actions take place in your home.",
|
|
||||||
category: "Convenience",
|
|
||||||
iconUrl: "http://obything.obycode.com/icons/obything-device.png",
|
|
||||||
iconX2Url: "http://obything.obycode.com/icons/obything-device.png"
|
|
||||||
)
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
page(name: "mainPage", title: "Control your music when something happens", install: true, uninstall: true)
|
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
|
||||||
section {
|
|
||||||
input "starting", "time", title: "Starting", required: false
|
|
||||||
input "ending", "time", title: "Ending", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def mainPage() {
|
|
||||||
dynamicPage(name: "mainPage") {
|
|
||||||
def anythingSet = anythingSet()
|
|
||||||
if (anythingSet) {
|
|
||||||
section("When..."){
|
|
||||||
ifSet "motion", "capability.motionSensor", title: "Motion Here", 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 "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
|
|
||||||
ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", 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 "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 "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
|
|
||||||
ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", 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("Perform this action"){
|
|
||||||
input "actionType", "enum", title: "Action?", required: true, defaultValue: "play", options: [
|
|
||||||
"Play",
|
|
||||||
"Stop Playing",
|
|
||||||
"Toggle Play/Pause",
|
|
||||||
"Skip to Next Track",
|
|
||||||
"Play Previous Track"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
input "obything", "capability.musicPlayer", title: "obything Music player", required: true
|
|
||||||
}
|
|
||||||
section("More options", hideable: true, hidden: true) {
|
|
||||||
input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
|
|
||||||
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"]
|
|
||||||
if (settings.modes) {
|
|
||||||
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","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","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() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
subscribeToEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
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(mySwitch, "switch.on", eventHandler)
|
|
||||||
subscribe(mySwitchOff, "switch.off", eventHandler)
|
|
||||||
subscribe(arrivalPresence, "presence.present", eventHandler)
|
|
||||||
subscribe(departurePresence, "presence.not present", eventHandler)
|
|
||||||
subscribe(smoke, "smoke.detected", eventHandler)
|
|
||||||
subscribe(smoke, "smoke.tested", eventHandler)
|
|
||||||
subscribe(smoke, "carbonMonoxide.detected", eventHandler)
|
|
||||||
subscribe(water, "water.wet", 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) {
|
|
||||||
takeAction(evt)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.debug "Not taking action because $frequency minutes have not elapsed since last action"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
takeAction(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) {
|
|
||||||
takeAction(evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
private takeAction(evt) {
|
|
||||||
log.debug "takeAction($actionType)"
|
|
||||||
def options = [:]
|
|
||||||
if (volume) {
|
|
||||||
obything.setLevel(volume as Integer)
|
|
||||||
options.delay = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (actionType) {
|
|
||||||
case "Play":
|
|
||||||
obything.play()
|
|
||||||
break
|
|
||||||
case "Stop Playing":
|
|
||||||
obything.pause()
|
|
||||||
break
|
|
||||||
case "Toggle Play/Pause":
|
|
||||||
def currentStatus = obything.currentValue("status")
|
|
||||||
log.debug "Current status is $currentStatus"
|
|
||||||
if (currentStatus == "playing") {
|
|
||||||
obything.pause()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
obything.play()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "Skip to Next Track":
|
|
||||||
obything.nextTrack()
|
|
||||||
break
|
|
||||||
case "Play Previous Track":
|
|
||||||
obything.previousTrack()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
log.error "Action type '$actionType' not defined"
|
|
||||||
}
|
|
||||||
|
|
||||||
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") : ""
|
|
||||||
}
|
|
||||||
// TODO - End Centralize
|
|
||||||
@@ -1,410 +0,0 @@
|
|||||||
/**
|
|
||||||
* obything Notify with Sound
|
|
||||||
*
|
|
||||||
* Author: obycode
|
|
||||||
* Date: 2015-08-30
|
|
||||||
*/
|
|
||||||
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
|
|
||||||
definition(
|
|
||||||
name: "obything Notify with Sound",
|
|
||||||
namespace: "com.obycode",
|
|
||||||
author: "obycode, based on 'Sonos Notify with Sound' by SmartThings",
|
|
||||||
description: "Play a sound or custom message through your Mac with obything when the mode changes or other events occur.",
|
|
||||||
category: "Convenience",
|
|
||||||
iconUrl: "http://obything.obycode.com/icons/obything-device.png",
|
|
||||||
iconX2Url: "http://obything.obycode.com/icons/obything-device.png",
|
|
||||||
parent: "com.obycode:obything Connect"
|
|
||||||
)
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
page(name: "mainPage", title: "Play a message over your speakers when something happens", install: true, uninstall: true)
|
|
||||||
page(name: "chooseTrack", title: "Select a playlist")
|
|
||||||
page(name: "chooseSpeakers", title: "Select speakers")
|
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
|
||||||
section {
|
|
||||||
input "starting", "time", title: "Starting", required: false
|
|
||||||
input "ending", "time", title: "Ending", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def mainPage() {
|
|
||||||
dynamicPage(name: "mainPage") {
|
|
||||||
def anythingSet = anythingSet()
|
|
||||||
if (anythingSet) {
|
|
||||||
section("Play message when"){
|
|
||||||
ifSet "motion", "capability.motionSensor", title: "Motion Here", 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 "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
|
|
||||||
ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def hideable = anythingSet || app.installationState == "COMPLETE"
|
|
||||||
def sectionTitle = anythingSet ? "Select additional triggers" : "Play message when..."
|
|
||||||
|
|
||||||
section(sectionTitle, hideable: hideable, hidden: true){
|
|
||||||
ifUnset "motion", "capability.motionSensor", title: "Motion Here", 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 "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
|
|
||||||
ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", 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", description: "Select mode(s)", required: false, multiple: true
|
|
||||||
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
|
|
||||||
}
|
|
||||||
section{
|
|
||||||
input "actionType", "enum", title: "Action?", required: true, defaultValue: "Custom Message", options: [
|
|
||||||
"Custom Message",
|
|
||||||
"Custom URL",
|
|
||||||
"Bell 1",
|
|
||||||
"Bell 2",
|
|
||||||
"Dogs Barking",
|
|
||||||
"Fire Alarm",
|
|
||||||
"The mail has arrived",
|
|
||||||
"A door opened",
|
|
||||||
"There is motion",
|
|
||||||
"Smartthings detected a flood",
|
|
||||||
"Smartthings detected smoke",
|
|
||||||
"Someone is arriving",
|
|
||||||
"Piano",
|
|
||||||
"Lightsaber"]
|
|
||||||
input "message","text",title:"Play this message", required:false, multiple: false
|
|
||||||
input "url","text",title:"Play a sound at this URL", required:false, multiple: false
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
input "obything", "capability.musicPlayer", title: "On this obything iTunes device", required: true
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
href "chooseSpeakers", title: "With these speakers", description: speakers ? speakers : "Tap to set", state: speakers ? "complete" : "incomplete"
|
|
||||||
}
|
|
||||||
section("More options", hideable: true, hidden: true) {
|
|
||||||
input "resumePlaying", "bool", title: "Resume currently playing music after notification", required: false, defaultValue: true
|
|
||||||
href "chooseTrack", title: "Or play this music or radio station", description: playlist ? playlist : "Tap to set", state: playlist ? "complete" : "incomplete"
|
|
||||||
|
|
||||||
input "volume", "number", title: "Temporarily change volume", description: "0-100%", required: false
|
|
||||||
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"]
|
|
||||||
if (settings.modes) {
|
|
||||||
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)", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def chooseTrack() {
|
|
||||||
dynamicPage(name: "chooseTrack") {
|
|
||||||
section{
|
|
||||||
input "playlist", "enum", title:"Play this playlist", required:true, multiple: false, options: playlistOptions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private playlistOptions() {
|
|
||||||
def playlistString = obything.currentValue("playlists")
|
|
||||||
log.debug "Playlists are $playlistString"
|
|
||||||
def jsonList = new JsonSlurper().parseText(playlistString)
|
|
||||||
jsonList.collect {
|
|
||||||
it.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def chooseSpeakers() {
|
|
||||||
dynamicPage(name: "chooseSpeakers") {
|
|
||||||
section{
|
|
||||||
input "speakers", "enum", title:"Play on these speakers", required:false, multiple: true, options: speakerOptions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private speakerOptions() {
|
|
||||||
def speakersString = obything.currentValue("speakers")
|
|
||||||
log.debug "Speakers are $speakersString"
|
|
||||||
def slurper = new JsonSlurper()
|
|
||||||
slurper.parseText(speakersString)
|
|
||||||
}
|
|
||||||
|
|
||||||
private anythingSet() {
|
|
||||||
for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","timeOfDay","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() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
subscribeToEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
unsubscribe()
|
|
||||||
unschedule()
|
|
||||||
subscribeToEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def 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(mySwitch, "switch.on", eventHandler)
|
|
||||||
subscribe(mySwitchOff, "switch.off", eventHandler)
|
|
||||||
subscribe(arrivalPresence, "presence.present", eventHandler)
|
|
||||||
subscribe(departurePresence, "presence.not present", eventHandler)
|
|
||||||
subscribe(smoke, "smoke.detected", eventHandler)
|
|
||||||
subscribe(smoke, "smoke.tested", eventHandler)
|
|
||||||
subscribe(smoke, "carbonMonoxide.detected", eventHandler)
|
|
||||||
subscribe(water, "water.wet", eventHandler)
|
|
||||||
subscribe(button1, "button.pushed", eventHandler)
|
|
||||||
|
|
||||||
if (triggerModes) {
|
|
||||||
subscribe(location, modeChangeHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeOfDay) {
|
|
||||||
runDaily(timeOfDay, scheduledTimeHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
loadText()
|
|
||||||
}
|
|
||||||
|
|
||||||
def eventHandler(evt) {
|
|
||||||
log.trace "eventHandler($evt?.name: $evt?.value)"
|
|
||||||
if (allOk) {
|
|
||||||
log.trace "allOk"
|
|
||||||
def lastTime = state[frequencyKey(evt)]
|
|
||||||
if (oncePerDayOk(lastTime)) {
|
|
||||||
if (frequency) {
|
|
||||||
if (lastTime == null || now() - lastTime >= frequency * 60000) {
|
|
||||||
takeAction(evt)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.debug "Not taking action because $frequency minutes have not elapsed since last action"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
takeAction(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) {
|
|
||||||
takeAction(evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
private takeAction(evt) {
|
|
||||||
log.trace "takeAction()"
|
|
||||||
|
|
||||||
def speakerString
|
|
||||||
if (speakers) {
|
|
||||||
speakerString = ""
|
|
||||||
speakers.each {
|
|
||||||
speakerString += "\"$it\","
|
|
||||||
}
|
|
||||||
// remove the last comma and encode
|
|
||||||
speakerString = encode(speakerString[0..-2])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playlist) {
|
|
||||||
obything.playTrack(state.sound.uri, speakerString, volume, resumePlaying, playlist)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
obything.playTrack(state.sound.uri, speakerString, volume, resumePlaying)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frequency || oncePerDay) {
|
|
||||||
state[frequencyKey(evt)] = now()
|
|
||||||
}
|
|
||||||
log.trace "Exiting takeAction()"
|
|
||||||
}
|
|
||||||
|
|
||||||
private frequencyKey(evt) {
|
|
||||||
"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 getTimeLabel()
|
|
||||||
{
|
|
||||||
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
|
|
||||||
}
|
|
||||||
// TODO - End Centralize
|
|
||||||
|
|
||||||
private loadText() {
|
|
||||||
switch ( actionType) {
|
|
||||||
case "Bell 1":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/bell1.mp3", duration: "10"]
|
|
||||||
break;
|
|
||||||
case "Bell 2":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/bell2.mp3", duration: "10"]
|
|
||||||
break;
|
|
||||||
case "Dogs Barking":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/dogs.mp3", duration: "10"]
|
|
||||||
break;
|
|
||||||
case "Fire Alarm":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/alarm.mp3", duration: "17"]
|
|
||||||
break;
|
|
||||||
case "The mail has arrived":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/the+mail+has+arrived.mp3", duration: "1"]
|
|
||||||
break;
|
|
||||||
case "A door opened":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/a+door+opened.mp3", duration: "1"]
|
|
||||||
break;
|
|
||||||
case "There is motion":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/there+is+motion.mp3", duration: "1"]
|
|
||||||
break;
|
|
||||||
case "Smartthings detected a flood":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/smartthings+detected+a+flood.mp3", duration: "2"]
|
|
||||||
break;
|
|
||||||
case "Smartthings detected smoke":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/smartthings+detected+smoke.mp3", duration: "1"]
|
|
||||||
break;
|
|
||||||
case "Someone is arriving":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/someone+is+arriving.mp3", duration: "1"]
|
|
||||||
break;
|
|
||||||
case "Piano":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/piano2.mp3", duration: "10"]
|
|
||||||
break;
|
|
||||||
case "Lightsaber":
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/lightsaber.mp3", duration: "10"]
|
|
||||||
break;
|
|
||||||
case "Custom Message":
|
|
||||||
if (message) {
|
|
||||||
log.debug "message is $message"
|
|
||||||
state.sound = textToSpeech(message) // instanceof List ? message[0] : message) // not sure why this is (sometimes) needed)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
state.sound = textToSpeech("You selected the custom message option but did not enter a message in the $app.label Smart App")
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Custom URL":
|
|
||||||
if (url) {
|
|
||||||
state.sound = [uri: url, duration: "0"]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
state.sound = textToSpeech("You selected the custom URL option but did not enter a URL in the $app.label Smart App")
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
log.debug "Invalid selection."
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
/**
|
|
||||||
* obything Trigger Playlist
|
|
||||||
*
|
|
||||||
* Author: obycode
|
|
||||||
* Date: 2015-11-05
|
|
||||||
*/
|
|
||||||
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
|
|
||||||
definition(
|
|
||||||
name: "obything Trigger Playlist",
|
|
||||||
namespace: "com.obycode",
|
|
||||||
author: "obycode, based on Sonos Mood Music by SmartThings",
|
|
||||||
description: "Plays a selected playlist on your Mac with obything",
|
|
||||||
category: "Convenience",
|
|
||||||
iconUrl: "http://obything.obycode.com/icons/obything-device.png",
|
|
||||||
iconX2Url: "http://obything.obycode.com/icons/obything-device.png"
|
|
||||||
)
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
page(name: "mainPage", title: "Choose the trigger(s)", nextPage: "chooseTrackAndSpeakers", uninstall: true)
|
|
||||||
page(name: "chooseTrackAndSpeakers", title: "Choose the playlist and speaker(s)", install: true, uninstall: true)
|
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
|
||||||
section {
|
|
||||||
input "starting", "time", title: "Starting", required: false
|
|
||||||
input "ending", "time", title: "Ending", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def mainPage() {
|
|
||||||
dynamicPage(name: "mainPage") {
|
|
||||||
def anythingSet = anythingSet()
|
|
||||||
if (anythingSet) {
|
|
||||||
section("Play music when..."){
|
|
||||||
ifSet "motion", "capability.motionSensor", title: "Motion Here", 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 "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
|
|
||||||
ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
|
|
||||||
ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true
|
|
||||||
ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
|
|
||||||
ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def hideable = anythingSet || app.installationState == "COMPLETE"
|
|
||||||
def sectionTitle = anythingSet ? "Select additional triggers" : "Play music when..."
|
|
||||||
|
|
||||||
section(sectionTitle, hideable: hideable, hidden: true){
|
|
||||||
ifUnset "motion", "capability.motionSensor", title: "Motion Here", 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 "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
|
|
||||||
ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", required: false, multiple: true
|
|
||||||
ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true
|
|
||||||
ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
|
|
||||||
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
input "obything", "capability.musicPlayer", title: "On this obything music player", required: true
|
|
||||||
}
|
|
||||||
section("More options", hideable: true, hidden: true) {
|
|
||||||
input "volume", "number", title: "Set the volume", description: "0-100%", required: false
|
|
||||||
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: ["Sunday","Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
|
||||||
if (settings.modes) {
|
|
||||||
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
|
|
||||||
}
|
|
||||||
input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def chooseTrackAndSpeakers() {
|
|
||||||
dynamicPage(name: "chooseTrackAndSpeakers") {
|
|
||||||
section {
|
|
||||||
input "playlist", "enum", title:"Play this playlist", required:true, multiple: false, options: playlistOptions()
|
|
||||||
input "speakers", "enum", title:"On these speakers", required:false, multiple: true, options: speakerOptions()
|
|
||||||
}
|
|
||||||
section([mobileOnly:true]) {
|
|
||||||
label title: "Assign a name", required: false
|
|
||||||
mode title: "Set for specific mode(s)", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private playlistOptions() {
|
|
||||||
def playlistString = obything.currentValue("playlists")
|
|
||||||
log.debug "Playlists are $playlistString"
|
|
||||||
def jsonList = new JsonSlurper().parseText(playlistString)
|
|
||||||
log.debug("jsonList is $jsonList")
|
|
||||||
jsonList.collect {
|
|
||||||
it.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def chooseSpeakers() {
|
|
||||||
dynamicPage(name: "chooseSpeakers") {
|
|
||||||
section{
|
|
||||||
input "speakers", "enum", title:"Play on these speakers", required:false, multiple: true, options: speakerOptions()
|
|
||||||
}
|
|
||||||
section([mobileOnly:true]) {
|
|
||||||
label title: "Assign a name", required: false
|
|
||||||
mode title: "Set for specific mode(s)", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private speakerOptions() {
|
|
||||||
def speakersString = obything.currentValue("speakers")
|
|
||||||
log.debug "Speakers are $speakersString"
|
|
||||||
def slurper = new JsonSlurper()
|
|
||||||
slurper.parseText(speakersString)
|
|
||||||
}
|
|
||||||
|
|
||||||
private anythingSet() {
|
|
||||||
for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","timeOfDay","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() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
subscribeToEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
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(mySwitch, "switch.on", eventHandler)
|
|
||||||
subscribe(mySwitchOff, "switch.off", eventHandler)
|
|
||||||
subscribe(arrivalPresence, "presence.present", eventHandler)
|
|
||||||
subscribe(departurePresence, "presence.not present", eventHandler)
|
|
||||||
subscribe(smoke, "smoke.detected", eventHandler)
|
|
||||||
subscribe(smoke, "smoke.tested", eventHandler)
|
|
||||||
subscribe(smoke, "carbonMonoxide.detected", eventHandler)
|
|
||||||
subscribe(water, "water.wet", eventHandler)
|
|
||||||
subscribe(button1, "button.pushed", eventHandler)
|
|
||||||
|
|
||||||
if (triggerModes) {
|
|
||||||
subscribe(location, modeChangeHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeOfDay) {
|
|
||||||
runDaily(timeOfDay, scheduledTimeHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def eventHandler(evt) {
|
|
||||||
log.debug "In eventHandler"
|
|
||||||
if (allOk) {
|
|
||||||
if (frequency) {
|
|
||||||
def lastTime = state[frequencyKey(evt)]
|
|
||||||
if (lastTime == null || now() - lastTime >= frequency * 60000) {
|
|
||||||
takeAction(evt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
takeAction(evt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
takeAction(evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
private takeAction(evt) {
|
|
||||||
log.info "Playing '$playlist'"
|
|
||||||
def speakerString
|
|
||||||
if (speakers) {
|
|
||||||
speakerString = ""
|
|
||||||
speakers.each {
|
|
||||||
speakerString += "\"$it\","
|
|
||||||
}
|
|
||||||
// remove the last comma and encode
|
|
||||||
speakerString = encode(speakerString[0..-2])
|
|
||||||
}
|
|
||||||
obything.playPlaylist(encode(playlist), speakerString, volume)
|
|
||||||
|
|
||||||
if (frequency || oncePerDay) {
|
|
||||||
state[frequencyKey(evt)] = now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private frequencyKey(evt) {
|
|
||||||
"lastActionTimeStamp"
|
|
||||||
}
|
|
||||||
|
|
||||||
private encode(text) {
|
|
||||||
return URLEncoder.encode(text).replaceAll("\\+", "%20")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = 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") : ""
|
|
||||||
}
|
|
||||||
@@ -1,405 +0,0 @@
|
|||||||
/**
|
|
||||||
* obything Weather Forecast
|
|
||||||
*
|
|
||||||
* Author: obycode
|
|
||||||
* Date: 2014-10-13
|
|
||||||
*/
|
|
||||||
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
|
|
||||||
definition(
|
|
||||||
name: "obything Weather Forecast",
|
|
||||||
namespace: "com.obycode",
|
|
||||||
author: "obycode, based on Sonos Weather Forecast by SmartThings",
|
|
||||||
description: "Play a weather report through your Mac or AirPlay speakers when the mode changes or other events occur",
|
|
||||||
category: "Convenience",
|
|
||||||
iconUrl: "http://obything.obycode.com/icons/obything-device.png",
|
|
||||||
iconX2Url: "http://obything.obycode.com/icons/obything-device.png"
|
|
||||||
)
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
page(name: "mainPage", title: "Play the weather report on your AirPlay speakers", install: true, uninstall: true)
|
|
||||||
page(name: "chooseTrack", title: "Select a playlist")
|
|
||||||
page(name: "chooseSpeakers", title: "Select speakers")
|
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
|
||||||
section {
|
|
||||||
input "starting", "time", title: "Starting", required: false
|
|
||||||
input "ending", "time", title: "Ending", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def mainPage() {
|
|
||||||
dynamicPage(name: "mainPage") {
|
|
||||||
def anythingSet = anythingSet()
|
|
||||||
if (anythingSet) {
|
|
||||||
section("Play weather report when"){
|
|
||||||
ifSet "motion", "capability.motionSensor", title: "Motion Here", 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 "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
|
|
||||||
ifSet "water", "capability.waterSensor", title: "Water Sensor Wet", 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def hideable = anythingSet || app.installationState == "COMPLETE"
|
|
||||||
def sectionTitle = anythingSet ? "Select additional triggers" : "Play weather report when..."
|
|
||||||
|
|
||||||
section(sectionTitle, hideable: hideable, hidden: true){
|
|
||||||
ifUnset "motion", "capability.motionSensor", title: "Motion Here", 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 "smoke", "capability.smokeDetector", title: "Smoke Detected", required: false, multiple: true
|
|
||||||
ifUnset "water", "capability.waterSensor", title: "Water Sensor Wet", 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 {
|
|
||||||
input("forecastOptions", "enum", defaultValue: "0", title: "Weather report options", description: "Select one or more", multiple: true,
|
|
||||||
options: [
|
|
||||||
["0": "Current Conditions"],
|
|
||||||
["1": "Today's Forecast"],
|
|
||||||
["2": "Tonight's Forecast"],
|
|
||||||
["3": "Tomorrow's Forecast"],
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
input "obything", "capability.musicPlayer", title: "On this obything iTunes device", required: true
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
href "chooseSpeakers", title: "With these speakers", description: speakers ? speakers : "Tap to set", state: speakers ? "complete" : "incomplete"
|
|
||||||
}
|
|
||||||
section("More options", hideable: true, hidden: true) {
|
|
||||||
input "resumePlaying", "bool", title: "Resume currently playing music after weather report finishes", required: false, defaultValue: true
|
|
||||||
href "choosePlaylist", title: "Or play this playlist", description: playlist ? playlist : "Tap to set", state: playlist ? "complete" : "incomplete"
|
|
||||||
|
|
||||||
input "zipCode", "text", title: "Zip Code", required: false
|
|
||||||
input "volume", "number", title: "Temporarily change volume", description: "0-100%", required: false
|
|
||||||
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"]
|
|
||||||
if (settings.modes) {
|
|
||||||
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)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def chooseTrack() {
|
|
||||||
dynamicPage(name: "chooseTrack") {
|
|
||||||
section{
|
|
||||||
input "playlist", "enum", title:"Play this playlist", required:true, multiple: false, options: playlistOptions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def chooseSpeakers() {
|
|
||||||
dynamicPage(name: "chooseSpeakers") {
|
|
||||||
section{
|
|
||||||
input "speakers", "enum", title:"Play on these speakers", required:false, multiple: true, options: speakerOptions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private speakerOptions() {
|
|
||||||
def speakersString = obything.currentValue("speakers")
|
|
||||||
log.debug "Speakers are $speakersString"
|
|
||||||
def slurper = new JsonSlurper()
|
|
||||||
slurper.parseText(speakersString)
|
|
||||||
}
|
|
||||||
|
|
||||||
private anythingSet() {
|
|
||||||
for (name in ["motion","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","smoke","water","button1","timeOfDay","triggerModes"]) {
|
|
||||||
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() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
subscribeToEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
unsubscribe()
|
|
||||||
unschedule()
|
|
||||||
subscribeToEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def 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(mySwitch, "switch.on", eventHandler)
|
|
||||||
subscribe(mySwitchOff, "switch.off", eventHandler)
|
|
||||||
subscribe(arrivalPresence, "presence.present", eventHandler)
|
|
||||||
subscribe(departurePresence, "presence.not present", eventHandler)
|
|
||||||
subscribe(smoke, "smoke.detected", eventHandler)
|
|
||||||
subscribe(smoke, "smoke.tested", eventHandler)
|
|
||||||
subscribe(smoke, "carbonMonoxide.detected", eventHandler)
|
|
||||||
subscribe(water, "water.wet", eventHandler)
|
|
||||||
subscribe(button1, "button.pushed", eventHandler)
|
|
||||||
|
|
||||||
if (triggerModes) {
|
|
||||||
subscribe(location,modeChangeHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeOfDay) {
|
|
||||||
runDaily(timeOfDay, scheduledTimeHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def eventHandler(evt) {
|
|
||||||
log.trace "eventHandler($evt?.name: $evt?.value)"
|
|
||||||
if (allOk) {
|
|
||||||
log.trace "allOk"
|
|
||||||
def lastTime = state[frequencyKey(evt)]
|
|
||||||
if (oncePerDayOk(lastTime)) {
|
|
||||||
if (frequency) {
|
|
||||||
if (lastTime == null || now() - lastTime >= frequency * 60000) {
|
|
||||||
takeAction(evt)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.debug "Not taking action because $frequency minutes have not elapsed since last action"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
takeAction(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) {
|
|
||||||
takeAction(evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
private takeAction(evt) {
|
|
||||||
|
|
||||||
loadText()
|
|
||||||
|
|
||||||
def speakerString
|
|
||||||
if (speakers) {
|
|
||||||
speakerString = ""
|
|
||||||
speakers.each {
|
|
||||||
speakerString += "$it,"
|
|
||||||
}
|
|
||||||
// remove the last comma and encode
|
|
||||||
speakerString = encode(speakerString[0..-2])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playlist) {
|
|
||||||
obything.playTrack(state.sound.uri, speakerString, volume, resumePlaying, playlist)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
obything.playTrack(state.sound.uri, speakerString, volume, resumePlaying)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frequency || oncePerDay) {
|
|
||||||
state[frequencyKey(evt)] = now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private playlistOptions() {
|
|
||||||
def playlistString = obything.currentValue("playlists")
|
|
||||||
log.debug "Playlists are $playlistString"
|
|
||||||
def jsonList = new JsonSlurper().parseText(playlistString)
|
|
||||||
jsonList.collect {
|
|
||||||
it.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private frequencyKey(evt) {
|
|
||||||
"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 getTimeLabel()
|
|
||||||
{
|
|
||||||
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
|
|
||||||
}
|
|
||||||
// TODO - End Centralize
|
|
||||||
|
|
||||||
private loadText() {
|
|
||||||
if (location.timeZone || zipCode) {
|
|
||||||
def weather = getWeatherFeature("forecast", zipCode)
|
|
||||||
def current = getWeatherFeature("conditions", zipCode)
|
|
||||||
def isMetric = location.temperatureScale == "C"
|
|
||||||
def delim = ""
|
|
||||||
def sb = new StringBuilder()
|
|
||||||
list(forecastOptions).sort().each {opt ->
|
|
||||||
if (opt == "0") {
|
|
||||||
if (isMetric) {
|
|
||||||
sb << "The current temperature is ${Math.round(current.current_observation.temp_c)} degrees."
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sb << "The current temperature is ${Math.round(current.current_observation.temp_f)} degrees."
|
|
||||||
}
|
|
||||||
delim = " "
|
|
||||||
}
|
|
||||||
else if (opt == "1") {
|
|
||||||
sb << delim
|
|
||||||
sb << "Today's forecast is "
|
|
||||||
if (isMetric) {
|
|
||||||
sb << weather.forecast.txt_forecast.forecastday[0].fcttext_metric
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sb << weather.forecast.txt_forecast.forecastday[0].fcttext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (opt == "2") {
|
|
||||||
sb << delim
|
|
||||||
sb << "Tonight will be "
|
|
||||||
if (isMetric) {
|
|
||||||
sb << weather.forecast.txt_forecast.forecastday[1].fcttext_metric
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sb << weather.forecast.txt_forecast.forecastday[1].fcttext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (opt == "3") {
|
|
||||||
sb << delim
|
|
||||||
sb << "Tomorrow will be "
|
|
||||||
if (isMetric) {
|
|
||||||
sb << weather.forecast.txt_forecast.forecastday[2].fcttext_metric
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sb << weather.forecast.txt_forecast.forecastday[2].fcttext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def msg = sb.toString()
|
|
||||||
msg = msg.replaceAll(/([0-9]+)C/,'$1 degrees') // TODO - remove after next release
|
|
||||||
log.debug "msg = ${msg}"
|
|
||||||
state.sound = textToSpeech(msg, true)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
state.sound = textToSpeech("Please set the location of your hub with the SmartThings mobile app, or enter a zip code to receive weather forecasts.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private list(String s) {
|
|
||||||
[s]
|
|
||||||
}
|
|
||||||
private list(l) {
|
|
||||||
l
|
|
||||||
}
|
|
||||||
@@ -5,23 +5,23 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
definition(
|
definition(
|
||||||
name: "LIFX (Connect)",
|
name: "LIFX (Connect)",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
author: "LIFX",
|
author: "LIFX",
|
||||||
description: "Allows you to use LIFX smart light bulbs with SmartThings.",
|
description: "Allows you to use LIFX smart light bulbs with SmartThings.",
|
||||||
category: "Convenience",
|
category: "Convenience",
|
||||||
iconUrl: "https://cloud.lifx.com/images/lifx.png",
|
iconUrl: "https://cloud.lifx.com/images/lifx.png",
|
||||||
iconX2Url: "https://cloud.lifx.com/images/lifx.png",
|
iconX2Url: "https://cloud.lifx.com/images/lifx.png",
|
||||||
iconX3Url: "https://cloud.lifx.com/images/lifx.png",
|
iconX3Url: "https://cloud.lifx.com/images/lifx.png",
|
||||||
oauth: true,
|
oauth: true,
|
||||||
singleInstance: true) {
|
singleInstance: true) {
|
||||||
appSetting "clientId"
|
appSetting "clientId"
|
||||||
appSetting "clientSecret"
|
appSetting "clientSecret"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
page(name: "Credentials", title: "LIFX", content: "authPage", install: false)
|
page(name: "Credentials", title: "LIFX", content: "authPage", install: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
mappings {
|
mappings {
|
||||||
@@ -33,29 +33,29 @@ mappings {
|
|||||||
path("/test") { action: [ GET: "oauthSuccess" ] }
|
path("/test") { action: [ GET: "oauthSuccess" ] }
|
||||||
}
|
}
|
||||||
|
|
||||||
def getServerUrl() { return "https://graph.api.smartthings.com" }
|
def getServerUrl() { return "https://graph.api.smartthings.com" }
|
||||||
def apiURL(path = '/') { return "https://api.lifx.com/v1beta1${path}" }
|
def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/callback"}
|
||||||
def buildRedirectUrl(page) {
|
def apiURL(path = '/') { return "https://api.lifx.com/v1${path}" }
|
||||||
return "${serverUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/${page}"
|
def getSecretKey() { return appSettings.secretKey }
|
||||||
}
|
def getClientId() { return appSettings.clientId }
|
||||||
|
|
||||||
def authPage() {
|
def authPage() {
|
||||||
log.debug "authPage"
|
log.debug "authPage test1"
|
||||||
if (!state.lifxAccessToken) {
|
if (!state.lifxAccessToken) {
|
||||||
log.debug "no LIFX access token"
|
log.debug "no LIFX access token"
|
||||||
// This is the SmartThings access token
|
// This is the SmartThings access token
|
||||||
if (!state.accessToken) {
|
if (!state.accessToken) {
|
||||||
log.debug "no access token, create access token"
|
log.debug "no access token, create access token"
|
||||||
createAccessToken() // predefined method
|
state.accessToken = createAccessToken() // predefined method
|
||||||
}
|
}
|
||||||
def description = "Tap to enter LIFX credentials"
|
def description = "Tap to enter LIFX credentials"
|
||||||
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}" // this triggers oauthInit() below
|
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below
|
||||||
log.debug "app id: ${app.id}"
|
// def redirectUrl = "${apiServerUrl}"
|
||||||
|
log.debug "app id: ${app.id}"
|
||||||
log.debug "redirect url: ${redirectUrl}"
|
log.debug "redirect url: ${redirectUrl}"
|
||||||
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:false) {
|
return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) {
|
||||||
section {
|
section {
|
||||||
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
|
href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account")
|
||||||
// href(url:buildRedirectUrl("test"), title: "Message test")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -63,17 +63,15 @@ def authPage() {
|
|||||||
|
|
||||||
def options = locationOptions() ?: []
|
def options = locationOptions() ?: []
|
||||||
def count = options.size()
|
def count = options.size()
|
||||||
def refreshInterval = 3
|
|
||||||
|
|
||||||
return dynamicPage(name:"Credentials", title:"Select devices...", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) {
|
||||||
section("Select your location") {
|
section("Select your location") {
|
||||||
input "selectedLocationId", "enum", required:true, title:"Select location (${count} found)", multiple:false, options:options
|
input "selectedLocationId", "enum", required:true, title:"Select location (${count} found)", multiple:false, options:options, submitOnChange: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// OAuth
|
// OAuth
|
||||||
|
|
||||||
def oauthInit() {
|
def oauthInit() {
|
||||||
@@ -112,7 +110,7 @@ def oauthCallback() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def oauthReceiveToken(redirectUrl = null) {
|
def oauthReceiveToken(redirectUrl = null) {
|
||||||
|
// Not sure what redirectUrl is for
|
||||||
log.debug "receiveToken - params: ${params}"
|
log.debug "receiveToken - params: ${params}"
|
||||||
def oauthParams = [ client_id: "${appSettings.clientId}", client_secret: "${appSettings.clientSecret}", grant_type: "authorization_code", code: params.code, scope: params.scope ] // how is params.code valid here?
|
def oauthParams = [ client_id: "${appSettings.clientId}", client_secret: "${appSettings.clientSecret}", grant_type: "authorization_code", code: params.code, scope: params.scope ] // how is params.code valid here?
|
||||||
def params = [
|
def params = [
|
||||||
@@ -135,25 +133,25 @@ def oauthReceiveToken(redirectUrl = null) {
|
|||||||
|
|
||||||
def oauthSuccess() {
|
def oauthSuccess() {
|
||||||
def message = """
|
def message = """
|
||||||
<p>Your LIFX Account is now connected to SmartThings!</p>
|
<p>Your LIFX Account is now connected to SmartThings!</p>
|
||||||
<p>Click 'Done' to finish setup.</p>
|
<p>Click 'Done' to finish setup.</p>
|
||||||
"""
|
"""
|
||||||
oauthConnectionStatus(message)
|
oauthConnectionStatus(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
def oauthFailure() {
|
def oauthFailure() {
|
||||||
def message = """
|
def message = """
|
||||||
<p>The connection could not be established!</p>
|
<p>The connection could not be established!</p>
|
||||||
<p>Click 'Done' to return to the menu.</p>
|
<p>Click 'Done' to return to the menu.</p>
|
||||||
"""
|
"""
|
||||||
oauthConnectionStatus(message)
|
oauthConnectionStatus(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
def oauthReceivedToken() {
|
def oauthReceivedToken() {
|
||||||
def message = """
|
def message = """
|
||||||
<p>Your LIFX Account is already connected to SmartThings!</p>
|
<p>Your LIFX Account is already connected to SmartThings!</p>
|
||||||
<p>Click 'Done' to finish setup.</p>
|
<p>Click 'Done' to finish setup.</p>
|
||||||
"""
|
"""
|
||||||
oauthConnectionStatus(message)
|
oauthConnectionStatus(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,74 +159,74 @@ def oauthConnectionStatus(message, redirectUrl = null) {
|
|||||||
def redirectHtml = ""
|
def redirectHtml = ""
|
||||||
if (redirectUrl) {
|
if (redirectUrl) {
|
||||||
redirectHtml = """
|
redirectHtml = """
|
||||||
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
|
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
def html = """
|
def html = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
<title>SmartThings Connection</title>
|
<title>SmartThings Connection</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Swiss 721 W01 Thin';
|
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');
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
|
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.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.ttf') format('truetype'),
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Swiss 721 W01 Light';
|
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');
|
||||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
|
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.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.ttf') format('truetype'),
|
||||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
width: 280;
|
width: 280;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
img:nth-child(2) {
|
img:nth-child(2) {
|
||||||
margin: 0 15px;
|
margin: 0 15px;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
font-family: 'Swiss 721 W01 Thin';
|
font-family: 'Swiss 721 W01 Thin';
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
span {
|
span {
|
||||||
font-family: 'Swiss 721 W01 Light';
|
font-family: 'Swiss 721 W01 Light';
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
${redirectHtml}
|
${redirectHtml}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<img src='https://cloud.lifx.com/images/lifx.png' alt='LIFX icon' width='100'/>
|
<img src='https://cloud.lifx.com/images/lifx.png' alt='LIFX icon' width='100'/>
|
||||||
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png' alt='connected device icon' width="40"/>
|
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png' alt='connected device icon' width="40"/>
|
||||||
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png' alt='SmartThings logo' width="100"/>
|
<img src='https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png' alt='SmartThings logo' width="100"/>
|
||||||
<p>
|
<p>
|
||||||
${message}
|
${message}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
render contentType: 'text/html', data: html
|
render contentType: 'text/html', data: html
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +237,6 @@ String toQueryString(Map m) {
|
|||||||
// App lifecycle hooks
|
// App lifecycle hooks
|
||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
enableCallback() // wtf does this do?
|
|
||||||
if (!state.accessToken) {
|
if (!state.accessToken) {
|
||||||
createAccessToken()
|
createAccessToken()
|
||||||
} else {
|
} else {
|
||||||
@@ -251,7 +248,6 @@ def installed() {
|
|||||||
|
|
||||||
// called after settings are changed
|
// called after settings are changed
|
||||||
def updated() {
|
def updated() {
|
||||||
enableCallback() // not sure what this does
|
|
||||||
if (!state.accessToken) {
|
if (!state.accessToken) {
|
||||||
createAccessToken()
|
createAccessToken()
|
||||||
} else {
|
} else {
|
||||||
@@ -305,27 +301,36 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) {
|
|||||||
state.remove("lifxAccessToken")
|
state.remove("lifxAccessToken")
|
||||||
options.logObject.warn "Access token is not valid"
|
options.logObject.warn "Access token is not valid"
|
||||||
}
|
}
|
||||||
return options.errerReturn
|
return options.errorReturn
|
||||||
} catch (java.net.SocketTimeoutException e) {
|
} catch (java.net.SocketTimeoutException e) {
|
||||||
options.logObject.warn "Connection timed out, not much we can do here"
|
options.logObject.warn "Connection timed out, not much we can do here"
|
||||||
return options.errerReturn
|
return options.errorReturn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def apiGET(path) {
|
def apiGET(path) {
|
||||||
httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response ->
|
try {
|
||||||
logResponse(response)
|
httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response ->
|
||||||
return response
|
logResponse(response)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
} catch (groovyx.net.http.HttpResponseException e) {
|
||||||
|
logResponse(e.response)
|
||||||
|
return e.response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def apiPUT(path, body = [:]) {
|
def apiPUT(path, body = [:]) {
|
||||||
log.debug("Beginning API PUT: ${path}, ${body}")
|
try {
|
||||||
httpPutJson(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders(), ) {response ->
|
log.debug("Beginning API PUT: ${path}, ${body}")
|
||||||
logResponse(response)
|
httpPutJson(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders(), ) {response ->
|
||||||
return response
|
logResponse(response)
|
||||||
}
|
return response
|
||||||
}
|
}
|
||||||
|
} catch (groovyx.net.http.HttpResponseException e) {
|
||||||
|
logResponse(e.response)
|
||||||
|
return e.response
|
||||||
|
}}
|
||||||
|
|
||||||
def devicesList(selector = '') {
|
def devicesList(selector = '') {
|
||||||
logErrors([]) {
|
logErrors([]) {
|
||||||
@@ -340,12 +345,12 @@ def devicesList(selector = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Map locationOptions() {
|
Map locationOptions() {
|
||||||
|
|
||||||
def options = [:]
|
def options = [:]
|
||||||
def devices = devicesList()
|
def devices = devicesList()
|
||||||
devices.each { device ->
|
devices.each { device ->
|
||||||
options[device.location.id] = device.location.name
|
options[device.location.id] = device.location.name
|
||||||
}
|
}
|
||||||
|
log.debug("Locations: ${options}")
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,28 +364,32 @@ def updateDevices() {
|
|||||||
state.devices = [:]
|
state.devices = [:]
|
||||||
}
|
}
|
||||||
def devices = devicesInLocation()
|
def devices = devicesInLocation()
|
||||||
def deviceIds = devices*.id
|
def selectors = []
|
||||||
|
|
||||||
|
log.debug("All selectors: ${selectors}")
|
||||||
|
|
||||||
devices.each { device ->
|
devices.each { device ->
|
||||||
def childDevice = getChildDevice(device.id)
|
def childDevice = getChildDevice(device.id)
|
||||||
|
selectors.add("${device.id}")
|
||||||
if (!childDevice) {
|
if (!childDevice) {
|
||||||
log.info("Adding device ${device.id}: ${device.capabilities}")
|
log.info("Adding device ${device.id}: ${device.product}")
|
||||||
def data = [
|
def data = [
|
||||||
label: device.label,
|
label: device.label,
|
||||||
level: sprintf("%f", (device.brightness ?: 1) * 100),
|
level: Math.round((device.brightness ?: 1) * 100),
|
||||||
switch: device.connected ? device.power : "unreachable",
|
switch: device.connected ? device.power : "unreachable",
|
||||||
colorTemperature: device.color.kelvin
|
colorTemperature: device.color.kelvin
|
||||||
]
|
]
|
||||||
if (device.capabilities.has_color) {
|
if (device.product.capabilities.has_color) {
|
||||||
data["color"] = colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int)
|
data["color"] = colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int)
|
||||||
data["hue"] = device.color.hue / 3.6
|
data["hue"] = device.color.hue / 3.6
|
||||||
data["saturation"] = device.color.saturation * 100
|
data["saturation"] = device.color.saturation * 100
|
||||||
childDevice = addChildDevice("smartthings", "LIFX Color Bulb", device.id, null, data)
|
childDevice = addChildDevice(app.namespace, "LIFX Color Bulb", device.id, null, data)
|
||||||
} else {
|
} else {
|
||||||
childDevice = addChildDevice("smartthings", "LIFX White Bulb", device.id, null, data)
|
childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getChildDevices().findAll { !deviceIds.contains(it.deviceNetworkId) }.each {
|
getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each {
|
||||||
log.info("Deleting ${it.deviceNetworkId}")
|
log.info("Deleting ${it.deviceNetworkId}")
|
||||||
deleteChildDevice(it.deviceNetworkId)
|
deleteChildDevice(it.deviceNetworkId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,24 +10,24 @@
|
|||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
* 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.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
* Sonos Control
|
* Speaker Control
|
||||||
*
|
*
|
||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
*
|
*
|
||||||
* Date: 2013-12-10
|
* Date: 2013-12-10
|
||||||
*/
|
*/
|
||||||
definition(
|
definition(
|
||||||
name: "Sonos Control",
|
name: "Speaker Control",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
author: "SmartThings",
|
author: "SmartThings",
|
||||||
description: "Play or pause your Sonos when certain actions take place in your home.",
|
description: "Play or pause your Speaker when certain actions take place in your home.",
|
||||||
category: "SmartThings Labs",
|
category: "SmartThings Labs",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
page(name: "mainPage", title: "Control your Sonos when something happens", install: true, uninstall: true)
|
page(name: "mainPage", title: "Control your Speaker when something happens", install: true, uninstall: true)
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
||||||
section {
|
section {
|
||||||
input "starting", "time", title: "Starting", required: false
|
input "starting", "time", title: "Starting", required: false
|
||||||
@@ -81,7 +81,7 @@ def mainPage() {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input "sonos", "capability.musicPlayer", title: "Sonos music player", required: true
|
input "sonos", "capability.musicPlayer", title: "Speaker music player", required: true
|
||||||
}
|
}
|
||||||
section("More options", hideable: true, hidden: true) {
|
section("More options", hideable: true, hidden: true) {
|
||||||
input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
|
input "volume", "number", title: "Set the volume volume", description: "0-100%", required: false
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
* 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.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
* Sonos Mood Music
|
* Speaker Mood Music
|
||||||
*
|
*
|
||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
* Date: 2014-02-12
|
* Date: 2014-02-12
|
||||||
@@ -65,7 +65,7 @@ private saveSelectedSong() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
definition(
|
definition(
|
||||||
name: "Sonos Mood Music",
|
name: "Speaker Mood Music",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
author: "SmartThings",
|
author: "SmartThings",
|
||||||
description: "Plays a selected song or station.",
|
description: "Plays a selected song or station.",
|
||||||
@@ -75,7 +75,7 @@ definition(
|
|||||||
)
|
)
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
page(name: "mainPage", title: "Play a selected song or station on your Sonos when something happens", nextPage: "chooseTrack", uninstall: true)
|
page(name: "mainPage", title: "Play a selected song or station on your Speaker when something happens", nextPage: "chooseTrack", uninstall: true)
|
||||||
page(name: "chooseTrack", title: "Select a song", install: true)
|
page(name: "chooseTrack", title: "Select a song", install: true)
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
||||||
section {
|
section {
|
||||||
@@ -125,7 +125,7 @@ def mainPage() {
|
|||||||
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
|
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
|
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
|
||||||
}
|
}
|
||||||
section("More options", hideable: true, hidden: true) {
|
section("More options", hideable: true, hidden: true) {
|
||||||
input "volume", "number", title: "Set the volume", description: "0-100%", required: false
|
input "volume", "number", title: "Set the volume", description: "0-100%", required: false
|
||||||
@@ -10,23 +10,23 @@
|
|||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
* 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.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
* Sonos Custom Message
|
* Speaker Custom Message
|
||||||
*
|
*
|
||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
* Date: 2014-1-29
|
* Date: 2014-1-29
|
||||||
*/
|
*/
|
||||||
definition(
|
definition(
|
||||||
name: "Sonos Notify with Sound",
|
name: "Speaker Notify with Sound",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
author: "SmartThings",
|
author: "SmartThings",
|
||||||
description: "Play a sound or custom message through your Sonos when the mode changes or other events occur.",
|
description: "Play a sound or custom message through your Speaker when the mode changes or other events occur.",
|
||||||
category: "SmartThings Labs",
|
category: "SmartThings Labs",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
page(name: "mainPage", title: "Play a message on your Sonos when something happens", install: true, uninstall: true)
|
page(name: "mainPage", title: "Play a message on your Speaker when something happens", install: true, uninstall: true)
|
||||||
page(name: "chooseTrack", title: "Select a song or station")
|
page(name: "chooseTrack", title: "Select a song or station")
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
||||||
section {
|
section {
|
||||||
@@ -92,7 +92,7 @@ def mainPage() {
|
|||||||
input "message","text",title:"Play this message", required:false, multiple: false
|
input "message","text",title:"Play this message", required:false, multiple: false
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
|
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
|
||||||
}
|
}
|
||||||
section("More options", hideable: true, hidden: true) {
|
section("More options", hideable: true, hidden: true) {
|
||||||
input "resumePlaying", "bool", title: "Resume currently playing music after notification", required: false, defaultValue: true
|
input "resumePlaying", "bool", title: "Resume currently playing music after notification", required: false, defaultValue: true
|
||||||
@@ -10,23 +10,23 @@
|
|||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
* 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.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
* Sonos Weather Forecast
|
* Speaker Weather Forecast
|
||||||
*
|
*
|
||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
* Date: 2014-1-29
|
* Date: 2014-1-29
|
||||||
*/
|
*/
|
||||||
definition(
|
definition(
|
||||||
name: "Sonos Weather Forecast",
|
name: "Speaker Weather Forecast",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
author: "SmartThings",
|
author: "SmartThings",
|
||||||
description: "Play a weather report through your Sonos when the mode changes or other events occur",
|
description: "Play a weather report through your Speaker when the mode changes or other events occur",
|
||||||
category: "SmartThings Labs",
|
category: "SmartThings Labs",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos.png",
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/sonos@2x.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
page(name: "mainPage", title: "Play the weather report on your sonos", install: true, uninstall: true)
|
page(name: "mainPage", title: "Play the weather report on your speaker", install: true, uninstall: true)
|
||||||
page(name: "chooseTrack", title: "Select a song or station")
|
page(name: "chooseTrack", title: "Select a song or station")
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
page(name: "timeIntervalInput", title: "Only during a certain time") {
|
||||||
section {
|
section {
|
||||||
@@ -85,7 +85,7 @@ def mainPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input "sonos", "capability.musicPlayer", title: "On this Sonos player", required: true
|
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
|
||||||
}
|
}
|
||||||
section("More options", hideable: true, hidden: true) {
|
section("More options", hideable: true, hidden: true) {
|
||||||
input "resumePlaying", "bool", title: "Resume currently playing music after weather report finishes", required: false, defaultValue: true
|
input "resumePlaying", "bool", title: "Resume currently playing music after weather report finishes", required: false, defaultValue: true
|
||||||
Reference in New Issue
Block a user