[OBSOLETE] Advanced Button Controller (ABC)

Hi

I can certainly revisit the code and see what I can do. I didn’t write the original but I am maintaining the code now.
Can you post a picture of the the controller and list what options you would like each button to do and I will have a look for you.

ABC and my DTH are only tested on the old SmartThings App

Hi,

I’ll take a look and ensure the code is working correctly although I’m using Hue Dimmer switches so should be ok, especially if their working on my DTH too.

I’ll come back to you when I have checked everything out, hopefully by end of weekend.

ABC and my DTH are only tested on the old SmartThings App

#####Please Check####
Can you check that in the setting for the dimer switch the Button Names option is set to use 1,2,3,4 (blue) as this will stop the ABC Controller working if it is unset (grey)

I’ve altered the code to give an option of using off or not using off. The fan will now toggle through 15 /50 & 90% then cycle back to 15% etc.

Simplified code…
setLevel(15)
setLevel(50)
setLevel(90)
if (fanIgnoreOff)
{ off}
else
{setLevel(15) }

I can send you a copy of the code if you wish to test. You will just have to paste it over the downloaded code in the editor

Problem: Can a command be separated from a button press in abc???
I use smart lighting with a motion detector in my bathroom to turn on a light and then set it to turn off after motion stops.
This same light is a switch with scene control, which I’ve set to turn off all lights with the single button press of the switch. This is very convenient when leaving the bathroom as my Doberman loves turning on all the lights because he can. There are three other lights in the bathroom.
Problem is when the smart lighting detects motion stoped, it tuns off all the lights in the bathroom and not just the single light. Same goes if I turn off the light thru SmartThings app.
Can a command be separated from a button press in abc? So a command would just turn off the light and not turn off all the lights as a button press does?

Can ABC be updated to function properly in the new SmartThings app, works great in classic app…

Is there any alternative to ABC currently? Has it been abandoned? The new Sonos integration ‘broke’ the ability to use ABC to control Sonos as the ‘Music Player’ feature has been deprecated.

The new commands for Media Playback are apparently not understood by ABC or even webcore at this point.

I have two buttons with 4 functions that no longer work. :frowning:

I was using play/pause, vol up and down and , next track. I have no idea how to accomplish that now with my Zwave buttons…

I was making some changes to the ABC SmartApp to make it working with the new Sonos DTH.

You need to change a few things in the child app.

I’ve made the volume and play/pause working for myself, but the next track shouldn’t be a big issue neither.

Let me get back to you later.

@hypyke, I’ve updated the code, but need to test first on my Sonos. I will post here after I’ve done that. (I’ve added the previous track option too, it might be useful.)

Rock!
I don’t mind testing as well.

@hypyke, here we go…

This is the Main SmartApp, this is to Save and Publish.

/*
 *	Advanced Button Controller (Parent/Child Version)
 *
 *	Author: Stephan Hackett, updated Gabor Szabados
 * 
 *
 * 6/20/17 - fixed missing subs for notifications
 * 1/07/18 - split smartApp into Parent/Child (IOS hanging on intial startup) - requires complete uninstall and reinstall of Parent and child SmartApps
 * 1/14/18a - updated version check code
 * 9/19/19 - updated voluem control, play/pause, next/previous track and mute/unmute for the 
 *			new capabilities of the Sonos speakers.
 */

definition(
    name: "ABC Manager Sonos Update",
    namespace: "gszabados",
    singleInstance: true,
    author: "Stephan Hackett, updated Gabor Szabados",
    description: "Configure devices with buttons like the Aeon Labs Minimote and Lutron Pico Remotes.",
    category: "My Apps",
    iconUrl: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abcNew.png",
    iconX2Url: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abcNew.png",
    iconX3Url: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abcNew.png",
)

preferences {
	page(name: "mainPage")
    page(name: "aboutPage")
	
}

def mainPage() {
	return dynamicPage(name: "mainPage", title: "", install: true, uninstall: true) {
    	def childApps = getAllChildApps()
        def childVer = "InitialSetup"
        if(childApps.size() > 0) {
        	childVer = childApps.first().version()
        }
        section("Create a new button device mapping.") {
            app(name: "childApps", appName: "ABC Child Creator Sonos Update", namespace: "gszabados", title: "New Button Device Mapping", multiple: true)
        }
        section("Version Info, User's Guide") {
       	href (name: "aboutPage", title: "Advanced Button Controller \n"+childVer, 
       		description: "Tap to get Smartapp Info and User's Guide.",
       		image: verImgCheck(childVer), required: false, // check repo for image that matches current version. Displays update icon if missing
       		page: "aboutPage"
		)		
   		}
        remove("Uninstall ABC App","WARNING!!","This will remove the ENTIRE SmartApp, including all configs listed above.")
    }
}

