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:
obycode
2015-11-15 08:41:10 -06:00
parent 8b6942525d
commit 01f13ed4a5
6 changed files with 1941 additions and 0 deletions

View File

@@ -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
}