mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 05:31:56 +00:00
Fixed formatting
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Bose SoundTouch
|
* Bose SoundTouch
|
||||||
*
|
*
|
||||||
* Copyright 2015 Henric Andersson
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
@@ -13,54 +13,54 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Needed to be able to serialize the XmlSlurper data back to XML
|
// Needed to be able to serialize the XmlSlurper data back to XML
|
||||||
import groovy.xml.XmlUtil
|
import groovy.xml.XmlUtil
|
||||||
|
|
||||||
// for the UI
|
// for the UI
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Bose SoundTouch", namespace: "smartthings", author: "Henric.Andersson@smartthings.com") {
|
definition (name: "Bose SoundTouch", namespace: "smartthings", author: "SmartThings") {
|
||||||
/**
|
/**
|
||||||
* List our capabilties. Doing so adds predefined command(s) which
|
* List our capabilties. Doing so adds predefined command(s) which
|
||||||
* belong to the capability.
|
* belong to the capability.
|
||||||
*/
|
*/
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Music Player"
|
capability "Music Player"
|
||||||
capability "Polling"
|
capability "Polling"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define all commands, ie, if you have a custom action not
|
* Define all commands, ie, if you have a custom action not
|
||||||
* covered by a capability, you NEED to define it here or
|
* covered by a capability, you NEED to define it here or
|
||||||
* the call will not be made.
|
* the call will not be made.
|
||||||
*
|
*
|
||||||
* To call a capability function, just prefix it with the name
|
* To call a capability function, just prefix it with the name
|
||||||
* of the capability, for example, refresh would be "refresh.refresh"
|
* of the capability, for example, refresh would be "refresh.refresh"
|
||||||
*/
|
*/
|
||||||
command "preset1"
|
command "preset1"
|
||||||
command "preset2"
|
command "preset2"
|
||||||
command "preset3"
|
command "preset3"
|
||||||
command "preset4"
|
command "preset4"
|
||||||
command "preset5"
|
command "preset5"
|
||||||
command "preset6"
|
command "preset6"
|
||||||
command "aux"
|
command "aux"
|
||||||
|
|
||||||
command "everywhereJoin"
|
|
||||||
command "everywhereLeave"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
command "everywhereJoin"
|
||||||
|
command "everywhereLeave"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* Define the various tiles and the states that they can be in.
|
* Define the various tiles and the states that they can be in.
|
||||||
* The 2nd parameter defines an event which the tile listens to,
|
* The 2nd parameter defines an event which the tile listens to,
|
||||||
* if received, it tries to map it to a state.
|
* if received, it tries to map it to a state.
|
||||||
*
|
*
|
||||||
* You can also use ${currentValue} for the value of the event
|
* You can also use ${currentValue} for the value of the event
|
||||||
* or ${name} for the name of the event. Just make SURE to use
|
* or ${name} for the name of the event. Just make SURE to use
|
||||||
* single quotes, otherwise it will only be interpreted at time of
|
* single quotes, otherwise it will only be interpreted at time of
|
||||||
* launch, instead of every time the event triggers.
|
* launch, instead of every time the event triggers.
|
||||||
*/
|
*/
|
||||||
valueTile("nowplaying", "device.nowplaying", width: 2, height: 1, decoration:"flat") {
|
valueTile("nowplaying", "device.nowplaying", width: 2, height: 1, decoration:"flat") {
|
||||||
state "nowplaying", label:'${currentValue}', action:"refresh.refresh"
|
state "nowplaying", label:'${currentValue}', action:"refresh.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
||||||
@@ -88,18 +88,18 @@ metadata {
|
|||||||
valueTile("aux", "device.switch", decoration: "flat", canChangeIcon: false) {
|
valueTile("aux", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||||
state "default", label:'Auxillary\nInput', action:"aux"
|
state "default", label:'Auxillary\nInput', action:"aux"
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("refresh", "device.nowplaying", decoration: "flat", canChangeIcon: false) {
|
standardTile("refresh", "device.nowplaying", decoration: "flat", canChangeIcon: false) {
|
||||||
state "default", label:'', action:"refresh", icon:"st.secondary.refresh"
|
state "default", label:'', action:"refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
controlTile("volume", "device.volume", "slider", height:1, width:3, range:"(0..100)") {
|
controlTile("volume", "device.volume", "slider", height:1, width:3, range:"(0..100)") {
|
||||||
state "volume", action:"music Player.setLevel"
|
state "volume", action:"music Player.setLevel"
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("playpause", "device.playpause", decoration: "flat") {
|
standardTile("playpause", "device.playpause", decoration: "flat") {
|
||||||
state "pause", label:'', icon:'st.sonos.play-btn', action:'music Player.play'
|
state "pause", label:'', icon:'st.sonos.play-btn', action:'music Player.play'
|
||||||
state "play", label:'', icon:'st.sonos.pause-btn', action:'music Player.pause'
|
state "play", label:'', icon:'st.sonos.pause-btn', action:'music Player.pause'
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("prev", "device.switch", decoration: "flat", canChangeIcon: false) {
|
standardTile("prev", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||||
@@ -110,28 +110,28 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
valueTile("everywhere", "device.everywhere", width:2, height:1, decoration:"flat") {
|
valueTile("everywhere", "device.everywhere", width:2, height:1, decoration:"flat") {
|
||||||
state "join", label:"Join\nEverywhere", action:"everywhereJoin"
|
state "join", label:"Join\nEverywhere", action:"everywhereJoin"
|
||||||
state "leave", label:"Leave\nEverywhere", action:"everywhereLeave"
|
state "leave", label:"Leave\nEverywhere", action:"everywhereLeave"
|
||||||
// Final state is used if the device is in a state where joining is not possible
|
// Final state is used if the device is in a state where joining is not possible
|
||||||
state "unavailable", label:"Not Available"
|
state "unavailable", label:"Not Available"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defines which tile to show in the overview
|
// Defines which tile to show in the overview
|
||||||
main "switch"
|
main "switch"
|
||||||
|
|
||||||
// Defines which tile(s) to show when user opens the detailed view
|
// Defines which tile(s) to show when user opens the detailed view
|
||||||
details ([
|
details ([
|
||||||
"nowplaying", "refresh", // Row 1 (112)
|
"nowplaying", "refresh", // Row 1 (112)
|
||||||
"prev", "playpause", "next", // Row 2 (123)
|
"prev", "playpause", "next", // Row 2 (123)
|
||||||
"volume", // Row 3 (111)
|
"volume", // Row 3 (111)
|
||||||
"1", "2", "3", // Row 4 (123)
|
"1", "2", "3", // Row 4 (123)
|
||||||
"4", "5", "6", // Row 5 (123)
|
"4", "5", "6", // Row 5 (123)
|
||||||
"aux", "everywhere"]) // Row 6 (122)
|
"aux", "everywhere"]) // Row 6 (122)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************************************************************
|
/**************************************************************************
|
||||||
* The following section simply maps the actions as defined in
|
* The following section simply maps the actions as defined in
|
||||||
* the metadata into onAction() calls.
|
* the metadata into onAction() calls.
|
||||||
*
|
*
|
||||||
* This is preferred since some actions can be dealt with more
|
* This is preferred since some actions can be dealt with more
|
||||||
* efficiently this way. Also keeps all user interaction code in
|
* efficiently this way. Also keeps all user interaction code in
|
||||||
@@ -164,7 +164,7 @@ def everywhereLeave() { onAction("eleave") }
|
|||||||
/**
|
/**
|
||||||
* Main point of interaction with things.
|
* Main point of interaction with things.
|
||||||
* This function is called by SmartThings Cloud with the resulting data from
|
* This function is called by SmartThings Cloud with the resulting data from
|
||||||
* any action (see HubAction()).
|
* any action (see HubAction()).
|
||||||
*
|
*
|
||||||
* Conversely, to execute any actions, you need to return them as a single
|
* Conversely, to execute any actions, you need to return them as a single
|
||||||
* item or a list (flattened).
|
* item or a list (flattened).
|
||||||
@@ -176,38 +176,38 @@ def everywhereLeave() { onAction("eleave") }
|
|||||||
def parse(String event) {
|
def parse(String event) {
|
||||||
def data = parseLanMessage(event)
|
def data = parseLanMessage(event)
|
||||||
def actions = []
|
def actions = []
|
||||||
|
|
||||||
// List of permanent root node handlers
|
// List of permanent root node handlers
|
||||||
def handlers = [
|
def handlers = [
|
||||||
"nowPlaying" : "boseParseNowPlaying",
|
"nowPlaying" : "boseParseNowPlaying",
|
||||||
"volume" : "boseParseVolume",
|
"volume" : "boseParseVolume",
|
||||||
"presets" : "boseParsePresets",
|
"presets" : "boseParsePresets",
|
||||||
"zone" : "boseParseEverywhere",
|
"zone" : "boseParseEverywhere",
|
||||||
]
|
]
|
||||||
|
|
||||||
// No need to deal with non-XML data
|
// No need to deal with non-XML data
|
||||||
if (!data.headers || !data.headers?."content-type".contains("xml"))
|
if (!data.headers || !data.headers?."content-type".contains("xml"))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
// Move any pending callbacks into ready state
|
// Move any pending callbacks into ready state
|
||||||
prepareCallbacks()
|
prepareCallbacks()
|
||||||
|
|
||||||
def xml = new XmlSlurper().parseText(data.body)
|
def xml = new XmlSlurper().parseText(data.body)
|
||||||
// Let each parser take a stab at it
|
// Let each parser take a stab at it
|
||||||
handlers.each { node,func ->
|
handlers.each { node,func ->
|
||||||
if (xml.name() == node)
|
if (xml.name() == node)
|
||||||
actions << "$func"(xml)
|
actions << "$func"(xml)
|
||||||
}
|
}
|
||||||
// If we have callbacks waiting for this...
|
// If we have callbacks waiting for this...
|
||||||
actions << processCallbacks(xml)
|
actions << processCallbacks(xml)
|
||||||
|
|
||||||
// Be nice and helpful
|
|
||||||
if (actions.size() == 0) {
|
|
||||||
log.warn "parse(): Unhandled data = " + lan
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue new actions
|
// Be nice and helpful
|
||||||
|
if (actions.size() == 0) {
|
||||||
|
log.warn "parse(): Unhandled data = " + lan
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue new actions
|
||||||
return actions.flatten()
|
return actions.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,21 +231,21 @@ def installed() {
|
|||||||
def onAction(String user, data=null) {
|
def onAction(String user, data=null) {
|
||||||
log.info "onAction(${user})"
|
log.info "onAction(${user})"
|
||||||
|
|
||||||
// Keep IP address current (since device may have changed)
|
// Keep IP address current (since device may have changed)
|
||||||
state.address = parent.resolveDNI2Address(device.deviceNetworkId)
|
state.address = parent.resolveDNI2Address(device.deviceNetworkId)
|
||||||
|
|
||||||
// Process action
|
// Process action
|
||||||
def actions = null
|
def actions = null
|
||||||
switch (user) {
|
switch (user) {
|
||||||
case "on":
|
case "on":
|
||||||
actions = boseSetPowerState(true)
|
actions = boseSetPowerState(true)
|
||||||
break
|
break
|
||||||
case "off":
|
case "off":
|
||||||
boseSetNowPlaying(null, "STANDBY")
|
boseSetNowPlaying(null, "STANDBY")
|
||||||
actions = boseSetPowerState(false)
|
actions = boseSetPowerState(false)
|
||||||
break
|
break
|
||||||
case "volume":
|
case "volume":
|
||||||
actions = boseSetVolume(data)
|
actions = boseSetVolume(data)
|
||||||
break
|
break
|
||||||
case "aux":
|
case "aux":
|
||||||
boseSetNowPlaying(null, "AUX")
|
boseSetNowPlaying(null, "AUX")
|
||||||
@@ -257,43 +257,43 @@ def onAction(String user, data=null) {
|
|||||||
case "4":
|
case "4":
|
||||||
case "5":
|
case "5":
|
||||||
case "6":
|
case "6":
|
||||||
actions = boseSetInput(user)
|
actions = boseSetInput(user)
|
||||||
break
|
break
|
||||||
case "refresh":
|
case "refresh":
|
||||||
boseSetNowPlaying(null, "REFRESH")
|
boseSetNowPlaying(null, "REFRESH")
|
||||||
actions = [boseRefreshNowPlaying(), boseGetPresets(), boseGetVolume(), boseGetEverywhereState()]
|
actions = [boseRefreshNowPlaying(), boseGetPresets(), boseGetVolume(), boseGetEverywhereState()]
|
||||||
break
|
break
|
||||||
case "play":
|
case "play":
|
||||||
actions = [boseSetPlayMode(true), boseRefreshNowPlaying()]
|
actions = [boseSetPlayMode(true), boseRefreshNowPlaying()]
|
||||||
break
|
break
|
||||||
case "pause":
|
case "pause":
|
||||||
actions = [boseSetPlayMode(false), boseRefreshNowPlaying()]
|
actions = [boseSetPlayMode(false), boseRefreshNowPlaying()]
|
||||||
break
|
break
|
||||||
case "previous":
|
case "previous":
|
||||||
actions = [boseChangeTrack(-1), boseRefreshNowPlaying()]
|
actions = [boseChangeTrack(-1), boseRefreshNowPlaying()]
|
||||||
break
|
break
|
||||||
case "next":
|
case "next":
|
||||||
actions = [boseChangeTrack(1), boseRefreshNowPlaying()]
|
actions = [boseChangeTrack(1), boseRefreshNowPlaying()]
|
||||||
break
|
break
|
||||||
case "mute":
|
case "mute":
|
||||||
actions = boseSetMute(true)
|
actions = boseSetMute(true)
|
||||||
break
|
break
|
||||||
case "unmute":
|
case "unmute":
|
||||||
actions = boseSetMute(false)
|
actions = boseSetMute(false)
|
||||||
break
|
break
|
||||||
case "ejoin":
|
case "ejoin":
|
||||||
actions = boseZoneJoin()
|
actions = boseZoneJoin()
|
||||||
break
|
break
|
||||||
case "eleave":
|
case "eleave":
|
||||||
actions = boseZoneLeave()
|
actions = boseZoneLeave()
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
log.error "Unhandled action: " + user
|
log.error "Unhandled action: " + user
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we don't have nested lists
|
// Make sure we don't have nested lists
|
||||||
if (actions instanceof List)
|
if (actions instanceof List)
|
||||||
return actions.flatten()
|
return actions.flatten()
|
||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,16 +309,16 @@ def poll() {
|
|||||||
* Joins this speaker into the everywhere zone
|
* Joins this speaker into the everywhere zone
|
||||||
*/
|
*/
|
||||||
def boseZoneJoin() {
|
def boseZoneJoin() {
|
||||||
def results = []
|
def results = []
|
||||||
def posts = parent.boseZoneJoin(this)
|
def posts = parent.boseZoneJoin(this)
|
||||||
|
|
||||||
for (post in posts) {
|
for (post in posts) {
|
||||||
if (post['endpoint'])
|
if (post['endpoint'])
|
||||||
results << bosePOST(post['endpoint'], post['body'], post['host'])
|
results << bosePOST(post['endpoint'], post['body'], post['host'])
|
||||||
}
|
}
|
||||||
sendEvent(name:"everywhere", value:"leave")
|
sendEvent(name:"everywhere", value:"leave")
|
||||||
results << boseRefreshNowPlaying()
|
results << boseRefreshNowPlaying()
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,15 +326,15 @@ def boseZoneJoin() {
|
|||||||
* Removes this speaker from the everywhere zone
|
* Removes this speaker from the everywhere zone
|
||||||
*/
|
*/
|
||||||
def boseZoneLeave() {
|
def boseZoneLeave() {
|
||||||
def results = []
|
def results = []
|
||||||
def posts = parent.boseZoneLeave(this)
|
def posts = parent.boseZoneLeave(this)
|
||||||
|
|
||||||
for (post in posts) {
|
for (post in posts) {
|
||||||
if (post['endpoint'])
|
if (post['endpoint'])
|
||||||
results << bosePOST(post['endpoint'], post['body'], post['host'])
|
results << bosePOST(post['endpoint'], post['body'], post['host'])
|
||||||
}
|
}
|
||||||
sendEvent(name:"everywhere", value:"join")
|
sendEvent(name:"everywhere", value:"join")
|
||||||
results << boseRefreshNowPlaying()
|
results << boseRefreshNowPlaying()
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
@@ -346,7 +346,7 @@ def boseZoneLeave() {
|
|||||||
* cause the zone to collapse (for example, AUX)
|
* cause the zone to collapse (for example, AUX)
|
||||||
*/
|
*/
|
||||||
def boseZoneReset() {
|
def boseZoneReset() {
|
||||||
parent.boseZoneReset()
|
parent.boseZoneReset()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -359,13 +359,13 @@ def boseZoneReset() {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseParseNowPlaying(xmlData) {
|
def boseParseNowPlaying(xmlData) {
|
||||||
def result = []
|
def result = []
|
||||||
|
|
||||||
// Perform display update, allow it to add additional commands
|
// Perform display update, allow it to add additional commands
|
||||||
if (boseSetNowPlaying(xmlData)) {
|
if (boseSetNowPlaying(xmlData)) {
|
||||||
result << boseRefreshNowPlaying()
|
result << boseRefreshNowPlaying()
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,11 +376,11 @@ def boseParseNowPlaying(xmlData) {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseParseVolume(xmlData) {
|
def boseParseVolume(xmlData) {
|
||||||
def result = []
|
def result = []
|
||||||
|
|
||||||
sendEvent(name:"volume", value:xmlData.actualvolume.text())
|
sendEvent(name:"volume", value:xmlData.actualvolume.text())
|
||||||
sendEvent(name:"mute", value:(Boolean.toBoolean(xmlData.muteenabled.text()) ? "unmuted" : "muted"))
|
sendEvent(name:"mute", value:(Boolean.toBoolean(xmlData.muteenabled.text()) ? "unmuted" : "muted"))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +390,7 @@ def boseParseVolume(xmlData) {
|
|||||||
* @param xmlData
|
* @param xmlData
|
||||||
*/
|
*/
|
||||||
def boseParseEverywhere(xmlData) {
|
def boseParseEverywhere(xmlData) {
|
||||||
// No good way of detecting the correct state right now
|
// No good way of detecting the correct state right now
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -400,28 +400,28 @@ def boseParseEverywhere(xmlData) {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseParsePresets(xmlData) {
|
def boseParsePresets(xmlData) {
|
||||||
def result = []
|
def result = []
|
||||||
|
|
||||||
state.preset = [:]
|
state.preset = [:]
|
||||||
|
|
||||||
def missing = ["1", "2", "3", "4", "5", "6"]
|
def missing = ["1", "2", "3", "4", "5", "6"]
|
||||||
for (preset in xmlData.preset) {
|
for (preset in xmlData.preset) {
|
||||||
def id = preset.attributes()['id']
|
def id = preset.attributes()['id']
|
||||||
def name = preset.ContentItem.itemName[0].text().replaceAll(~/ +/, "\n")
|
def name = preset.ContentItem.itemName[0].text().replaceAll(~/ +/, "\n")
|
||||||
if (name == "##TRANS_SONGS##")
|
if (name == "##TRANS_SONGS##")
|
||||||
name = "Local\nPlaylist"
|
name = "Local\nPlaylist"
|
||||||
sendEvent(name:"station${id}", value:name)
|
sendEvent(name:"station${id}", value:name)
|
||||||
missing = missing.findAll { it -> it != id }
|
missing = missing.findAll { it -> it != id }
|
||||||
|
|
||||||
// Store the presets into the state for recall later
|
// Store the presets into the state for recall later
|
||||||
state.preset["$id"] = XmlUtil.serialize(preset.ContentItem)
|
state.preset["$id"] = XmlUtil.serialize(preset.ContentItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (id in missing) {
|
for (id in missing) {
|
||||||
state.preset["$id"] = null
|
state.preset["$id"] = null
|
||||||
sendEvent(name:"station${id}", value:"Preset $id\n\nNot set")
|
sendEvent(name:"station${id}", value:"Preset $id\n\nNot set")
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,25 +435,25 @@ def boseParsePresets(xmlData) {
|
|||||||
* @return true if it would prefer a refresh soon
|
* @return true if it would prefer a refresh soon
|
||||||
*/
|
*/
|
||||||
def boseSetNowPlaying(xmlData, override=null) {
|
def boseSetNowPlaying(xmlData, override=null) {
|
||||||
def needrefresh = false
|
def needrefresh = false
|
||||||
def nowplaying = null
|
def nowplaying = null
|
||||||
|
|
||||||
if (xmlData && xmlData.playStatus) {
|
if (xmlData && xmlData.playStatus) {
|
||||||
switch(xmlData.playStatus) {
|
switch(xmlData.playStatus) {
|
||||||
case "BUFFERING_STATE":
|
case "BUFFERING_STATE":
|
||||||
nowplaying = "Please wait\nBuffering..."
|
nowplaying = "Please wait\nBuffering..."
|
||||||
needrefresh = true
|
needrefresh = true
|
||||||
break
|
break
|
||||||
case "PLAY_STATE":
|
case "PLAY_STATE":
|
||||||
sendEvent(name:"playpause", value:"play")
|
sendEvent(name:"playpause", value:"play")
|
||||||
break
|
break
|
||||||
case "PAUSE_STATE":
|
case "PAUSE_STATE":
|
||||||
case "STOP_STATE":
|
case "STOP_STATE":
|
||||||
sendEvent(name:"playpause", value:"pause")
|
sendEvent(name:"playpause", value:"pause")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the previous section didn't handle this, take another stab at it
|
// If the previous section didn't handle this, take another stab at it
|
||||||
if (!nowplaying) {
|
if (!nowplaying) {
|
||||||
nowplaying = ""
|
nowplaying = ""
|
||||||
@@ -480,21 +480,21 @@ def boseSetNowPlaying(xmlData, override=null) {
|
|||||||
if (xmlData.ContentItem.itemName[0])
|
if (xmlData.ContentItem.itemName[0])
|
||||||
nowplaying += "[${xmlData.ContentItem.itemName[0].text()}]\n\n"
|
nowplaying += "[${xmlData.ContentItem.itemName[0].text()}]\n\n"
|
||||||
case "STORED_MUSIC":
|
case "STORED_MUSIC":
|
||||||
nowplaying += "${xmlData.track.text()}"
|
nowplaying += "${xmlData.track.text()}"
|
||||||
if (xmlData.artist)
|
if (xmlData.artist)
|
||||||
nowplaying += "\nby\n${xmlData.artist.text()}"
|
nowplaying += "\nby\n${xmlData.artist.text()}"
|
||||||
if (xmlData.album)
|
if (xmlData.album)
|
||||||
nowplaying += "\n\n(${xmlData.album.text()})"
|
nowplaying += "\n\n(${xmlData.album.text()})"
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (xmlData != null)
|
if (xmlData != null)
|
||||||
nowplaying = "${xmlData.ContentItem.itemName[0].text()}"
|
nowplaying = "${xmlData.ContentItem.itemName[0].text()}"
|
||||||
else
|
else
|
||||||
nowplaying = "Unknown"
|
nowplaying = "Unknown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some last parsing which only deals with actual data from device
|
// Some last parsing which only deals with actual data from device
|
||||||
if (xmlData) {
|
if (xmlData) {
|
||||||
if (xmlData.attributes()['source'] == "STANDBY") {
|
if (xmlData.attributes()['source'] == "STANDBY") {
|
||||||
log.trace "nowPlaying reports standby: " + XmlUtil.serialize(xmlData)
|
log.trace "nowPlaying reports standby: " + XmlUtil.serialize(xmlData)
|
||||||
@@ -502,22 +502,22 @@ def boseSetNowPlaying(xmlData, override=null) {
|
|||||||
} else {
|
} else {
|
||||||
sendEvent(name:"switch", value:"on")
|
sendEvent(name:"switch", value:"on")
|
||||||
}
|
}
|
||||||
boseSetPlayerAttributes(xmlData)
|
boseSetPlayerAttributes(xmlData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not allow a standby device or AUX to be master
|
// Do not allow a standby device or AUX to be master
|
||||||
if (!parent.boseZoneHasMaster() && (override ? override : xmlData.attributes()['source']) == "STANDBY")
|
if (!parent.boseZoneHasMaster() && (override ? override : xmlData.attributes()['source']) == "STANDBY")
|
||||||
sendEvent(name:"everywhere", value:"unavailable")
|
sendEvent(name:"everywhere", value:"unavailable")
|
||||||
else if ((override ? override : xmlData.attributes()['source']) == "AUX")
|
else if ((override ? override : xmlData.attributes()['source']) == "AUX")
|
||||||
sendEvent(name:"everywhere", value:"unavailable")
|
sendEvent(name:"everywhere", value:"unavailable")
|
||||||
else if (boseGetZone()) {
|
else if (boseGetZone()) {
|
||||||
log.info "We're in the zone: " + boseGetZone()
|
log.info "We're in the zone: " + boseGetZone()
|
||||||
sendEvent(name:"everywhere", value:"leave")
|
sendEvent(name:"everywhere", value:"leave")
|
||||||
} else
|
} else
|
||||||
sendEvent(name:"everywhere", value:"join")
|
sendEvent(name:"everywhere", value:"join")
|
||||||
|
|
||||||
|
sendEvent(name:"nowplaying", value:nowplaying)
|
||||||
|
|
||||||
sendEvent(name:"nowplaying", value:nowplaying)
|
|
||||||
|
|
||||||
return needrefresh
|
return needrefresh
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,18 +531,18 @@ def boseSetPlayerAttributes(xmlData) {
|
|||||||
def trackText = ""
|
def trackText = ""
|
||||||
def trackDesc = ""
|
def trackDesc = ""
|
||||||
def trackData = [:]
|
def trackData = [:]
|
||||||
|
|
||||||
switch (xmlData.attributes()['source']) {
|
switch (xmlData.attributes()['source']) {
|
||||||
case "STANDBY":
|
case "STANDBY":
|
||||||
trackData["station"] = trackText = trackDesc = "Standby"
|
trackData["station"] = trackText = trackDesc = "Standby"
|
||||||
break
|
break
|
||||||
case "AUX":
|
case "AUX":
|
||||||
trackData["station"] = trackText = trackDesc = "Auxiliary Input"
|
trackData["station"] = trackText = trackDesc = "Auxiliary Input"
|
||||||
break
|
break
|
||||||
case "AIRPLAY":
|
case "AIRPLAY":
|
||||||
trackData["station"] = trackText = trackDesc = "Air Play"
|
trackData["station"] = trackText = trackDesc = "Air Play"
|
||||||
break
|
break
|
||||||
case "SPOTIFY":
|
case "SPOTIFY":
|
||||||
case "DEEZER":
|
case "DEEZER":
|
||||||
case "PANDORA":
|
case "PANDORA":
|
||||||
case "IHEART":
|
case "IHEART":
|
||||||
@@ -550,25 +550,25 @@ def boseSetPlayerAttributes(xmlData) {
|
|||||||
trackText = trackDesc = "${xmlData.track.text()}"
|
trackText = trackDesc = "${xmlData.track.text()}"
|
||||||
trackData["name"] = xmlData.track.text()
|
trackData["name"] = xmlData.track.text()
|
||||||
if (xmlData.artist) {
|
if (xmlData.artist) {
|
||||||
trackText += " by ${xmlData.artist.text()}"
|
trackText += " by ${xmlData.artist.text()}"
|
||||||
trackDesc += " - ${xmlData.artist.text()}"
|
trackDesc += " - ${xmlData.artist.text()}"
|
||||||
trackData["artist"] = xmlData.artist.text()
|
trackData["artist"] = xmlData.artist.text()
|
||||||
}
|
}
|
||||||
if (xmlData.album) {
|
if (xmlData.album) {
|
||||||
trackText += " (${xmlData.album.text()})"
|
trackText += " (${xmlData.album.text()})"
|
||||||
trackData["album"] = xmlData.album.text()
|
trackData["album"] = xmlData.album.text()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "INTERNET_RADIO":
|
case "INTERNET_RADIO":
|
||||||
trackDesc = xmlData.stationName.text()
|
trackDesc = xmlData.stationName.text()
|
||||||
trackText = xmlData.stationName.text() + ": " + xmlData.description.text()
|
trackText = xmlData.stationName.text() + ": " + xmlData.description.text()
|
||||||
trackData["station"] = xmlData.stationName.text()
|
trackData["station"] = xmlData.stationName.text()
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
trackText = trackDesc = xmlData.ContentItem.itemName[0].text()
|
trackText = trackDesc = xmlData.ContentItem.itemName[0].text()
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(name:"trackDescription", value:trackDesc, descriptionText:trackText)
|
sendEvent(name:"trackDescription", value:trackDesc, descriptionText:trackText)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -577,7 +577,7 @@ def boseSetPlayerAttributes(xmlData) {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseGetEverywhereState() {
|
def boseGetEverywhereState() {
|
||||||
return boseGET("/getZone")
|
return boseGET("/getZone")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -591,9 +591,9 @@ def boseGetEverywhereState() {
|
|||||||
* the second key info.
|
* the second key info.
|
||||||
*/
|
*/
|
||||||
def boseKeypress(key) {
|
def boseKeypress(key) {
|
||||||
def press = "<key state=\"press\" sender=\"Gabbo\">${key}</key>"
|
def press = "<key state=\"press\" sender=\"Gabbo\">${key}</key>"
|
||||||
def release = "<key state=\"release\" sender=\"Gabbo\">${key}</key>"
|
def release = "<key state=\"release\" sender=\"Gabbo\">${key}</key>"
|
||||||
|
|
||||||
return [bosePOST("/key", press), bosePOST("/key", release)]
|
return [bosePOST("/key", press), bosePOST("/key", release)]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,23 +605,23 @@ def boseKeypress(key) {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseSetPlayMode(boolean play) {
|
def boseSetPlayMode(boolean play) {
|
||||||
log.trace "Sending " + (play ? "PLAY" : "PAUSE")
|
log.trace "Sending " + (play ? "PLAY" : "PAUSE")
|
||||||
return boseKeypress(play ? "PLAY" : "PAUSE")
|
return boseKeypress(play ? "PLAY" : "PAUSE")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the volume in a deterministic way.
|
* Sets the volume in a deterministic way.
|
||||||
*
|
*
|
||||||
* @param New volume level, ranging from 0 to 100
|
* @param New volume level, ranging from 0 to 100
|
||||||
*
|
*
|
||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseSetVolume(int level) {
|
def boseSetVolume(int level) {
|
||||||
def result = []
|
def result = []
|
||||||
int vol = Math.min(100, Math.max(level, 0))
|
int vol = Math.min(100, Math.max(level, 0))
|
||||||
|
|
||||||
|
sendEvent(name:"volume", value:"${vol}")
|
||||||
|
|
||||||
sendEvent(name:"volume", value:"${vol}")
|
|
||||||
|
|
||||||
return [bosePOST("/volume", "<volume>${vol}</volume>"), boseGetVolume()]
|
return [bosePOST("/volume", "<volume>${vol}</volume>"), boseGetVolume()]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,28 +633,28 @@ def boseSetVolume(int level) {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseSetMute(boolean mute) {
|
def boseSetMute(boolean mute) {
|
||||||
queueCallback('volume', 'cb_boseSetMute', mute ? 'MUTE' : 'UNMUTE')
|
queueCallback('volume', 'cb_boseSetMute', mute ? 'MUTE' : 'UNMUTE')
|
||||||
return boseGetVolume()
|
return boseGetVolume()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for boseSetMute(), checks current state and changes it
|
* Callback for boseSetMute(), checks current state and changes it
|
||||||
* if it doesn't match the requested state.
|
* if it doesn't match the requested state.
|
||||||
*
|
*
|
||||||
* @param xml The volume XML data
|
* @param xml The volume XML data
|
||||||
* @param mute The new state of mute
|
* @param mute The new state of mute
|
||||||
*
|
*
|
||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def cb_boseSetMute(xml, mute) {
|
def cb_boseSetMute(xml, mute) {
|
||||||
def result = []
|
def result = []
|
||||||
if ((xml.muteenabled.text() == 'false' && mute == 'MUTE') ||
|
if ((xml.muteenabled.text() == 'false' && mute == 'MUTE') ||
|
||||||
(xml.muteenabled.text() == 'true' && mute == 'UNMUTE'))
|
(xml.muteenabled.text() == 'true' && mute == 'UNMUTE'))
|
||||||
{
|
{
|
||||||
result << boseKeypress("MUTE")
|
result << boseKeypress("MUTE")
|
||||||
}
|
}
|
||||||
log.trace("muteunmute: " + ((mute == "MUTE") ? "unmute" : "mute"))
|
log.trace("muteunmute: " + ((mute == "MUTE") ? "unmute" : "mute"))
|
||||||
sendEvent(name:"muteunmute", value:((mute == "MUTE") ? "unmute" : "mute"))
|
sendEvent(name:"muteunmute", value:((mute == "MUTE") ? "unmute" : "mute"))
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,7 +664,7 @@ def cb_boseSetMute(xml, mute) {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseGetVolume() {
|
def boseGetVolume() {
|
||||||
return boseGET("/volume")
|
return boseGET("/volume")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -674,10 +674,10 @@ def boseGetVolume() {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseChangeTrack(int direction) {
|
def boseChangeTrack(int direction) {
|
||||||
if (direction < 0) {
|
if (direction < 0) {
|
||||||
return boseKeypress("PREV_TRACK")
|
return boseKeypress("PREV_TRACK")
|
||||||
} else if (direction > 0) {
|
} else if (direction > 0) {
|
||||||
return boseKeypress("NEXT_TRACK")
|
return boseKeypress("NEXT_TRACK")
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -692,14 +692,14 @@ def boseChangeTrack(int direction) {
|
|||||||
* @note If no presets have been loaded, it will first refresh the presets.
|
* @note If no presets have been loaded, it will first refresh the presets.
|
||||||
*/
|
*/
|
||||||
def boseSetInput(input) {
|
def boseSetInput(input) {
|
||||||
log.info "boseSetInput(${input})"
|
log.info "boseSetInput(${input})"
|
||||||
def result = []
|
def result = []
|
||||||
|
|
||||||
if (!state.preset) {
|
if (!state.preset) {
|
||||||
result << boseGetPresets()
|
result << boseGetPresets()
|
||||||
queueCallback('presets', 'cb_boseSetInput', input)
|
queueCallback('presets', 'cb_boseSetInput', input)
|
||||||
} else {
|
} else {
|
||||||
result << cb_boseSetInput(null, input)
|
result << cb_boseSetInput(null, input)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -720,10 +720,10 @@ def boseSetInput(input) {
|
|||||||
* the preset if there is a long delay between the two.
|
* the preset if there is a long delay between the two.
|
||||||
*/
|
*/
|
||||||
def cb_boseSetInput(xml, input) {
|
def cb_boseSetInput(xml, input) {
|
||||||
def result = []
|
def result = []
|
||||||
|
|
||||||
if (input >= "1" && input <= "6" && state.preset["$input"])
|
if (input >= "1" && input <= "6" && state.preset["$input"])
|
||||||
result << bosePOST("/select", state.preset["$input"])
|
result << bosePOST("/select", state.preset["$input"])
|
||||||
else if (input.toLowerCase() == "aux") {
|
else if (input.toLowerCase() == "aux") {
|
||||||
result << boseKeypress("AUX_INPUT")
|
result << boseKeypress("AUX_INPUT")
|
||||||
}
|
}
|
||||||
@@ -731,7 +731,7 @@ def cb_boseSetInput(xml, input) {
|
|||||||
// Horrible workaround... but we need to delay
|
// Horrible workaround... but we need to delay
|
||||||
// the update by at least a few seconds...
|
// the update by at least a few seconds...
|
||||||
result << boseRefreshNowPlaying(3000)
|
result << boseRefreshNowPlaying(3000)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -746,9 +746,9 @@ def cb_boseSetInput(xml, input) {
|
|||||||
* is no discreete call.
|
* is no discreete call.
|
||||||
*/
|
*/
|
||||||
def boseSetPowerState(boolean enable) {
|
def boseSetPowerState(boolean enable) {
|
||||||
log.info "boseSetPowerState(${enable})"
|
log.info "boseSetPowerState(${enable})"
|
||||||
queueCallback('nowPlaying', "cb_boseSetPowerState", enable ? "POWERON" : "POWEROFF")
|
queueCallback('nowPlaying', "cb_boseSetPowerState", enable ? "POWERON" : "POWEROFF")
|
||||||
return boseRefreshNowPlaying()
|
return boseRefreshNowPlaying()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -761,13 +761,13 @@ def boseSetPowerState(boolean enable) {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def cb_boseSetPowerState(xml, state) {
|
def cb_boseSetPowerState(xml, state) {
|
||||||
def result = []
|
def result = []
|
||||||
if ( (xml.attributes()['source'] == "STANDBY" && state == "POWERON") ||
|
if ( (xml.attributes()['source'] == "STANDBY" && state == "POWERON") ||
|
||||||
(xml.attributes()['source'] != "STANDBY" && state == "POWEROFF") )
|
(xml.attributes()['source'] != "STANDBY" && state == "POWEROFF") )
|
||||||
{
|
{
|
||||||
result << boseKeypress("POWER")
|
result << boseKeypress("POWER")
|
||||||
if (state == "POWERON") {
|
if (state == "POWERON") {
|
||||||
result << boseRefreshNowPlaying()
|
result << boseRefreshNowPlaying()
|
||||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", 5)
|
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", 5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -786,9 +786,9 @@ def cb_boseSetPowerState(xml, state) {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def cb_boseConfirmPowerOn(xml, tries) {
|
def cb_boseConfirmPowerOn(xml, tries) {
|
||||||
def result = []
|
def result = []
|
||||||
log.warn "boseConfirmPowerOn() attempt #" + tries
|
log.warn "boseConfirmPowerOn() attempt #" + tries
|
||||||
if (xml.attributes()['source'] == "STANDBY" && tries > 0) {
|
if (xml.attributes()['source'] == "STANDBY" && tries > 0) {
|
||||||
result << boseRefreshNowPlaying()
|
result << boseRefreshNowPlaying()
|
||||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", tries-1)
|
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", tries-1)
|
||||||
}
|
}
|
||||||
@@ -803,19 +803,19 @@ def cb_boseConfirmPowerOn(xml, tries) {
|
|||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseRefreshNowPlaying(delay=0) {
|
def boseRefreshNowPlaying(delay=0) {
|
||||||
if (delay > 0) {
|
if (delay > 0) {
|
||||||
return ["delay ${delay}", boseGET("/now_playing")]
|
return ["delay ${delay}", boseGET("/now_playing")]
|
||||||
}
|
}
|
||||||
return boseGET("/now_playing")
|
return boseGET("/now_playing")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the list of presets
|
* Requests the list of presets
|
||||||
*
|
*
|
||||||
* @return command
|
* @return command
|
||||||
*/
|
*/
|
||||||
def boseGetPresets() {
|
def boseGetPresets() {
|
||||||
return boseGET("/presets")
|
return boseGET("/presets")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -864,10 +864,10 @@ def bosePOST(String path, String data, String address=null) {
|
|||||||
* @param param Parameters for function (optional)
|
* @param param Parameters for function (optional)
|
||||||
*/
|
*/
|
||||||
def queueCallback(String root, String func, param=null) {
|
def queueCallback(String root, String func, param=null) {
|
||||||
if (!state.pending)
|
if (!state.pending)
|
||||||
state.pending = [:]
|
state.pending = [:]
|
||||||
if (!state.pending[root])
|
if (!state.pending[root])
|
||||||
state.pending[root] = []
|
state.pending[root] = []
|
||||||
state.pending[root] << ["$func":"$param"]
|
state.pending[root] << ["$func":"$param"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,16 +879,16 @@ def queueCallback(String root, String func, param=null) {
|
|||||||
* the same loop.
|
* the same loop.
|
||||||
*/
|
*/
|
||||||
def prepareCallbacks() {
|
def prepareCallbacks() {
|
||||||
if (!state.pending)
|
if (!state.pending)
|
||||||
return
|
return
|
||||||
if (!state.ready)
|
if (!state.ready)
|
||||||
state.ready = [:]
|
state.ready = [:]
|
||||||
state.ready << state.pending
|
state.ready << state.pending
|
||||||
state.pending = [:]
|
state.pending = [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes any ready callback for a specific root node
|
* Executes any ready callback for a specific root node
|
||||||
* with associated parameter and then clears that entry.
|
* with associated parameter and then clears that entry.
|
||||||
*
|
*
|
||||||
* If a callback returns data, it's added to a list of
|
* If a callback returns data, it's added to a list of
|
||||||
@@ -901,14 +901,14 @@ def prepareCallbacks() {
|
|||||||
* @return list of commands
|
* @return list of commands
|
||||||
*/
|
*/
|
||||||
def processCallbacks(xml) {
|
def processCallbacks(xml) {
|
||||||
def result = []
|
def result = []
|
||||||
|
|
||||||
if (!state.ready)
|
if (!state.ready)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if (state.ready[xml.name()]) {
|
if (state.ready[xml.name()]) {
|
||||||
state.ready[xml.name()].each { callback ->
|
state.ready[xml.name()].each { callback ->
|
||||||
callback.each { func, param ->
|
callback.each { func, param ->
|
||||||
if (func != "func") {
|
if (func != "func") {
|
||||||
if (param)
|
if (param)
|
||||||
result << "$func"(xml, param)
|
result << "$func"(xml, param)
|
||||||
@@ -923,25 +923,25 @@ def processCallbacks(xml) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State managament for the Play Everywhere zone.
|
* State managament for the Play Everywhere zone.
|
||||||
* This is typically called from the parent.
|
* This is typically called from the parent.
|
||||||
*
|
*
|
||||||
* A device is either:
|
* A device is either:
|
||||||
*
|
*
|
||||||
* null = Not participating
|
* null = Not participating
|
||||||
* server = running the show
|
* server = running the show
|
||||||
* client = under the control of the server
|
* client = under the control of the server
|
||||||
*
|
*
|
||||||
* @param newstate (see above for types)
|
* @param newstate (see above for types)
|
||||||
*/
|
*/
|
||||||
def boseSetZone(String newstate) {
|
def boseSetZone(String newstate) {
|
||||||
log.debug "boseSetZone($newstate)"
|
log.debug "boseSetZone($newstate)"
|
||||||
state.zone = newstate
|
state.zone = newstate
|
||||||
|
|
||||||
// Refresh our state
|
// Refresh our state
|
||||||
if (newstate) {
|
if (newstate) {
|
||||||
sendEvent(name:"everywhere", value:"leave")
|
sendEvent(name:"everywhere", value:"leave")
|
||||||
} else {
|
} else {
|
||||||
sendEvent(name:"everywhere", value:"join")
|
sendEvent(name:"everywhere", value:"join")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -954,7 +954,7 @@ def boseSetZone(String newstate) {
|
|||||||
* @return state
|
* @return state
|
||||||
*/
|
*/
|
||||||
def boseGetZone() {
|
def boseGetZone() {
|
||||||
return state.zone
|
return state.zone
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -967,7 +967,7 @@ def boseGetZone() {
|
|||||||
* @param devID The DeviceID
|
* @param devID The DeviceID
|
||||||
*/
|
*/
|
||||||
def boseSetDeviceID(String devID) {
|
def boseSetDeviceID(String devID) {
|
||||||
state.deviceID = devID
|
state.deviceID = devID
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -976,7 +976,7 @@ def boseSetDeviceID(String devID) {
|
|||||||
* @return deviceID
|
* @return deviceID
|
||||||
*/
|
*/
|
||||||
def boseGetDeviceID() {
|
def boseGetDeviceID() {
|
||||||
return state.deviceID
|
return state.deviceID
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -985,5 +985,5 @@ def boseGetDeviceID() {
|
|||||||
* @return IP address
|
* @return IP address
|
||||||
*/
|
*/
|
||||||
def getDeviceIP() {
|
def getDeviceIP() {
|
||||||
return parent.resolveDNI2Address(device.deviceNetworkId)
|
return parent.resolveDNI2Address(device.deviceNetworkId)
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Bose SoundTouch (Connect)
|
* Bose SoundTouch (Connect)
|
||||||
*
|
*
|
||||||
* Copyright 2015 Henric Andersson
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
definition(
|
definition(
|
||||||
name: "Bose SoundTouch (Connect)",
|
name: "Bose SoundTouch (Connect)",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
author: "Henric.Andersson@smartthings.com",
|
author: "SmartThings",
|
||||||
description: "Control your Bose SoundTouch speakers",
|
description: "Control your Bose SoundTouch speakers",
|
||||||
category: "SmartThings Labs",
|
category: "SmartThings Labs",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
page(name:"deviceDiscovery", title:"Device Setup", content:"deviceDiscovery", refreshTimeout:5)
|
page(name:"deviceDiscovery", title:"Device Setup", content:"deviceDiscovery", refreshTimeout:5)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,7 +36,7 @@ preferences {
|
|||||||
* @todo This + getUSNQualifier should be one and should use regular expressions
|
* @todo This + getUSNQualifier should be one and should use regular expressions
|
||||||
*/
|
*/
|
||||||
def getDeviceType() {
|
def getDeviceType() {
|
||||||
return "urn:schemas-upnp-org:device:MediaRenderer:1" // Bose
|
return "urn:schemas-upnp-org:device:MediaRenderer:1" // Bose
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,7 +46,7 @@ def getDeviceType() {
|
|||||||
* @return Additional qualifier OR null if not needed
|
* @return Additional qualifier OR null if not needed
|
||||||
*/
|
*/
|
||||||
def getUSNQualifier() {
|
def getUSNQualifier() {
|
||||||
return "uuid:BO5EBO5E-F00D-F00D-FEED-"
|
return "uuid:BO5EBO5E-F00D-F00D-FEED-"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +56,7 @@ def getUSNQualifier() {
|
|||||||
* @return name
|
* @return name
|
||||||
*/
|
*/
|
||||||
def getDeviceName() {
|
def getDeviceName() {
|
||||||
return "Bose SoundTouch"
|
return "Bose SoundTouch"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +65,7 @@ def getDeviceName() {
|
|||||||
* @return namespace
|
* @return namespace
|
||||||
*/
|
*/
|
||||||
def getNameSpace() {
|
def getNameSpace() {
|
||||||
return "smartthings"
|
return "smartthings"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,48 +77,48 @@ def getNameSpace() {
|
|||||||
*/
|
*/
|
||||||
def deviceDiscovery()
|
def deviceDiscovery()
|
||||||
{
|
{
|
||||||
if(canInstallLabs())
|
if(canInstallLabs())
|
||||||
{
|
{
|
||||||
def refreshInterval = 3 // Number of seconds between refresh
|
def refreshInterval = 3 // Number of seconds between refresh
|
||||||
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
|
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
|
||||||
state.deviceRefreshCount = deviceRefreshCount + refreshInterval
|
state.deviceRefreshCount = deviceRefreshCount + refreshInterval
|
||||||
|
|
||||||
|
def devices = getSelectableDevice()
|
||||||
|
def numFound = devices.size() ?: 0
|
||||||
|
|
||||||
def devices = getSelectableDevice()
|
|
||||||
def numFound = devices.size() ?: 0
|
|
||||||
|
|
||||||
// Make sure we get location updates (contains LAN data such as SSDP results, etc)
|
// Make sure we get location updates (contains LAN data such as SSDP results, etc)
|
||||||
subscribeNetworkEvents()
|
subscribeNetworkEvents()
|
||||||
|
|
||||||
//device discovery request every 15s
|
//device discovery request every 15s
|
||||||
if((deviceRefreshCount % 15) == 0) {
|
if((deviceRefreshCount % 15) == 0) {
|
||||||
discoverDevices()
|
discoverDevices()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify request every 3 seconds except on discoveries
|
// Verify request every 3 seconds except on discoveries
|
||||||
if(((deviceRefreshCount % 3) == 0) && ((deviceRefreshCount % 15) != 0)) {
|
if(((deviceRefreshCount % 3) == 0) && ((deviceRefreshCount % 15) != 0)) {
|
||||||
verifyDevices()
|
verifyDevices()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.trace "Discovered devices: ${devices}"
|
log.trace "Discovered devices: ${devices}"
|
||||||
|
|
||||||
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||||
section("Please wait while we discover your ${getDeviceName()}. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
section("Please wait while we discover your ${getDeviceName()}. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||||
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices
|
input "selecteddevice", "enum", required:false, title:"Select ${getDeviceName()} (${numFound} found)", multiple:true, options:devices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
|
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
|
||||||
|
|
||||||
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
|
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
|
||||||
|
|
||||||
return dynamicPage(name:"deviceDiscovery", title:"Upgrade needed!", nextPage:"", install:true, uninstall: true) {
|
return dynamicPage(name:"deviceDiscovery", title:"Upgrade needed!", nextPage:"", install:true, uninstall: true) {
|
||||||
section("Upgrade") {
|
section("Upgrade") {
|
||||||
paragraph "$upgradeNeeded"
|
paragraph "$upgradeNeeded"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,17 +126,17 @@ To update your Hub, access Location Settings in the Main Menu (tap the gear next
|
|||||||
* pressed "Install".
|
* pressed "Install".
|
||||||
*/
|
*/
|
||||||
def installed() {
|
def installed() {
|
||||||
log.trace "Installed with settings: ${settings}"
|
log.trace "Installed with settings: ${settings}"
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by SmartThings Cloud when app has been updated
|
* Called by SmartThings Cloud when app has been updated
|
||||||
*/
|
*/
|
||||||
def updated() {
|
def updated() {
|
||||||
log.trace "Updated with settings: ${settings}"
|
log.trace "Updated with settings: ${settings}"
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,13 +157,13 @@ def uninstalled() {
|
|||||||
* for changes (new address, port, etc...)
|
* for changes (new address, port, etc...)
|
||||||
*/
|
*/
|
||||||
def initialize() {
|
def initialize() {
|
||||||
log.trace "initialize()"
|
log.trace "initialize()"
|
||||||
state.subscribe = false
|
state.subscribe = false
|
||||||
if (selecteddevice) {
|
if (selecteddevice) {
|
||||||
addDevice()
|
addDevice()
|
||||||
refreshDevices()
|
refreshDevices()
|
||||||
subscribeNetworkEvents(true)
|
subscribeNetworkEvents(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,33 +172,33 @@ def initialize() {
|
|||||||
* Uses selecteddevice defined in the deviceDiscovery() page
|
* Uses selecteddevice defined in the deviceDiscovery() page
|
||||||
*/
|
*/
|
||||||
def addDevice(){
|
def addDevice(){
|
||||||
def devices = getVerifiedDevices()
|
def devices = getVerifiedDevices()
|
||||||
def devlist
|
def devlist
|
||||||
log.trace "Adding childs"
|
log.trace "Adding childs"
|
||||||
|
|
||||||
// If only one device is selected, we don't get a list (when using simulator)
|
// If only one device is selected, we don't get a list (when using simulator)
|
||||||
if (!(selecteddevice instanceof List)) {
|
if (!(selecteddevice instanceof List)) {
|
||||||
devlist = [selecteddevice]
|
devlist = [selecteddevice]
|
||||||
} else {
|
} else {
|
||||||
devlist = selecteddevice
|
devlist = selecteddevice
|
||||||
}
|
}
|
||||||
|
|
||||||
log.trace "These are being installed: ${devlist}"
|
log.trace "These are being installed: ${devlist}"
|
||||||
|
|
||||||
devlist.each { dni ->
|
devlist.each { dni ->
|
||||||
def d = getChildDevice(dni)
|
def d = getChildDevice(dni)
|
||||||
if(!d) {
|
if(!d) {
|
||||||
def newDevice = devices.find { (it.value.mac) == dni }
|
def newDevice = devices.find { (it.value.mac) == dni }
|
||||||
def deviceName = newDevice?.value.name
|
def deviceName = newDevice?.value.name
|
||||||
if (!deviceName)
|
if (!deviceName)
|
||||||
deviceName = getDeviceName() + "[${newDevice?.value.name}]"
|
deviceName = getDeviceName() + "[${newDevice?.value.name}]"
|
||||||
d = addChildDevice(getNameSpace(), getDeviceName(), dni, newDevice?.value.hub, [label:"${deviceName}"])
|
d = addChildDevice(getNameSpace(), getDeviceName(), dni, newDevice?.value.hub, [label:"${deviceName}"])
|
||||||
d.boseSetDeviceID(newDevice.value.deviceID)
|
d.boseSetDeviceID(newDevice.value.deviceID)
|
||||||
log.trace "Created ${d.displayName} with id $dni"
|
log.trace "Created ${d.displayName} with id $dni"
|
||||||
} else {
|
} else {
|
||||||
log.trace "${d.displayName} with id $dni already exists"
|
log.trace "${d.displayName} with id $dni already exists"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,9 +208,9 @@ def addDevice(){
|
|||||||
* @return address or null
|
* @return address or null
|
||||||
*/
|
*/
|
||||||
def resolveDNI2Address(dni) {
|
def resolveDNI2Address(dni) {
|
||||||
def device = getVerifiedDevices().find { (it.value.mac) == dni }
|
def device = getVerifiedDevices().find { (it.value.mac) == dni }
|
||||||
if (device) {
|
if (device) {
|
||||||
return convertHexToIP(device.value.networkAddress)
|
return convertHexToIP(device.value.networkAddress)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -219,33 +219,33 @@ def resolveDNI2Address(dni) {
|
|||||||
* Joins a child to the "Play Everywhere" zone
|
* Joins a child to the "Play Everywhere" zone
|
||||||
*
|
*
|
||||||
* @param child The speaker joining the zone
|
* @param child The speaker joining the zone
|
||||||
* @return A list of maps with POST data
|
* @return A list of maps with POST data
|
||||||
*/
|
*/
|
||||||
def boseZoneJoin(child) {
|
def boseZoneJoin(child) {
|
||||||
log = child.log // So we can debug this function
|
log = child.log // So we can debug this function
|
||||||
|
|
||||||
def results = []
|
def results = []
|
||||||
def result = [:]
|
def result = [:]
|
||||||
|
|
||||||
// Find the master (if any)
|
// Find the master (if any)
|
||||||
def server = getChildDevices().find{ it.boseGetZone() == "server" }
|
def server = getChildDevices().find{ it.boseGetZone() == "server" }
|
||||||
|
|
||||||
if (server) {
|
if (server) {
|
||||||
log.debug "boseJoinZone() We have a server already, so lets add the new speaker"
|
log.debug "boseJoinZone() We have a server already, so lets add the new speaker"
|
||||||
child.boseSetZone("client")
|
child.boseSetZone("client")
|
||||||
|
|
||||||
result['endpoint'] = "/setZone"
|
result['endpoint'] = "/setZone"
|
||||||
result['host'] = server.getDeviceIP() + ":8090"
|
result['host'] = server.getDeviceIP() + ":8090"
|
||||||
result['body'] = "<zone master=\"${server.boseGetDeviceID()}\" senderIPAddress=\"${server.getDeviceIP()}\">"
|
result['body'] = "<zone master=\"${server.boseGetDeviceID()}\" senderIPAddress=\"${server.getDeviceIP()}\">"
|
||||||
getChildDevices().each{ it ->
|
getChildDevices().each{ it ->
|
||||||
log.trace "child: " + child
|
log.trace "child: " + child
|
||||||
log.trace "zone : " + it.boseGetZone()
|
log.trace "zone : " + it.boseGetZone()
|
||||||
if (it.boseGetZone() || it.boseGetDeviceID() == child.boseGetDeviceID())
|
if (it.boseGetZone() || it.boseGetDeviceID() == child.boseGetDeviceID())
|
||||||
result['body'] = result['body'] + "<member ipaddress=\"${it.getDeviceIP()}\">${it.boseGetDeviceID()}</member>"
|
result['body'] = result['body'] + "<member ipaddress=\"${it.getDeviceIP()}\">${it.boseGetDeviceID()}</member>"
|
||||||
}
|
}
|
||||||
result['body'] = result['body'] + '</zone>'
|
result['body'] = result['body'] + '</zone>'
|
||||||
} else {
|
} else {
|
||||||
log.debug "boseJoinZone() No server, add it!"
|
log.debug "boseJoinZone() No server, add it!"
|
||||||
result['endpoint'] = "/setZone"
|
result['endpoint'] = "/setZone"
|
||||||
result['host'] = child.getDeviceIP() + ":8090"
|
result['host'] = child.getDeviceIP() + ":8090"
|
||||||
result['body'] = "<zone master=\"${child.boseGetDeviceID()}\" senderIPAddress=\"${child.getDeviceIP()}\">"
|
result['body'] = "<zone master=\"${child.boseGetDeviceID()}\" senderIPAddress=\"${child.getDeviceIP()}\">"
|
||||||
@@ -258,11 +258,11 @@ def boseZoneJoin(child) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def boseZoneReset() {
|
def boseZoneReset() {
|
||||||
getChildDevices().each{ it.boseSetZone(null) }
|
getChildDevices().each{ it.boseSetZone(null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
def boseZoneHasMaster() {
|
def boseZoneHasMaster() {
|
||||||
return getChildDevices().find{ it.boseGetZone() == "server" } != null
|
return getChildDevices().find{ it.boseGetZone() == "server" } != null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -272,19 +272,19 @@ def boseZoneHasMaster() {
|
|||||||
* @return a list of maps with POST data
|
* @return a list of maps with POST data
|
||||||
*/
|
*/
|
||||||
def boseZoneLeave(child) {
|
def boseZoneLeave(child) {
|
||||||
log = child.log // So we can debug this function
|
log = child.log // So we can debug this function
|
||||||
|
|
||||||
def results = []
|
def results = []
|
||||||
def result = [:]
|
def result = [:]
|
||||||
|
|
||||||
// First, tag us as a non-member
|
// First, tag us as a non-member
|
||||||
child.boseSetZone(null)
|
child.boseSetZone(null)
|
||||||
|
|
||||||
// Find the master (if any)
|
// Find the master (if any)
|
||||||
def server = getChildDevices().find{ it.boseGetZone() == "server" }
|
def server = getChildDevices().find{ it.boseGetZone() == "server" }
|
||||||
|
|
||||||
if (server && server.boseGetDeviceID() != child.boseGetDeviceID()) {
|
if (server && server.boseGetDeviceID() != child.boseGetDeviceID()) {
|
||||||
log.debug "boseLeaveZone() We have a server, so tell him we're leaving"
|
log.debug "boseLeaveZone() We have a server, so tell him we're leaving"
|
||||||
result['endpoint'] = "/removeZoneSlave"
|
result['endpoint'] = "/removeZoneSlave"
|
||||||
result['host'] = server.getDeviceIP() + ":8090"
|
result['host'] = server.getDeviceIP() + ":8090"
|
||||||
result['body'] = "<zone master=\"${server.boseGetDeviceID()}\" senderIPAddress=\"${server.getDeviceIP()}\">"
|
result['body'] = "<zone master=\"${server.boseGetDeviceID()}\" senderIPAddress=\"${server.getDeviceIP()}\">"
|
||||||
@@ -292,28 +292,28 @@ def boseZoneLeave(child) {
|
|||||||
result['body'] = result['body'] + '</zone>'
|
result['body'] = result['body'] + '</zone>'
|
||||||
results << result
|
results << result
|
||||||
} else {
|
} else {
|
||||||
log.debug "boseLeaveZone() No server, then...uhm, we probably were it!"
|
log.debug "boseLeaveZone() No server, then...uhm, we probably were it!"
|
||||||
// Dismantle the entire thing, first send this to master
|
// Dismantle the entire thing, first send this to master
|
||||||
result['endpoint'] = "/removeZoneSlave"
|
result['endpoint'] = "/removeZoneSlave"
|
||||||
result['host'] = child.getDeviceIP() + ":8090"
|
result['host'] = child.getDeviceIP() + ":8090"
|
||||||
result['body'] = "<zone master=\"${child.boseGetDeviceID()}\" senderIPAddress=\"${child.getDeviceIP()}\">"
|
result['body'] = "<zone master=\"${child.boseGetDeviceID()}\" senderIPAddress=\"${child.getDeviceIP()}\">"
|
||||||
getChildDevices().each{ dev ->
|
getChildDevices().each{ dev ->
|
||||||
if (dev.boseGetZone() || dev.boseGetDeviceID() == child.boseGetDeviceID())
|
if (dev.boseGetZone() || dev.boseGetDeviceID() == child.boseGetDeviceID())
|
||||||
result['body'] = result['body'] + "<member ipaddress=\"${dev.getDeviceIP()}\">${dev.boseGetDeviceID()}</member>"
|
result['body'] = result['body'] + "<member ipaddress=\"${dev.getDeviceIP()}\">${dev.boseGetDeviceID()}</member>"
|
||||||
}
|
}
|
||||||
result['body'] = result['body'] + '</zone>'
|
result['body'] = result['body'] + '</zone>'
|
||||||
results << result
|
results << result
|
||||||
|
|
||||||
// Also issue this to each individual client
|
// Also issue this to each individual client
|
||||||
getChildDevices().each{ dev ->
|
getChildDevices().each{ dev ->
|
||||||
if (dev.boseGetZone() && dev.boseGetDeviceID() != child.boseGetDeviceID()) {
|
if (dev.boseGetZone() && dev.boseGetDeviceID() != child.boseGetDeviceID()) {
|
||||||
log.trace "Additional device: " + dev
|
log.trace "Additional device: " + dev
|
||||||
result['host'] = dev.getDeviceIP() + ":8090"
|
result['host'] = dev.getDeviceIP() + ":8090"
|
||||||
results << result
|
results << result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,10 +323,10 @@ def boseZoneLeave(child) {
|
|||||||
* @return mapping of root-node <-> parser function
|
* @return mapping of root-node <-> parser function
|
||||||
*/
|
*/
|
||||||
def getParsers() {
|
def getParsers() {
|
||||||
[
|
[
|
||||||
"root" : "parseDESC",
|
"root" : "parseDESC",
|
||||||
"info" : "parseINFO"
|
"info" : "parseINFO"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -337,27 +337,27 @@ def getParsers() {
|
|||||||
* @param evt Holds event information
|
* @param evt Holds event information
|
||||||
*/
|
*/
|
||||||
def onLocation(evt) {
|
def onLocation(evt) {
|
||||||
// Convert the event into something we can use
|
// Convert the event into something we can use
|
||||||
def lanEvent = parseLanMessage(evt.description, true)
|
def lanEvent = parseLanMessage(evt.description, true)
|
||||||
lanEvent << ["hub":evt?.hubId]
|
lanEvent << ["hub":evt?.hubId]
|
||||||
|
|
||||||
// Determine what we need to do...
|
// Determine what we need to do...
|
||||||
if (lanEvent?.ssdpTerm?.contains(getDeviceType()) &&
|
if (lanEvent?.ssdpTerm?.contains(getDeviceType()) &&
|
||||||
(getUSNQualifier() == null ||
|
(getUSNQualifier() == null ||
|
||||||
lanEvent?.ssdpUSN?.contains(getUSNQualifier())
|
lanEvent?.ssdpUSN?.contains(getUSNQualifier())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
parseSSDP(lanEvent)
|
parseSSDP(lanEvent)
|
||||||
}
|
}
|
||||||
else if (
|
else if (
|
||||||
lanEvent.headers && lanEvent.body &&
|
lanEvent.headers && lanEvent.body &&
|
||||||
lanEvent.headers."content-type".contains("xml")
|
lanEvent.headers."content-type".contains("xml")
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
def parsers = getParsers()
|
def parsers = getParsers()
|
||||||
def xmlData = new XmlSlurper().parseText(lanEvent.body)
|
def xmlData = new XmlSlurper().parseText(lanEvent.body)
|
||||||
|
|
||||||
// Let each parser take a stab at it
|
// Let each parser take a stab at it
|
||||||
parsers.each { node,func ->
|
parsers.each { node,func ->
|
||||||
if (xmlData.name() == node)
|
if (xmlData.name() == node)
|
||||||
@@ -369,20 +369,20 @@ def onLocation(evt) {
|
|||||||
/**
|
/**
|
||||||
* Handles SSDP description file.
|
* Handles SSDP description file.
|
||||||
*
|
*
|
||||||
* @param xmlData
|
* @param xmlData
|
||||||
*/
|
*/
|
||||||
private def parseDESC(xmlData) {
|
private def parseDESC(xmlData) {
|
||||||
log.info "parseDESC()"
|
log.info "parseDESC()"
|
||||||
|
|
||||||
def devicetype = getDeviceType().toLowerCase()
|
def devicetype = getDeviceType().toLowerCase()
|
||||||
def devicetxml = body.device.deviceType.text().toLowerCase()
|
def devicetxml = body.device.deviceType.text().toLowerCase()
|
||||||
|
|
||||||
// Make sure it's the type we want
|
// Make sure it's the type we want
|
||||||
if (devicetxml == devicetype) {
|
if (devicetxml == devicetype) {
|
||||||
def devices = getDevices()
|
def devices = getDevices()
|
||||||
def device = devices.find {it?.key?.contains(xmlData?.device?.UDN?.text())}
|
def device = devices.find {it?.key?.contains(xmlData?.device?.UDN?.text())}
|
||||||
if (device && !device.value?.verified) {
|
if (device && !device.value?.verified) {
|
||||||
// Unlike regular DESC, we cannot trust this just yet, parseINFO() decides all
|
// Unlike regular DESC, we cannot trust this just yet, parseINFO() decides all
|
||||||
device.value << [name:xmlData?.device?.friendlyName?.text(),model:xmlData?.device?.modelName?.text(), serialNumber:xmlData?.device?.serialNum?.text()]
|
device.value << [name:xmlData?.device?.friendlyName?.text(),model:xmlData?.device?.modelName?.text(), serialNumber:xmlData?.device?.serialNum?.text()]
|
||||||
} else {
|
} else {
|
||||||
log.error "parseDESC(): The xml file returned a device that didn't exist"
|
log.error "parseDESC(): The xml file returned a device that didn't exist"
|
||||||
@@ -398,9 +398,9 @@ private def parseDESC(xmlData) {
|
|||||||
* @param xmlData
|
* @param xmlData
|
||||||
*/
|
*/
|
||||||
private def parseINFO(xmlData) {
|
private def parseINFO(xmlData) {
|
||||||
log.info "parseINFO()"
|
log.info "parseINFO()"
|
||||||
def devicetype = getDeviceType().toLowerCase()
|
def devicetype = getDeviceType().toLowerCase()
|
||||||
|
|
||||||
def deviceID = xmlData.attributes()['deviceID']
|
def deviceID = xmlData.attributes()['deviceID']
|
||||||
def device = getDevices().find {it?.key?.contains(deviceID)}
|
def device = getDevices().find {it?.key?.contains(deviceID)}
|
||||||
if (device && !device.value?.verified) {
|
if (device && !device.value?.verified) {
|
||||||
@@ -420,15 +420,15 @@ def parseSSDP(lanEvent) {
|
|||||||
def USN = lanEvent.ssdpUSN.toString()
|
def USN = lanEvent.ssdpUSN.toString()
|
||||||
def devices = getDevices()
|
def devices = getDevices()
|
||||||
|
|
||||||
if (!(devices."${USN}")) {
|
if (!(devices."${USN}")) {
|
||||||
//device does not exist
|
//device does not exist
|
||||||
log.trace "parseSDDP() Adding Device \"${USN}\" to known list"
|
log.trace "parseSDDP() Adding Device \"${USN}\" to known list"
|
||||||
devices << ["${USN}":lanEvent]
|
devices << ["${USN}":lanEvent]
|
||||||
} else {
|
} else {
|
||||||
// update the values
|
// update the values
|
||||||
def d = devices."${USN}"
|
def d = devices."${USN}"
|
||||||
if (d.networkAddress != lanEvent.networkAddress || d.deviceAddress != lanEvent.deviceAddress) {
|
if (d.networkAddress != lanEvent.networkAddress || d.deviceAddress != lanEvent.deviceAddress) {
|
||||||
log.trace "parseSSDP() Updating device location (ip & port)"
|
log.trace "parseSSDP() Updating device location (ip & port)"
|
||||||
d.networkAddress = lanEvent.networkAddress
|
d.networkAddress = lanEvent.networkAddress
|
||||||
d.deviceAddress = lanEvent.deviceAddress
|
d.deviceAddress = lanEvent.deviceAddress
|
||||||
}
|
}
|
||||||
@@ -442,14 +442,14 @@ def parseSSDP(lanEvent) {
|
|||||||
* @return Map with zero or more devices
|
* @return Map with zero or more devices
|
||||||
*/
|
*/
|
||||||
Map getSelectableDevice() {
|
Map getSelectableDevice() {
|
||||||
def devices = getVerifiedDevices()
|
def devices = getVerifiedDevices()
|
||||||
def map = [:]
|
def map = [:]
|
||||||
devices.each {
|
devices.each {
|
||||||
def value = "${it.value.name}"
|
def value = "${it.value.name}"
|
||||||
def key = it.value.mac
|
def key = it.value.mac
|
||||||
map["${key}"] = value
|
map["${key}"] = value
|
||||||
}
|
}
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -470,7 +470,7 @@ private refreshDevices() {
|
|||||||
private subscribeNetworkEvents(force=false) {
|
private subscribeNetworkEvents(force=false) {
|
||||||
if (force) {
|
if (force) {
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
state.subscribe = false
|
state.subscribe = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!state.subscribe) {
|
if(!state.subscribe) {
|
||||||
@@ -484,7 +484,7 @@ private subscribeNetworkEvents(force=false) {
|
|||||||
*/
|
*/
|
||||||
private discoverDevices() {
|
private discoverDevices() {
|
||||||
log.trace "discoverDevice() Issuing SSDP request"
|
log.trace "discoverDevice() Issuing SSDP request"
|
||||||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery ${getDeviceType()}", physicalgraph.device.Protocol.LAN))
|
sendHubCommand(new physicalgraph.device.HubAction("lan discovery ${getDeviceType()}", physicalgraph.device.Protocol.LAN))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -492,16 +492,16 @@ private discoverDevices() {
|
|||||||
* request for each of them (basically calling verifyDevice() per unverified)
|
* request for each of them (basically calling verifyDevice() per unverified)
|
||||||
*/
|
*/
|
||||||
private verifyDevices() {
|
private verifyDevices() {
|
||||||
def devices = getDevices().findAll { it?.value?.verified != true }
|
def devices = getDevices().findAll { it?.value?.verified != true }
|
||||||
|
|
||||||
devices.each {
|
devices.each {
|
||||||
verifyDevice(
|
verifyDevice(
|
||||||
it?.value?.mac,
|
it?.value?.mac,
|
||||||
convertHexToIP(it?.value?.networkAddress),
|
convertHexToIP(it?.value?.networkAddress),
|
||||||
convertHexToInt(it?.value?.deviceAddress),
|
convertHexToInt(it?.value?.deviceAddress),
|
||||||
it?.value?.ssdpPath
|
it?.value?.ssdpPath
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -509,7 +509,7 @@ private verifyDevices() {
|
|||||||
* holds information such as the actual mac to use in certain scenarios.
|
* holds information such as the actual mac to use in certain scenarios.
|
||||||
*
|
*
|
||||||
* Without this mac (henceforth referred to as deviceID), we can't do multi-speaker
|
* Without this mac (henceforth referred to as deviceID), we can't do multi-speaker
|
||||||
* functions.
|
* functions.
|
||||||
*
|
*
|
||||||
* @param deviceNetworkId The DNI of the device
|
* @param deviceNetworkId The DNI of the device
|
||||||
* @param ip The address of the device on the network (not the same as DNI)
|
* @param ip The address of the device on the network (not the same as DNI)
|
||||||
@@ -528,7 +528,7 @@ private verifyDevice(String deviceNetworkId, String ip, int port, String devices
|
|||||||
HOST: address,
|
HOST: address,
|
||||||
]]))
|
]]))
|
||||||
} else {
|
} else {
|
||||||
log.warn("verifyDevice() IP address was empty")
|
log.warn("verifyDevice() IP address was empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,7 +538,7 @@ private verifyDevice(String deviceNetworkId, String ip, int port, String devices
|
|||||||
* @return array of verified devices
|
* @return array of verified devices
|
||||||
*/
|
*/
|
||||||
def getVerifiedDevices() {
|
def getVerifiedDevices() {
|
||||||
getDevices().findAll{ it?.value?.verified == true }
|
getDevices().findAll{ it?.value?.verified == true }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -547,7 +547,7 @@ def getVerifiedDevices() {
|
|||||||
* @return array of devices
|
* @return array of devices
|
||||||
*/
|
*/
|
||||||
def getDevices() {
|
def getDevices() {
|
||||||
state.devices = state.devices ?: [:]
|
state.devices = state.devices ?: [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -557,7 +557,7 @@ def getDevices() {
|
|||||||
* @return An integer
|
* @return An integer
|
||||||
*/
|
*/
|
||||||
private Integer convertHexToInt(hex) {
|
private Integer convertHexToInt(hex) {
|
||||||
Integer.parseInt(hex,16)
|
Integer.parseInt(hex,16)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -567,10 +567,10 @@ private Integer convertHexToInt(hex) {
|
|||||||
* @return String containing normal IPv4 dot notation
|
* @return String containing normal IPv4 dot notation
|
||||||
*/
|
*/
|
||||||
private String convertHexToIP(hex) {
|
private String convertHexToIP(hex) {
|
||||||
if (hex)
|
if (hex)
|
||||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||||
else
|
else
|
||||||
hex
|
hex
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -580,7 +580,7 @@ private String convertHexToIP(hex) {
|
|||||||
*/
|
*/
|
||||||
private Boolean canInstallLabs()
|
private Boolean canInstallLabs()
|
||||||
{
|
{
|
||||||
return hasAllHubsOver("000.011.00603")
|
return hasAllHubsOver("000.011.00603")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -592,7 +592,7 @@ private Boolean canInstallLabs()
|
|||||||
*/
|
*/
|
||||||
private Boolean hasAllHubsOver(String desiredFirmware)
|
private Boolean hasAllHubsOver(String desiredFirmware)
|
||||||
{
|
{
|
||||||
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -602,5 +602,5 @@ private Boolean hasAllHubsOver(String desiredFirmware)
|
|||||||
*/
|
*/
|
||||||
private List getRealHubFirmwareVersions()
|
private List getRealHubFirmwareVersions()
|
||||||
{
|
{
|
||||||
return location.hubs*.firmwareVersionString.findAll { it }
|
return location.hubs*.firmwareVersionString.findAll { it }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user