Files
SmartThingsPublic/smartapps/com-obycode/obything-connect.src/obything-connect.groovy
obycode 01f13ed4a5 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).
2015-11-15 08:41:10 -06:00

212 lines
5.8 KiB
Groovy

/**
* 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))
}