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
================================================================================================
/**
*/
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…
================================================================================================
/**
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