def verImgCheck(childVer){
	def params = [
    	uri: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abc_${childVer}.png",
	]
	try {
   		httpGet(params) { resp ->
        	resp.headers.each {
           	//log.debug "${it.name} : ${it.value}"
        	}
            log.debug "ABC appears to be running the latest Version"
        	return params.uri
    	}
	} catch (e) {
    	log.error "ABC does not appear to be the latest version: Please update from IDE"
    	return "https://cdn.rawgit.com/stephack/ABC/master/resources/images/update.png"
	}
}

def installed() {
	initialize()
}

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

def initialize() {

}

def aboutPage() {
	dynamicPage(name: "aboutPage", title: none){
        textHelp()
	}
}

private def textHelp() {
	def text =
	section("User's Guide - Advanced Button Controller") {
    	paragraph "This smartapp allows you to use a device with buttons including, but not limited to:\n\n  Aeon Labs Minimotes\n"+
    	"  HomeSeer HS-WD100+ switches**\n  HomeSeer HS-WS100+ switches\n  Lutron Picos***\n\n"+
		"It is a heavily modified version of @dalec's 'Button Controller Plus' which is in turn"+
        " a version of @bravenel's 'Button Controller+'."
   	}
	section("Some of the included changes are:"){
        paragraph "A complete revamp of the configuration flow. You can now tell at a glance, what has been configured for each button."+
        "The button configuration page has been collapsed by default for easier navigation."
        paragraph "The original apps were hardcoded to allow configuring 4 or 6 button devices."+
        " This app will automatically detect the number of buttons on your device or allow you to manually"+
        " specify (only needed if device does not report on its own)."
		paragraph "Allows you to give your buton device full speaker control including: Play/Pause, NextTrack, Mute, VolumeUp/Down."+
    	"(***Standard Pico remotes can be converted to Audio Picos)\n\nThe additional control options have been highlighted below."
	}
	section("Available Control Options are:"){
        paragraph "	Switches - Toggle \n"+
        "	Switches - Turn On \n"+
        "	Switches - Turn Off \n"+
        "	Dimmers - Toggle \n"+
        "	Dimmers - Set Level (Group 1) \n"+
        "	Dimmers - Set Level (Group 2) \n"+
        "	*Dimmers - Inc Level \n"+
        "	*Dimmers - Dec Level \n"+
        "	Fans - Low, Medium, High, Off \n"+
        "	Shades - Up, Down, or Stop \n"+
        "	Locks - Unlock Only \n"+
        "	Speaker - Play/Pause \n"+
        "	*Speaker - Next Track \n"+
        "	*Speaker - Previous Track \n"+
        "	*Speaker - Mute/Unmute \n"+
        "	*Speaker - Volume Up \n"+
        "	*Speaker - Volume Down \n"+
        "	Set Modes \n"+
        "	Run Routines \n"+
        "	Sirens - Toggle \n"+
        "	Push Notifications \n"+
        "	SMS Notifications"
	}
	section ("** Quirk for HS-WD100+ on Button 5 & 6:"){
        paragraph "Because a dimmer switch already uses Press&Hold to manually set the dimming level"+
        " please be aware of this operational behavior. If you only want to manually change"+
        " the dim level to the lights that are wired to the switch, you will automatically"+
        " trigger the 5/6 button event as well. And the same is true in reverse. If you"+ 
        " only want to trigger a 5/6 button event action with Press&Hold, you will be manually"+
        " changing the dim level of the switch simultaneously as well.\n"+
        "This quirk doesn't exist of course with the HS-HS100+ since it is not a dimmer."
	}
	section("*** Lutron Pico Requirements:"){
        paragraph "Lutron Picos are not natively supported by SmartThings. A Lutron SmartBridge Pro, a device running @njschwartz's python script (or node.js) and the Lutron Caseta Service Manager"+
    	" SmartApp are also required for this functionality!\nSearch the forums for details."
	}
}

@hypyke, this is the second part…

This is the Child SmartApp to save ONLY.

DO NOT PUBLISH!

/*	DO NOT PUBLISH !!!!
 *
 *	Child Creator - Advanced Button Controller
 *
 *	Author: SmartThings, modified by Bruce Ravenel, Dale Coffing, Stephan Hackett, updated Gabor Szabados
 * 
 *
 * 6/20/17 - fixed missing subs for notifications
 * 1/14/18 - updated Version check code
 * 1/15/18 - added icon support for Inovelli Switches (NZW30S and NZW31S)
 *		   - small adjustments to "Configure Button" page layout
 * 1/28/18 - Added Icons and details for Remotec ZRC-90US Button Controller.
 * 2/08/18 - reformatted Button Config Preview
 * 2/12/18 - re-did getDescription() to only diplay Pushed/Held preview if it exists
 *			restructured detailsMap and button config build for easy editting
 *			made subValue inputs "hidden" and "required" when appropriate
 * 9/19/19 - updated voluem control, play/pause, next/previous track and mute/unmute for the 
 *			new capabilities of the Sonos speakers.
 *
 *
 *	DO NOT PUBLISH !!!!
 */
