mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 05:31:56 +00:00
MSA-685: This is version 2.0 of what used to be "ObyThing Music Player". This new version adds support for additional music players (Spotify and Pandora in addition to iTunes) and will continue to expand with other functionality.
This version uses OAuth to simplify the setup process. It also uses child apps to make it easy for users to find the apps that work well with the devices. Please email me when you are testing and I can send a beta version of the Mac app to the tester(s).
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* 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
|
||||
@@ -0,0 +1,410 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* 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") : ""
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
Reference in New Issue
Block a user