iTunes + Airplay instead of Sonos?

I was going to try and use the documentation (I think), got really busy and forgot what I was doing, I’d prefer to have a real button to press but I’m just going to use the simulated minimise with icons to begin. I need to add HAM Bridge commands to each of the buttons to try out a few things and see how it goes. I only have this so far…

/**

  • Copyright 2015 SmartThings
  • 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:
  •  http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  • on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  • for the specific language governing permissions and limitations under the License.

*/
metadata {
definition (name: “Simulated Minimote”, namespace: “smartthings/testing”, author: “SmartThings”) {
capability "Actuator"
capability "Button"
capability "Holdable Button"
capability "Configuration"
capability “Sensor”

    command "push1"
    command "push2"
    command "push3"
    command "push4"
    command "hold1"
    command "hold2"
    command "hold3"
    command "hold4"
}

simulator {
	status "button 1 pushed":  "command: 2001, payload: 01"
	status "button 1 held":  "command: 2001, payload: 15"
	status "button 2 pushed":  "command: 2001, payload: 29"
	status "button 2 held":  "command: 2001, payload: 3D"
	status "button 3 pushed":  "command: 2001, payload: 51"
	status "button 3 held":  "command: 2001, payload: 65"
	status "button 4 pushed":  "command: 2001, payload: 79"
	status "button 4 held":  "command: 2001, payload: 8D"
	status "wakeup":  "command: 8407, payload: "
}
tiles {
	standardTile("button", "device.button") {
		state "default", label: "", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
	}
	standardTile("push1", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Play iTunes", icon:"st.sonos.play-btn", backgroundColor: "#ffffff", action: "push1"
	}
	standardTile("push2", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Pause iTunes", icon:"st.sonos.pause-btn", backgroundColor: "#ffffff", action: "push2"
	}
	standardTile("push3", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Prev iTunes", icon:"st.sonos.previous-btn", backgroundColor: "#ffffff", action: "push3"
	}
	standardTile("push4", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Next iTunes", icon:"st.sonos.next-btn", backgroundColor: "#ffffff", action: "push4"
	}
	standardTile("dummy1", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
	}
	standardTile("hold1", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Play Spotify", icon:"st.sonos.play-btn", backgroundColor: "#ffffff", action: "hold1"
	}
	standardTile("hold2", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Pause Spotify", icon:"st.sonos.pause-btn", backgroundColor: "#ffffff", action: "hold2"
	}
	standardTile("dummy2", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: " ", backgroundColor: "#ffffff", action: "push4"
	}
	standardTile("hold3", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Prev Spotify", icon:"st.sonos.previous-btn", backgroundColor: "#ffffff", action: "hold3"
	}
	standardTile("hold4", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Next Spotify", icon:"st.sonos.next-btn", backgroundColor: "#ffffff", action: "hold4"
	}

	main "button"
	details(["push1","push2","button","push3","push4","dummy1","hold1","hold2","dummy2","hold3","hold4"])
}

}

def parse(String description) {

}

def push1() {
push(1)
}

def push2() {
push(2)
}

def push3() {
push(3)
}

def push4() {
push(4)
}

def hold1() {
hold(1)
}

def hold2() {
hold(2)
}

def hold3() {
hold(3)
}

def hold4() {
hold(4)
}

private push(button) {
log.debug "$device.displayName button $button was pushed"
sendEvent(name: “button”, value: “pushed”, data: [buttonNumber: button], descriptionText: “$device.displayName button $button was pushed”, isStateChange: true)
}

private hold(button) {
log.debug "$device.displayName button $button was held"
sendEvent(name: “button”, value: “held”, data: [buttonNumber: button], descriptionText: “$device.displayName button $button was held”, isStateChange: true)
}

def installed() {
initialize()
}

def updated() {
initialize()
}

def initialize() {
sendEvent(name: “numberOfButtons”, value: 4)
}

Is there a way of adding multiple commands? I thought of changing the def doHAMB() to def doHAMB1() def doHAMB2() def doHAMB3() and so on, would that work?

If I understand you correctly there is no need to have multiple doHAMB commands, just stuff the command you want to send in each case into a var and pass the var to doHAMB…

def theCOM = "commandToSendToHAMBridge"
doHAMB(theCOM)
def doHAMB(theCOM) {
	def ip = "${settings.server}:${settings.port}"
	sendHubCommand(new physicalgraph.device.HubAction("""GET /?$theCOM HTTP/1.1\r\nHOST: $ip\r\n\r\n""", physicalgraph.device.Protocol.LAN))
}

OK, I think I’ve done it…? I can’t test it yet because my baby is sleeping, but here is the Simulated Media Controller

================================================================================================