def version(){"v0.2.190919"}

definition(
    name: "ABC Child Creator Sonos Update",
    namespace: "gszabados",
    author: "Stephan Hackett, updated Gabor Szabados",
    description: "SHOULD NOT BE PUBLISHED",
    category: "My Apps",
    parent: "gszabados:ABC Manager Sonos Update",
    iconUrl: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abcNew.png",
    iconX2Url: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abcNew.png",
    iconX3Url: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/abcNew.png",
)

preferences {
	page(name: "chooseButton")
	page(name: "configButtonsPage")
	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 chooseButton() {
	dynamicPage(name: "chooseButton", install: true, uninstall: true) {
		section("Step 1: Select Your Button Device") {
			input "buttonDevice", "capability.button", title: "Button Device", multiple: false, required: true, submitOnChange: true
		}
        if(buttonDevice){
        	state.buttonType =  buttonDevice.typeName
            if(state.buttonType.contains("Aeon Minimote")) state.buttonType =  "Aeon Minimote"
            log.debug "Device Type is now set to: "+state.buttonType
            state.buttonCount = manualCount?: buttonDevice.currentValue('numberOfButtons')
            //if(state.buttonCount==null) state.buttonCount = buttonDevice.currentValue('numButtons')	//added for kyse minimote(hopefully will be updated to correct attribute name)
            section("Step 2: Configure Your Buttons"){
            	if(state.buttonCount<1) {
                	paragraph "The selected button device did not report the number of buttons it has. Please specify in the Advanced Config section below."
                }
                else {
                	for(i in 1..state.buttonCount){
                		href "configButtonsPage", title: "Button ${i}", state: getDescription(i)!="Tap to configure"? "complete": null, description: getDescription(i), params: [pbutton: i]
                    }
            	}
            }
		}
        section("Set Custom Name (Optional)") {
        	label title: "Assign a name:", required: false
        }
        section("Advanced Config:", hideable: true, hidden: hideOptionsSection()) {
            	input "manualCount", "number", title: "Set/Override # of Buttons?", required: false, description: "Only set if DTH does not report", submitOnChange: true
                input "collapseAll", "bool", title: "Collapse Unconfigured Sections?", defaultValue: true
                input "hwSpecifics", "bool", title: "Hide H/W Specific Details?", defaultValue: false
			}
        section(title: "Only Execute When:", hideable: true, hidden: hideOptionsSection()) {
			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 configButtonsPage(params) {
	if (params.pbutton != null) state.currentButton = params.pbutton.toInteger()
	dynamicPage(name: "configButtonsPage", title: "CONFIGURE BUTTON ${state.currentButton}:\n${state.buttonType}", getButtonSections(state.currentButton))
}

def getButtonSections(buttonNumber) {
	return {
    	def picNameNoSpace = "${state.buttonType}${state.currentButton}.png"-" "-" "-" "-"/"-"-"
        //log.debug picNameNoSpace
        section(){	//"Hardware specific info on button selection:") {
			if(hwSpecifics== false) paragraph image: "https://cdn.rawgit.com/stephack/ABC/master/resources/images/${picNameNoSpace}", "${getSpecText()}"
    	}
        def myDetail
        for(i in 1..17) {//Build 1st 17 Button Config Options
        	myDetail = getPrefDetails().find{it.sOrder==i}
        	section(myDetail.secLabel, hideable: true, hidden: !(shallHide("${myDetail.id}${buttonNumber}") || shallHide("${myDetail.sub}${buttonNumber}"))) {
				input "${myDetail.id}${buttonNumber}_pushed", myDetail.cap, title: "When Pushed", multiple: true, required: false, submitOnChange: collapseAll
				if(myDetail.sub && isReq("${myDetail.id}${buttonNumber}_pushed")) input "${myDetail.sub}${buttonNumber}_pushed", "number", title: myDetail.sTitle, multiple: false, required: isReq("${myDetail.id}${buttonNumber}_pushed"), description: myDetail.sDesc
				if(showHeld()) input "${myDetail.id}${buttonNumber}_held", myDetail.cap, title: "When Held", multiple: true, required: false, submitOnChange: collapseAll
				if(myDetail.sub && isReq("${myDetail.id}${buttonNumber}_held")) input "${myDetail.sub}${buttonNumber}_held", "number", title: myDetail.sTitle, multiple: false, required: isReq("${myDetail.id}${buttonNumber}_held"), description: myDetail.sDesc
			}
        	if(i==3 || i==8 || i==13 || i==17) section(" "){}
        }        
		section("Set Mode", hideable: true, hidden: !shallHide("mode_${buttonNumber}")) {
			input "mode_${buttonNumber}_pushed", "mode", title: "When Pushed", required: false, submitOnChange: collapseAll
			if(showHeld())input "mode_${buttonNumber}_held", "mode", title: "When Held", required: false, submitOnChange: collapseAll
		}
		def phrases = location.helloHome?.getPhrases()*.label
		if (phrases) {
        	section("Run Routine", hideable: true, hidden: !shallHide("phrase_${buttonNumber}")) {
				//log.trace phrases
				input "phrase_${buttonNumber}_pushed", "enum", title: "When Pushed", required: false, options: phrases, submitOnChange: collapseAll
				if(showHeld()) input "phrase_${buttonNumber}_held", "enum", title: "When Held", required: false, options: phrases, submitOnChange: collapseAll
			}
		}
        section("Notifications:\nSMS, In App or Both", hideable: true, hidden: !shallHide("notifications_${buttonNumber}")) {
        paragraph "****************\nWHEN PUSHED\n****************"
			input "notifications_${buttonNumber}_pushed", "text", title: "Message", description: "Enter message to send", required: false, submitOnChange: collapseAll
            input "phone_${buttonNumber}_pushed","phone" ,title: "Send Text To", description: "Enter phone number", required: false, submitOnChange: collapseAll
            input "valNotify${buttonNumber}_pushed","bool" ,title: "Notify In App?", required: false, defaultValue: false, submitOnChange: collapseAll
            if(showHeld()) { paragraph "*************\nWHEN HELD\n*************"
				input "notifications_${buttonNumber}_held", "text", title: "Message", description: "Enter message to send", required: false, submitOnChange: collapseAll
				input "phone_${buttonNumber}_held", "phone", title: "Send Text To", description: "Enter phone number", required: false, submitOnChange: collapseAll
				input "valNotify${buttonNumber}_held", "bool", title: "Notify In App?", required: false, defaultValue: false, submitOnChange: collapseAll
            }
		}
        if(enableSpec()){
        	section(" "){}
			section("Special", hideable: true, hidden: !shallHide("container_${buttonNumber}")) {
				input "container_${buttonNumber}_pushed", "device.VirtualContainer", title: "When Pushed", required: false, submitOnChange: collapseAll
				if(showHeld())input "container_${buttonNumber}_held", "device.VirtualContainer", title: "When Held", required: false, submitOnChange: collapseAll
			}
        }
	}
}

def enableSpec() {
	return false
}

def showHeld() {
	if(state.buttonType.contains("100+ ")) return false
	else return true
}

def shallHide(myFeature) {
	if(collapseAll) return (settings["${myFeature}_pushed"]||settings["${myFeature}_held"]||settings["${myFeature}"])
	return true
}

def isReq(myFeature) {
    (settings[myFeature])? true : false
}

def getDescription(dNumber) {
    def descript = ""
    if(!(settings.find{it.key.contains("_${dNumber}_")})) return "Tap to configure"
    if(settings.find{it.key.contains("_${dNumber}_pushed")}) descript = "\nPUSHED:"+getDescDetails(dNumber,"_pushed")+"\n"
    if(settings.find{it.key.contains("_${dNumber}_held")}) descript = descript+"\nHELD:"+getDescDetails(dNumber,"_held")+"\n"
    //if(anySettings) descript = "PUSHED:"+getDescDetails(dNumber,"_pushed")+"\n\nHELD:"+getDescDetails(dNumber,"_held")//"CONFIGURED : Tap to edit"
	return descript
}

def getDescDetails(bNum, type){
	def numType=bNum+type
	def preferenceNames = settings.findAll{it.key.contains("_${numType}")}.sort()		//get all configured settings that: match button# and type, AND are not false
    if(!preferenceNames){
    	return " **Not Configured** "
    }
    else {
    	def formattedPage =""
    	preferenceNames.each {eachPref->
        	def prefDetail = getPrefDetails().find{eachPref.key.contains(it.id)}	//gets decription of action being performed(eg Turn On)
        	def prefDevice = " : ${eachPref.value}" - "[" - "]"											//name of device the action is being performed on (eg Bedroom Fan)
            def prefSubValue = settings[prefDetail.sub + numType]?:"(!Missing!)"
            if(prefDetail.type=="normal") formattedPage += "\n- ${prefDetail.desc}${prefDevice}"
            if(prefDetail.type=="hasSub") formattedPage += "\n- ${prefDetail.desc}${prefSubValue}${prefDevice}"
            if(prefDetail.type=="bool") formattedPage += "\n- ${prefDetail.desc}"
    	}
		return formattedPage
    }
}

def installed() {
	initialize()
}

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

def initialize() {
    log.debug "INITIALIZED with settings: ${settings}"
	app.label==app.name?app.updateLabel(defaultLabel()):app.updateLabel(app.label)
	subscribe(buttonDevice, "button", buttonEvent)
    state.lastshadesUp = true
}

def defaultLabel() {
	return "${buttonDevice} Mapping"
}

def getPrefDetails(){
	def detailMappings =
    	[[id:'lightOn_', sOrder:1, desc:'Turn On ', comm:turnOn, type:"normal", secLabel: "Switches (Turn On)", cap: "capability.switch"],
         [id:'lights_', sOrder:3, desc:'Toggle On/Off', comm:toggle, type:"normal", secLabel: "Switches (Toggle On/Off)", cap: "capability.switch"],
     	 [id:"lightOff_", sOrder:2, desc:'Turn Off', comm:turnOff, type:"normal", secLabel: "Switches (Turn Off)", cap: "capability.switch"],
     	 [id:"lightDim_", sOrder:4, desc:'Dim to ', comm:turnDim, sub:"valLight", type:"hasSub", secLabel: "Dimmers (On to Level - Group 1)", cap: "capability.switchLevel", sTitle: "Bright Level", sDesc:"0 to 100%"],
     	 [id:"lightD2m_", sOrder:5, desc:'Dim to ', comm:turnDim, sub:"valLight2", type:"hasSub", secLabel: "Dimmers (On to Level - Group 2)", cap: "capability.switchLevel", sTitle: "Bright Level", sDesc:"0 to 100%"],
         [id:'dimPlus_', sOrder:6, desc:'Brightness +', comm:levelUp, sub:"valDimP", type:"hasSub", secLabel: "Dimmers (Increase Level By)", cap: "capability.switchLevel", sTitle: "Increase by", sDesc:"0 to 15"],
     	 [id:'dimMinus_', sOrder:7, desc:'Brightness -', comm:levelDown, sub:"valDimM", type:"hasSub", secLabel: "Dimmers (Decrease Level By)", cap: "capability.switchLevel", sTitle: "Decrease by", sDesc:"0 to 15"],
         [id:'lightsDT_', sOrder:8, desc:'Toggle Off/Dim to ', comm:dimToggle, sub:"valDT", type:"hasSub", secLabel: "Dimmers (Toggle OnToLevel/Off)", cap: "capability.switchLevel", sTitle: "Bright Level", sDesc:"0 to 100%"],
         [id:"speakerpp_", sOrder:9, desc:'Toggle Play/Pause', comm:speakerplaystate, type:"normal", secLabel: "Speakers (Toggle Play/Pause)", cap: "capability.mediaPlayback"],
     	 [id:'speakervu_', sOrder:10, desc:'Volume +', comm:levelUp, sub:"valSpeakU", type:"hasSub", secLabel: "Speakers (Increase Vol By)", cap: "capability.audioVolume", sTitle: "Increase by", sDesc:"0 to 15"],
     	 [id:"speakervd_", sOrder:11, desc:'Volume -', comm:levelDown, sub:"valSpeakD", type:"hasSub", secLabel: "Speakers (Decrease Vol By)", cap: "capability.audioVolume", sTitle: "Decrease by", sDesc:"0 to 15"],
         [id:'speakernt_', sOrder:12, desc:'Next Track', comm:speakernexttrack, type:"normal", secLabel: "Speakers (Go to Next Track)", cap: "capability.mediaTrackControl"],
         [id:'speakerpt_', sOrder:13, desc:'Previous Track', comm:speakerprevioustrack, type:"normal", secLabel: "Speakers (Go to Previous Track)", cap: "capability.mediaTrackControl"],
    	 [id:'speakermu_', sOrder:14, desc:'Mute', comm:speakermute, type:"normal", secLabel: "Speakers (Toggle Mute/Unmute)", cap: "capability.audioMute"],
         [id:'sirens_', sOrder:15, desc:'Toggle', comm:toggle, type:"normal", secLabel: "Sirens (Toggle)", cap: "capability.alarm"],
     	 [id:"locks_", sOrder:16, desc:'Lock', comm:setUnlock, type:"normal", secLabel: "Locks (Lock Only)", cap: "capability.lock"],
     	 [id:"fanAdjust_", sOrder:17,desc:'Adjust', comm:adjustFan, type:"normal", secLabel: "Fans (Adjust - Low, Medium, High, Off)", cap: "capability.switchLevel"],
     	 [id:"shadeAdjust_", sOrder:18,desc:'Adjust', comm:adjustShade, type:"normal", secLabel: "Shades (Adjust - Up, Down, or Stop)", cap: "capability.doorControl"],
     	 [id:"mode_", desc:'Set Mode', comm:changeMode, type:"normal"],
     	 [id:"phrase_", desc:'Run Routine', comm:runRout, type:"normal"],
		 [id:"notifications_", desc:'Send Push Notification', comm:messageHandle, sub:"valNotify", type:"bool"],
     	 [id:"phone_", desc:'Send SMS', comm:smsHandle, sub:"notifications_", type:"normal"],
         [id:"container_", desc:'Cycle Playlist', comm:cyclePL, type:"normal"],         
        ]
    return detailMappings
}

def buttonEvent(evt) {
	if(allOk) {
    	def buttonNumber = evt.jsonData.buttonNumber
		def pressType = evt.value
		log.debug "$buttonDevice: Button $buttonNumber was $pressType"
    	def preferenceNames = settings.findAll{it.key.contains("_${buttonNumber}_${pressType}")}
    	preferenceNames.each{eachPref->
        	def prefDetail = getPrefDetails()?.find{eachPref.key.contains(it.id)}		//returns the detail map of id,desc,comm,sub
        	def PrefSubValue = settings["${prefDetail.sub}${buttonNumber}_${pressType}"]	//value of subsetting (eg 100)
        	if(prefDetail.sub) "$prefDetail.comm"(eachPref.value,PrefSubValue)
        	else "$prefDetail.comm"(eachPref.value)
    	}
	}
}

def turnOn(devices) {
	log.debug "Turning On: $devices"
	devices.on()
}

def turnOff(devices) {
	log.debug "Turning Off: $devices"
	devices.off()
}

def turnDim(devices, level) {
	log.debug "Dimming (to $level): $devices"
	devices.setLevel(level)
}

def adjustFan(device) {
	log.debug "Adjusting: $device"
	def currentLevel = device.currentLevel
	if(device.currentSwitch == 'off') device.setLevel(15)
	else if (currentLevel < 34) device.setLevel(50)
  	else if (currentLevel < 67) device.setLevel(90)
	else device.off()
}

def adjustShade(device) {
	log.debug "Shades: $device = ${device.currentMotor} state.lastUP = $state.lastshadesUp"
	if(device.currentMotor in ["up","down"]) {
    	state.lastshadesUp = device.currentMotor == "up"
    	device.stop()
    } else {
    	state.lastshadesUp ? device.down() : device.up()
//    	if(state.lastshadesUp) device.down()
//        else device.up()
        state.lastshadesUp = !state.lastshadesUp
    }
}

def speakerplaystate(device) {
	log.debug "Toggling Play/Pause: $device"
	device.currentValue('playbackStatus').contains('playing')? device.stop() : device.play()
}
   
def speakernexttrack(device) {
	log.debug "Next Track Sent to: $device"
	device.nextTrack()
}

def speakerprevioustrack(device) {
	log.debug "Previous Track Sent to: $device"
	device.previousTrack()
} 

def speakermute(device) {
	log.debug "Toggling Mute/Unmute: $device"
	device.currentValue('mute').contains('unmuted')? device.mute() : device.unmute()
} 

def levelUp(device, inclevel) {
	log.debug "Incrementing Level (by +$inclevel: $device"
	def currentVol = device.currentValue('volume')[0]	//currentlevel return a list...[0] is first item in list ie volume level
    def newVol = currentVol + inclevel
  	device.setVolume(newVol)
    log.debug "Level increased by $inclevel to $newVol"
}

def levelDown(device, declevel) {
	log.debug "Decrementing Level (by -declevel: $device"
	def currentVol = device.currentValue('volume')[0]
    def newVol = currentVol.toInteger()-declevel
  	device.setVolume(newVol)
    log.debug "Level decreased by $declevel to $newVol"
}

def setUnlock(devices) {
	log.debug "Locking: $devices"
	devices.lock()
}

def toggle(devices) {
	log.debug "Toggling: $devices"
	if (devices*.currentValue('switch').contains('on')) {
		devices.off()
	}
	else if (devices*.currentValue('switch').contains('off')) {
		devices.on()
	}
	else if (devices*.currentValue('alarm').contains('off')) {
        devices.siren()
    }
	else {
		devices.on()
	}
}

def dimToggle(devices, dimLevel) {
	log.debug "Toggling On/Off | Dimming (to $dimLevel): $devices"
	if (devices*.currentValue('switch').contains('on')) devices.off()
	else devices.setLevel(dimLevel)
}

def runRout(rout){
	log.debug "Running: $rout"
	location.helloHome.execute(rout)
}

def messageHandle(msg, inApp) {
	if(inApp==true) {
    	log.debug "Push notification sent"
    	sendPush(msg)
	}
}

def smsHandle(phone, msg){
    log.debug "SMS sent"
    sendSms(phone, msg ?:"No custom text entered on: $app.label")
}

def changeMode(mode) {
	log.debug "Changing Mode to: $mode"
	if (location.mode != mode && location.modes?.find { it.name == mode }) setLocationMode(mode)
}

def cyclePL(device) {
	//int currPL = device.currentValue('lastRun')
   // int nextPL = currPL+1
    device.cycleChild()
    //device.on(nextPL)

}

// 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).time
		def stop = timeToday(ending).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 || manualCount) ? false : true
}

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

private def textHelp() {
	def text =
	section("User's Guide - Advanced Button Controller") {
    	paragraph "This smartapp allows you to use a device with buttons including, but not limited to:\n\n  Aeon Labs Minimotes\n"+
    	"  HomeSeer HS-WD100+ switches**\n  HomeSeer HS-WS100+ switches\n  Lutron Picos***\n\n"+
		"It is a heavily modified version of @dalec's 'Button Controller Plus' which is in turn"+
        " a version of @bravenel's 'Button Controller+'."
   	}
	section("Some of the included changes are:"){
        paragraph "A complete revamp of the configuration flow. You can now tell at a glance, what has been configured for each button."+
        "The button configuration page has been collapsed by default for easier navigation."
        paragraph "The original apps were hardcoded to allow configuring 4 or 6 button devices."+
        " This app will automatically detect the number of buttons on your device or allow you to manually"+
        " specify (only needed if device does not report on its own)."
		paragraph "Allows you to give your buton device full speaker control including: Play/Pause, NextTrack, Mute, VolumeUp/Down."+
    	"(***Standard Pico remotes can be converted to Audio Picos)\n\nThe additional control options have been highlighted below."
	}
	section("Available Control Options are:"){
        paragraph "	Switches - Toggle \n"+
        "	Switches - Turn On \n"+
        "	Switches - Turn Off \n"+
        "	Dimmers - Toggle \n"+
        "	Dimmers - Set Level (Group 1) \n"+
        "	Dimmers - Set Level (Group 2) \n"+
        "	*Dimmers - Inc Level \n"+
        "	*Dimmers - Dec Level \n"+
        "	Fans - Low, Medium, High, Off \n"+
        "	Shades - Up, Down, or Stop \n"+
        "	Locks - Unlock Only \n"+
        "	Speaker - Play/Pause \n"+
        "	*Speaker - Next Track \n"+
        "	*Speaker - Previous Track \n"+
        "	*Speaker - Mute/Unmute \n"+
        "	*Speaker - Volume Up \n"+
        "	*Speaker - Volume Down \n"+
        "	Set Modes \n"+
        "	Run Routines \n"+
        "	Sirens - Toggle \n"+
        "	Push Notifications \n"+
        "	SMS Notifications"
	}
	section ("** Quirk for HS-WD100+ on Button 5 & 6:"){
        paragraph "Because a dimmer switch already uses Press&Hold to manually set the dimming level"+
        " please be aware of this operational behavior. If you only want to manually change"+
        " the dim level to the lights that are wired to the switch, you will automatically"+
        " trigger the 5/6 button event as well. And the same is true in reverse. If you"+ 
        " only want to trigger a 5/6 button event action with Press&Hold, you will be manually"+
        " changing the dim level of the switch simultaneously as well.\n"+
        "This quirk doesn't exist of course with the HS-HS100+ since it is not a dimmer."
	}
	section("*** Lutron Pico Requirements:"){
        paragraph "Lutron Picos are not natively supported by SmartThings. A Lutron SmartBridge Pro, a device running @njschwartz's python script (or node.js) and the Lutron Caseta Service Manager"+
    	" SmartApp are also required for this functionality!\nSearch the forums for details."
	}
}
  
def getSpecText(){
	if(state.buttonType == "Lutron Pico") {
    	switch (state.currentButton){
        	case 1: return "Top Button"; break
			case 2: return "Bottom Button"; break
			case 3: return "Middle Button";break
			case 4: return "Up Button"; break
			case 5: return "Down Button"; break        
        }
    }
	if(state.buttonType.contains("Aeon Minimote")) {
    	switch (state.currentButton){
        	case 1: return "Top Left Button"; break
			case 2: return "Top Right Button"; break
			case 3: return "Lower Left Button";break
			case 4: return "Lower Right"; break
        }
    }
	if(state.buttonType.contains("WD100+ Dimmer")) {
    	switch (state.currentButton){
        	case 1: return "Double-Tap Upper Paddle"; break
			case 2: return "Double-Tap Lower Paddle"; break
			case 3: return "Triple-Tap Upper Paddle";break
			case 4: return "Triple-Tap Lower Paddle"; break
            case 5: return "Press & Hold Upper Paddle\n(See user guide for quirks)"; break
			case 6: return "Press & Hold Lower Paddle\n(See user guide for quirks)"; break
			case 7: return "Single Tap Upper Paddle\n(See user guide for quirks)"; break
			case 8: return "Single Tap Lower Paddle\n(See user guide for quirks)"; break
        }
    }
    if(state.buttonType.contains("WS100+ Switch")) {
    	switch (state.currentButton){
        	case 1: return "Double-Tap Upper Paddle"; break
			case 2: return "Double-Tap Lower Paddle"; break
			case 3: return "Triple-Tap Upper Paddle";break
			case 4: return "Triple-Tap Lower Paddle"; break
            case 5: return "Press & Hold Upper Paddle"; break
			case 6: return "Press & Hold Lower Paddle"; break
			case 7: return "Single Tap Upper Paddle";break
			case 8: return "Single Tap Lower Paddle"; break
        }
    }
    if(state.buttonType.contains("Inovelli")) {
    	switch (state.currentButton){
        	case 1: return "NOT OPERATIONAL - DO NOT USE"; break
        	case 2: return "2X Tap Upper Paddle = Pushed\n2X Tap Lower Paddle = Held"; break
			case 3: return "3X Tap Upper Paddle = Pushed\n3X Tap Lower Paddle = Held"; break
			case 4: return "4X Tap Upper Paddle = Pushed\n4X Tap Lower Paddle = Held";break
			case 5: return "5X Tap Upper Paddle = Pushed\n5X Tap Lower Paddle = Held"; break
            case 6: return "Hold Upper Paddle = Pushed\nHold Lower Paddle = Held"; break
        }
    }
    if(state.buttonType.contains("ZRC-90")) {
    	switch (state.currentButton){
        	case 1: return "Tap or Hold Button 1"; break
        	case 2: return "Tap or Hold Button 2"; break
			case 3: return "Tap or Hold Button 3"; break
			case 4: return "Tap or Hold Button 4";break
			case 5: return "Tap or Hold Button 5"; break
            case 6: return "Tap or Hold Button 6"; break
            case 7: return "Tap or Hold Button 7"; break
        	case 8: return "Tap or Hold Button 8"; break
			case 9: return "2X Tap Button 1\nHold Not Available"; break
			case 10: return "2X Tap Button 2\nHold Not Available";break
			case 11: return "2X Tap Button 3\nHold Not Available"; break
            case 12: return "2X Tap Button 4\nHold Not Available"; break
            case 13: return "2X Tap Button 5\nHold Not Available"; break
        	case 14: return "2X Tap Button 6\nHold Not Available"; break
			case 15: return "2X Tap Button 7\nHold Not Available"; break
			case 16: return "2X Tap Button 8\nHold Not Available";break			
        }
    }
    return "Not Specified By Device"
}


/*FOR NEW INPUTS//////////////
1. add input to config
2. add info to detailMappings including subvalue if needed
3. ensure correct type is used in map..or create a new one with its own formattedPage



FOR NEW BUTTON DEVICE TYPES///////////////
1. ensure device reports buttonNumber
2. if not, add sendEvent to DTH as needed OR just enter manually
3. add any special instructions to getSpecText() using dth name
4. create pics for each button using dthName+dNumber






*/

It should work as the previous version was working, but the previous musicPlayer features have been replaced by the new capabilities of the new LAN Sonos Player WebSocket and the previous track option has been added.

If you want to use the buttons with any device which has the musicPlayer capability, then use the original version of the SmartApp. This is differentiated by the name, as Sonos Update has been added to the name.

Please test it, and let me know if the changed features are not working.

@Paul_Sheldon, do you want to have a look? You could add it to your GitHub repo. I’ve updated the original version of the SmartApp, but not yours with the Color Bulb part.

@GSzabados

Yeah man, Yeah… It seems to work perfectly so far.

Thank you good sir. You are gold.

@hypyke

Yes thank you I’ll review , did you branch of my master and do changes as you can then send a request for me to merge them in. That way we keep only one version live. If not I’ll do a manual merge so everyone will get the updated version.

I also have the Fan Control code to upload too

Regards

Paul

1 Like

@Paul_Sheldon It was @GSzabados who did the updates.

I think his changes were to REPLACE the music player functionality which, even though deprecated now it seems would still be needed by some folks. A version with both music and media player would be ideal for an official ABC release I would think.

Just what I gleaned from what was written. @GSzabados would know better than I.

Just don’t want other users of ABC to get boned. :slight_smile:

Hi Paul,

I haven’t read through the whole thread when grabbed ABC for myself. So it is based on the original code. I’ve just notices your updates from 6 month ago regarding the Hue Color bulbs.

So, a manual update/fork would be required. As I wrote this is for the new Sonos. If anyone use and old DHT, which uses the musicPlayer, then this code is not backward compatible.

Thanks, I’ll look to see if I can add a toggle for Music Player / Sonos and incorporte both

It is not only the capability, but the command has changed too for the volume control. Plus I added the previous track option to the list.

You can get over the volume one if you put an if statement into the command part with the capability in question.

I’ve mostly changed these things only for myself to make my buttons working. It was second that someone asked for it. But why not share the love…

I’ll have a go this weekend and then maybe you could test it for me to see if your side still works

Ok! Send me a PM from where I should get the code. :+1: