[OBSOLETE] Almond Click DTH

I have also only been able to get it to work with a single button click, using Button Controller. Though the product description says it supports hold and double press, the simulator logs do not show any difference in the commands the button actually sends out for these events, so I’m not sure how they are handled. But I am also a noob at making device handlers. No matter how I manipulate the button, the only string it sends is:

catchall: 0104 0501 01 FF 0140 00 8563 01 00 0000 00 00 0308303030303030303001

Since it does not seem to send a separate signal on button release, or a different one for double click, how one would program this for more than a single click is beyond me.

Edit: Oops, I lied. I just hadn’t held the button long enough, or maybe too long, whatever. The held string is:

catchall: 0104 0501 01 FF 0140 00 8563 01 00 0000 00 00 0208303030303030303001

Double click is:

catchall: 0104 0501 01 FF 0140 00 8563 01 00 0000 00 00 0008303030303030303001

So with that in mind, it should be pretty easy to integrate those into a more diverse device handler. But of course, Button Controller only handles single press and hold inputs, so you would also need to update Button Controller to handle the double click behavior. I will take a look and see if I can figure it out… but like I said, I am a noob at this!

1 Like

Ok, here’s what I’ve got. Crude but seemingly effective. Like I said, Button Controller does not support “double pushed” behavior at this time, but this should send the command at least, so that Button Controller can be updated to support it. This will handle push and hold behaviors.

/**
 *  Almond Click Button
 *
 *  Copyright 2017 Gene Eilebrecht
 *
 *  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: "Almond Click", namespace: "geneiparq", author: "Gene Eilebrecht") {
        capability "Actuator"
		capability "Button"
		capability "Holdable Button"

		fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0003,0500", outClusters: "0003,0501", model:"ZB2-BU01", manufacturer: "Securifi Ltd. ZB2-BU01"
	}
    
    simulator {
	}

	tiles {
		standardTile("button", "device.button", width: 2, height: 2) {
			state "default", label: "", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
		}
		main "button"
		details(["button"])
	}
}

def parse(String description) {
	log.debug "parse description: $description"
    def descMap = zigbee.parseDescriptionAsMap(description)
    
    def button = 1
    def results = null
    
    if (description?.substring(56) == '0308303030303030303001'){
		results = createEvent([
    		name: "button",
        	value: "pushed", 
        	data:[buttonNumber: button], 
        	descriptionText: "${device.displayName} button ${button} was pushed",
        	isStateChange: true, 
        	displayed: true
    	])
    } else if (description?.substring(56) == '0208303030303030303001'){
    	results = createEvent([
    		name: "button",
        	value: "held", 
        	data:[buttonNumber: button], 
        	descriptionText: "${device.displayName} button ${button} was held",
        	isStateChange: true, 
        	displayed: true
    	])
    } else if (description?.substring(56) == '0008303030303030303001'){
    	results = createEvent([
    		name: "button",
        	value: "doublepushed", 
        	data:[buttonNumber: button], 
        	descriptionText: "${device.displayName} button ${button} was double pushed",
        	isStateChange: true, 
        	displayed: true
    	])
   	} else {
    	log.debug "unhandled response"
        results = null
    }
            
	return results;
}
4 Likes

Installed and it works with single and long press! That is really all I need. Thank you so much!

2 Likes

EDIT: Hey guys, sorry I have not responded to any of the questions on this topic, I am sadly unable to do so due to this forum’s user settings (new users can only reply 3 times on a topic). To save anyone time who may be reading this thread, the top bit of code I posted in the device handler for the Almond Click. The second bit of code (on this post, see below) is a new version of the Button Controller smart app. So to get all three click behaviors of the Almond Click to work, you will first need to publish the device handler, and assign it to your buttons. Secondly, you will need to publish the new smart app to control what the buttons do. It will allow you to assign click, double click, and hold behaviors to different activities.

As for the question regarding also setting dimming/intensity of lights with a click, that is something I am also interested in and will post an update here if I have time to add it to the button controller app.


You’re welcome! It was an interesting first foray into Groovy coding, device handlers, and smart apps. I have only been using SmartThings for very basic tasks until now, so this opens a lot of doors for me as well. Glad it works for you!

EDIT:

@tbs26

They won’t let new users post more than 3 times, so I have to edit this one instead of making a new one.

Here’s a reworked version of Button Controller that supports the “double pushed” behavior of the Almond Click:

/**
 *  Copyright 2017 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.
 *
 *	Button Controller
 *
 *	Author: SmartThings
 *	Date: 2014-5-21
 *	Updated by Gene Eilebrecht to support "Double Push" for Almond Click smart button
 *	Date: 2017-3-29
 */
definition(
    name: "Button Controller r2",
    namespace: "smartthings",
    author: "SmartThings",
    description: "Control devices with buttons like the Aeon Labs Minimote",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png"
)

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: "First, select your button device", 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: "Now let's decide how to use the first button",
		nextPage: "configureButton2", uninstall: configured(), getButtonSections(1))
}
def configureButton2() {
	dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here",
		nextPage: "configureButton3", uninstall: configured(), getButtonSections(2))
}

def configureButton3() {
	dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here",
		nextPage: "configureButton4", uninstall: configured(), getButtonSections(3))
}
def configureButton4() {
	dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here",
		install: true, uninstall: true, getButtonSections(4))
}