/**

  • Copyright 2017 ijaspley
  • 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:
  •  http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  • on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  • for the specific language governing permissions and limitations under the License.

*/
metadata {
definition (name: “Simulated Media Controller”, namespace: “ijaspley”, author: “ijaspley”) {
capability "Actuator"
capability "Button"
capability "Holdable Button"
capability "Configuration"
capability “Sensor”

    command "iTunesPlay"
    command "iTunesPause"
    command "iTunesPrev"
    command "iTunesNext"
    command "SpotifyPlay"
    command "SpotifyPause"
    command "SpotifyPrev"
    command "SpotifyNext"
}

simulator {
	status "button 1 pushed":  "command: 2001, payload: 01"
	status "button 1 held":  "command: 2001, payload: 15"
	status "button 2 pushed":  "command: 2001, payload: 29"
	status "button 2 held":  "command: 2001, payload: 3D"
	status "button 3 pushed":  "command: 2001, payload: 51"
	status "button 3 held":  "command: 2001, payload: 65"
	status "button 4 pushed":  "command: 2001, payload: 79"
	status "button 4 held":  "command: 2001, payload: 8D"
	status "wakeup":  "command: 8407, payload: "
}
tiles {
	standardTile("button", "device.button") {
		state "default", label: "", icon: "http://tinyurl.com/mehdpmu", backgroundColor: "#ffffff"
	}
	standardTile("push1", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Play iTunes", icon:"st.sonos.play-btn", backgroundColor: "#ffffff", action: "push1"
	}
	standardTile("push2", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Pause iTunes", icon:"st.sonos.pause-btn", backgroundColor: "#ffffff", action: "push2"
	}
	standardTile("dummy1", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: " ", backgroundColor: "#ffffff"
	}
	standardTile("push3", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Prev iTunes", icon:"st.sonos.previous-btn", backgroundColor: "#ffffff", action: "push3"
	}
	standardTile("push4", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Next iTunes", icon:"st.sonos.next-btn", backgroundColor: "#ffffff", action: "push4"
	}
	standardTile("dummy2", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: " ", icon: "http://tinyurl.com/lcndohy", backgroundColor: "#ffffff", action: "push4"
	}
	standardTile("hold1", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Play Spotify", icon:"st.sonos.play-btn", backgroundColor: "#ffffff", action: "hold1"
	}
	standardTile("hold2", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Pause Spotify", icon:"st.sonos.pause-btn", backgroundColor: "#ffffff", action: "hold2"
	}
	standardTile("dummy3", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: " ", backgroundColor: "#ffffff"
	}
	standardTile("hold3", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Prev Spotify", icon:"st.sonos.previous-btn", backgroundColor: "#ffffff", action: "hold3"
	}
	standardTile("hold4", "device.button", width: 1, height: 1, decoration: "flat") {
		state "default", label: "Next Spotify", icon:"st.sonos.next-btn", backgroundColor: "#ffffff", action: "hold4"
	}

	main "button"
	details(["button","push1","push2","dummy1","push3","push4","dummy2","hold1","hold2","dummy3","hold3","hold4"])
}

}

def parse(String description) {

}

def push1() {
push(1)
}

def push2() {
push(2)
}

def push3() {
push(3)
}

def push4() {
push(4)

}

def hold1() {
hold(1)
}

def hold2() {
hold(2)

}

def hold3() {
hold(3)
}

def hold4() {
hold(4)

}

private push(button) {
log.debug "$device.displayName button $button was pushed"
sendEvent(name: “button”, value: “pushed”, data: [buttonNumber: button], descriptionText: “$device.displayName button $button was pushed”, isStateChange: true)
}

private hold(button) {
log.debug "$device.displayName button $button was held"
sendEvent(name: “button”, value: “held”, data: [buttonNumber: button], descriptionText: “$device.displayName button $button was held”, isStateChange: true)
}

def installed() {
initialize()
}

def updated() {
initialize()
}

def initialize() {
sendEvent(name: “numberOfButtons”, value: 4)
}

================================================================================================

Here is the SmartApp to configure it…

================================================================================================

/**

  • Copyright 2017 ijaspley
  • 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:
  •  http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  • on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  • for the specific language governing permissions and limitations under the License.
  • Button Controller
  • Author: ijaspley
  • Date: 2017-3-30
    */
    definition(
    name: “Media Controller”,
    namespace: “ijaspley”,
    author: “ijaspley”,
    description: “Control iTunes, Spotify and anything via HAM Bridge with Simulated Media Controller”,
    category: “Convenience”,
    iconUrl: “http://tinyurl.com/lmyjsso”,
    )

preferences {
page(name: “selectButton”)
page(name: “configureButton1”)
page(name: “configureButton2”)
page(name: “configureButton3”)
page(name: “configureButton4”)

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 selectButton() {
dynamicPage(name: “selectButton”, title: “Select your media remote”, nextPage: “configureButton1”, uninstall: configured()) {
section {
input “buttonDevice”, “capability.button”, title: “Button”, multiple: false, required: true
}

	section(title: "More options", hidden: hideOptionsSection(), hideable: true) {

		def timeLabel = timeIntervalLabel()

		href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null

		input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
			options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]

		input "modes", "mode", title: "Only when mode is", multiple: true, required: false
	}
}

}

def configureButton1() {
dynamicPage(name: “configureButton1”, title: “Configure Play Buttons (Button 1: Push for iTunes, Hold for Spotify)”,
nextPage: “configureButton2”, uninstall: configured(), getButtonSections(1))
}
def configureButton2() {
dynamicPage(name: “configureButton2”, title: “Configure Pause Buttons (Button 2: Push for iTunes, Hold for Spotify)”,
nextPage: “configureButton3”, uninstall: configured(), getButtonSections(2))
}

def configureButton3() {
dynamicPage(name: “configureButton3”, title: “Configure Prev Buttons (Button 3: Push for iTunes, Hold for Spotify)”,
nextPage: “configureButton4”, uninstall: configured(), getButtonSections(3))
}
def configureButton4() {
dynamicPage(name: “configureButton4”, title: “Configure Skip Buttons (Button 4: Push for iTunes, Hold for Spotify)”,
install: true, uninstall: true, getButtonSections(4))
}

def getButtonSections(buttonNumber) {
return {
section(“Send this command to HAM Bridge”) {
input “HAMBcommand_${buttonNumber}pushed", “text”, title: “Pushed Command to send”, multiple: false, required: false
input "HAMBcommand
${buttonNumber}held", “text”, title: “Held Command to send”, multiple: false, required: false
input “server”, “text”, title: “Server IP”, description: “Your HAM Bridger Server IP”, required: true
input “port”, “number”, title: “Port”, description: “Port Number”, required: true
}
section(“Lights”) {
input "lights
${buttonNumber}pushed", “capability.switch”, title: “Pushed”, multiple: true, required: false
input "lights
${buttonNumber}held", “capability.switch”, title: “Held”, multiple: true, required: false
}
section(“Locks”) {
input "locks
${buttonNumber}pushed", “capability.lock”, title: “Pushed”, multiple: true, required: false
input "locks
${buttonNumber}held", “capability.lock”, title: “Held”, multiple: true, required: false
}
section(“Sonos”) {
input "sonos
${buttonNumber}pushed", “capability.musicPlayer”, title: “Pushed”, multiple: true, required: false
input "sonos
${buttonNumber}held", “capability.musicPlayer”, title: “Held”, multiple: true, required: false
}
section(“Modes”) {
input "mode
${buttonNumber}pushed", “mode”, title: “Pushed”, required: false
input "mode
${buttonNumber}held", “mode”, title: “Held”, required: false
}
def phrases = location.helloHome?.getPhrases()*.label
if (phrases) {
section(“Hello Home Actions”) {
log.trace phrases
input "phrase
${buttonNumber}pushed", “enum”, title: “Pushed”, required: false, options: phrases
input "phrase
${buttonNumber}held", “enum”, title: “Held”, required: false, options: phrases
}
}
section(“Sirens”) {
input "sirens
${buttonNumber}pushed",“capability.alarm” ,title: “Pushed”, multiple: true, required: false
input "sirens
${buttonNumber}_held”, “capability.alarm”, title: “Held”, multiple: true, required: false
}

	section("Custom Message") {
		input "textMessage_${buttonNumber}", "text", title: "Message", required: false
	}

    section("Push Notifications") {
        input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false
        input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false
    }

    section("Sms Notifications") {
        input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false
        input "phone_${buttonNumber}_held", "phone", title: "Held", required: false
    }
}

}

def installed() {
initialize()
}

def updated() {
unsubscribe()
initialize()
}

def initialize() {
subscribe(buttonDevice, “button”, buttonEvent)
}

def configured() {
return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4)
}

def buttonConfigured(idx) {
return settings[“HAMBcommand_$idx_pushed”] ||
settings[“lights_$idx_pushed”] ||
settings[“locks_$idx_pushed”] ||
settings[“sonos_$idx_pushed”] ||
settings[“mode_$idx_pushed”] ||
settings[“notifications_$idx_pushed”] ||
settings[“sirens_$idx_pushed”] ||
settings[“notifications_$idx_pushed”] ||
settings[“phone_$idx_pushed”]
}

def buttonEvent(evt){
if(allOk) {
def buttonNumber = evt.data // why doesn’t jsonData work? always returning [:]
def value = evt.value
log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)"
log.debug “button: $buttonNumber, value: $value”

	def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data}
	log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds"

	if(recentEvents.size <= 1){
		switch(buttonNumber) {
			case ~/.*1.*/:
				executeHandlers(1, value)
				break
			case ~/.*2.*/:
				executeHandlers(2, value)
				break
			case ~/.*3.*/:
				executeHandlers(3, value)
				break
			case ~/.*4.*/:
				executeHandlers(4, value)
				break
		}
	} else {
		log.debug "Found recent button press events for $buttonNumber with value $value"
	}
}

}

def executeHandlers(buttonNumber, value) {
log.debug “executeHandlers: $buttonNumber - $value”

def HAMBcommand = find('HAMBcommand', buttonNumber, value)

def lights = find('lights', buttonNumber, value)
if (lights != null) toggle(lights)

def locks = find('locks', buttonNumber, value)
if (locks != null) toggle(locks)

def sonos = find('sonos', buttonNumber, value)
if (sonos != null) toggle(sonos)

def mode = find('mode', buttonNumber, value)
if (mode != null) changeMode(mode)

def phrase = find('phrase', buttonNumber, value)
if (phrase != null) location.helloHome.execute(phrase)

def textMessage = findMsg('textMessage', buttonNumber)

def notifications = find('notifications', buttonNumber, value)
if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" )

def phone = find('phone', buttonNumber, value)
if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed")

def sirens = find('sirens', buttonNumber, value)
if (sirens != null) toggle(sirens)

}

def find(type, buttonNumber, value) {
def preferenceName = type + “" + buttonNumber + "” + value
def pref = settings[preferenceName]
if(pref != null) {
log.debug “Found: $pref for $preferenceName”
}

return pref

}

def findMsg(type, buttonNumber) {
def preferenceName = type + “_” + buttonNumber
def pref = settings[preferenceName]
if(pref != null) {
log.debug “Found: $pref for $preferenceName”
}

return pref

}

def toggle(devices) {
log.debug “toggle: $devices = ${devices*.currentValue(‘switch’)}”

if (devices*.currentValue('switch').contains('on')) {
	devices.off()
}
else if (devices*.currentValue('switch').contains('off')) {
	devices.on()
}
else if (devices*.currentValue('lock').contains('locked')) {
	devices.unlock()
}
else if (devices*.currentValue('lock').contains('unlocked')) {
	devices.lock()
}
else if (devices*.currentValue('alarm').contains('off')) {
    devices.siren()
}
else {
	devices.on()
}

}

def changeMode(mode) {
log.debug “changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes”

if (location.mode != mode && location.modes?.find { it.name == mode }) {
	setLocationMode(mode)
}

}

def doHAMB() {
def ip = “${settings.server}:${settings.port}“sendHubCommand(new physicalgraph.device.HubAction(””“GET /?${settings.HAMBcommand} HTTP/1.1\r\nHOST: $ip\r\n\r\n”"", physicalgraph.device.Protocol.LAN))
}

// execution filter methods
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, location.timeZone).time
def stop = timeToday(ending, location.timeZone).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 hideOptionsSection() {
(starting || ending || days || modes) ? false : true
}

private timeIntervalLabel() {
(starting && ending) ? hhmm(starting) + “-” + hhmm(ending, “h:mm a z”) : “”
}

================================================================================================

I’ve named my HAM Commands iTunesPlay, SpotifyPlay, iTunesPrev… etc, but that is not important as anyone can configure their own. I haven’t included airplay commands as I’ve used Airfoil for that (that’s where I get separate Play and Pause GETs for Spotify.

Hope it works, if not I’m stuck for ideas.

Thanks for the help and HAM Bridge @scottinpollock

Sorry, it doesn’t work.

As a workaround I’ve decided to use one of Scottin Pollock’s modified Alexa momentary switches for each of play/pause, skip, previous and for iTunes and Spotify. Then used button controller+ to associate each of the media controller buttons with the required command. End result is working media remote which can be used by Alexa, Harmony and Siri via Homebridge so I guess I can live with having separate buttons in my list.

I’ve figured out a workaround which allows control of iTunes and Spotify via HAM Bridge using Amazon Echo, Logitech Harmony and Smartthings (both for automation and with a Simulated Media Remote in the App). I’ve knocked together a quick guide in the readme.md in a repository in my GitHub (https://github.com/ijaspley/HAM-Bridge-Smartthings-Media-Control). Enjoy.

I see you have stated there was a plan on creating a windows version of this. I am highly anticipating this and would be very grateful for such. I have a windows server running iTunes and I would like to use that as my system to stream music from, especially Apple Music service. Looking forward to an update!

1 Like