def getButtonSections(buttonNumber) {
	return {
		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
            input "lights_${buttonNumber}_doublepushed", "capability.switch", title: "Double Pushed", 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
            input "locks_${buttonNumber}_doublepushed", "capability.lock", title: "Double Pushed", 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
            input "sonos_${buttonNumber}_doublepushed", "capability.musicPlayer", title: "Double Pushed", multiple: true, required: false
		}
		section("Modes") {
			input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false
			input "mode_${buttonNumber}_held", "mode", title: "Held", required: false
            input "mode_${buttonNumber}_doublepushed", "mode", title: "Double Pushed", 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
                input "phrase_${buttonNumber}_doublepushed", "enum", title: "Double Pushed", 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
            input "sirens_${buttonNumber}_doublepushed", "capability.alarm", title: "Double Pushed", 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
            input "notifications_${buttonNumber}_doublepushed", "bool", title: "Double Pushed", 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
            input "phone_${buttonNumber}_doublepushed", "phone", title: "Double Pushed", 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["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 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)
	}
}

// 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") : ""
}
2 Likes

Hmmm, I published the device handler and selected it for the button, but this won’t work. When I “save” the changes to my device, it won’t remember the new device handler. IE, it wont let me assign button controller r2 as the device handler. Any ideas?

Edit - it’s only your latest version that supports double click that I can’t get to work. I noticed no “capabilities” are present when I try and use this device handler.

I used the older code that only supports single click and long hold, and this seems to work ok.

Remember that one is a device handler and one is the smartapp. I got mixed up on that the first time.

Thanks guys, very helpful!

EDIT: NM, I am a dumbass. I didnt realize Gene’s last post was the App code, and not a DTH code. You can ignore everything below this post. Thanks for the help guys!

Yes, agree. I was only trying to select the device handler, but for some reason, I am unable to select this device handler and save the setting. Is anyone else having this issue? And when I publish the device handler, there are “no capabilities.” I wonder if this is why I cannot assign it to any of my button devices.

Button Controller is a smartapp, not a device type handler. That’s why it doesn’t have any defined capabilities.

This whole thread my be a little confusing so let me clarify: The first set of code is the Almond Click DTH… Create a new Device Handler. The second code it the “Button Controller r2” Smartapp. Create a new Smartapp. Add each button to Smarthing then come back to Hub Manager and assign the Almond Click DTH Type. You will need to do this for each button you add. Then go back to Smartthings and add a Button Controller r2 instance for each physical button and configure the “First Button” for each click type. Ignore the 2nd-4th options. Now each button should work for all 3 click types.

Hey guys. This is very helpful. Got this working after a little while. Thank you.
Some questions if you have time/interest in working on this. I bought these buttons to control a flex LED light that has no switch and is only controllable from the Smartthings app. In both the Samsung app and the native app, there is the ability to control intensity of the light and the color. Right now, I can only use this for a binary - on or off. Thoughts on how I could assign double clicks or a hold to other lighting options so that my kid could choose “night time” or “reading time” light as an option instead of just on or off?

Just wanted to chime in here. You can use webCoRE for this, you will only be able to use press and hold when using CoRE from what I can tell.

Thank you for creating the DTH & SmartApp code! My Iris buttons tuckered out & the family was getting restless. I received & installed the Almond buttons today. The buttons paired up as a “Thing” & updated the device type as described. When you change the device type, Almond Click will be near the bottom (not alpha). I mention this as I had forgotten. Had all three paired & running in less than 10 minutes.

These have the form factor that we wanted. The buttons are slightly smaller than Iris, battery compartment is easy to access.That long hold & double-click also work is a great bonus. Thanks again!!

Anyone having an issue with the buttons sending more then one puch it you hit it once? What it looks like is happening to me is if I hit the button once it with send the command again after a few seconds and if I hold the button it will also send it twice.

I have been experiencing this as well but had not been using the DH and smart app listed here. I just installed them and when I get home I will try again and see if this resolves the issue. Did you figure yours out yet?

I am using both the smart app here and WebCoRE since the double tap does not show as an option in WebCoRE. It seems to only happen to one of my buttons. I am using an other the same way but it does not seem to have an issue.

That correlates to me. I am experiencing it with 1 of 3 buttons now. I am using the “Almond Click” DH and the “Button Controller r2” Smart App and everything is recognized consistently on all but 1 button and about 80% of the time on the 1 problem button. I am getting 1 click, 2 click and Held functions on all 3.

Would still like to get the double tap to work with webcore anyone know how to do this? The only things that show up is press and hold.

Thanks.

Hello,

I would like to get the double press working with this DT so I can use WebCoRE exclusively with these buttons. I would like to use double press for turning on lights at a certain leave, I edited the Smart App but can’t get it to do it like I would be able to in WebCoRE.

@ady624 and @erocm1231, hope you guys would be willing to help out if you have the time.
If you add numberOfButtons attribute like with the ZRC-90 Scene Master would it be possible to do this? I cannot get it to work properly with the DH with my limited know how.

Thank you!

Thanks @Gene_Eilebrecht for the device handler & SmartApp.

For those who want to use webcore, you can reference to this: