Iris Smart Button

Re-format @canalrun 's posted codes for easy view :smile:

 *  Iris Smart Button II
 *  Copyright 2015 Mitch Pond
 *  Edited 2016 canalrun, change press & held to one event
 *  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:
 *  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: "Iris Smart Button II", namespace: "mitchpond", author: "Mitch Pond") {
		capability "Battery"
		capability "Button"
		capability "Configuration"
		capability "Refresh"
		capability "Sensor"
		capability "Temperature Measurement"

		command "test"

		attribute "lastPress", "string"

		fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0001,0003,0007,0020,0402,0B05", outClusters: "0003,0006,0019", model:"3460-L", manufacturer: "CentraLite"
simulator {
	status "button 2 pressed": "catchall: 0104 0006 02 01 0140 00 6F37 01 00 0000 01 00"
	status "button 2 released": "catchall: 0104 0006 02 01 0140 00 6F37 01 00 0000 00 00"

	input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"",
	defaultValue: 3, displayDuringSetup: false)

	tiles(scale: 2) {
	standardTile("button", "device.button", decoration: "flat", width: 2, height: 2) {
		state "default", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"//, action: "test()"
	valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
			state "battery", label:'${currentValue}% battery', unit:""
	valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
			state "temperature", label:'${currentValue}°',
					[value: 31, color: "#153591"],
					[value: 44, color: "#1e9cbb"],
					[value: 59, color: "#90d2a7"],
					[value: 74, color: "#44b621"],
					[value: 84, color: "#f1d801"],
					[value: 95, color: "#d04e00"],
					[value: 96, color: "#bc2323"]
	standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
			state "default", action:"refresh.refresh", icon:"st.secondary.refresh"

		main (["temperature"])

def parse(String description) {
	log.debug "Parsing '${description}'"
	def descMap = zigbee.parseDescriptionAsMap(description)
	//log.debug descMap

	def results = []
	if (description?.startsWith('catchall:'))
	results = parseCatchAllMessage(descMap)
	else if (description?.startsWith('read attr -'))
	results = parseReportAttributeMessage(descMap)
	else if (description?.startsWith('temperature: '))
	results = parseCustomMessage(description)
	return results;

def configure(){
	"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
	"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 200",

	"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 100",
	"send 0x${device.deviceNetworkId} 1 1", "delay 200",

	"zcl global send-me-a-report 1 0x20 0x20 3600 86400 {01}", "delay 100", //battery report request
	"send 0x${device.deviceNetworkId} 1 1", "delay 200"
	] + refresh()

def refresh(){
	"st rattr 0x${device.deviceNetworkId} 1 1 0x20",
	"st rattr 0x${device.deviceNetworkId} 1 0x402 0"

private Map parseCustomMessage(String description) {
	if (description?.startsWith('temperature: ')) {
		def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())

def parseCatchAllMessage(descMap) {
	//log.debug (descMap)
//    if (descMap?.clusterId == "0006" && descMap?.command == "01") 		//button pressed
//     	createPressEvent(descMap.sourceEndpoint as int)
//    else if (descMap?.clusterId == "0006" && descMap?.command == "00") 	//button released
//    	createButtonEvent(descMap.sourceEndpoint as int)
	if (descMap?.clusterId == "0006" && descMap?.command == "00") //button released
		createButtonEvent(descMap.sourceEndpoint as int)
	else if (descMap?.clusterId == "0402" && descMap?.command == "01") //temperature response

def parseReportAttributeMessage(descMap) {
	if (descMap?.cluster == "0001" && descMap?.attrId == "0020") createBatteryEvent(getBatteryLevel(descMap.value))
	else if (descMap?.cluster == "0402" && descMap?.attrId == "0000") createTempEvent(getTemperature(descMap.value))

private parseTempAttributeMsg(descMap) {
	String temp =[-2..-1].reverse().join()

private createTempEvent(value) {
	return createEvent(getTemperatureResult(value))

private createBatteryEvent(percent) {
	log.debug "Battery level at " + percent
	return createEvent([name: "battery", value: percent])

//this method determines if a press should count as a push or a hold and returns the relevant event type
private createButtonEvent(button) {
	return createButtonPushedEvent(button)

//private createButtonEvent(button) {
//	def currentTime = now()
//    def startOfPress = device.latestState('lastPress').date.getTime()
//    def timeDif = currentTime - startOfPress
//    def holdTimeMillisec = (settings.holdTime?:3).toInteger() * 1000
//    if (timeDif < 0) 
//    	return []	//likely a message sequence issue. Drop this press and wait for another. Probably won't happen...
//    else if (timeDif < holdTimeMillisec) 
//    	return createButtonPushedEvent(button)
//    else 
//    	return createButtonHeldEvent(button)
//private createPressEvent(button) {
//	return createEvent([name: 'lastPress', value: now(), data:[buttonNumber: button], displayed: false])

private createButtonPushedEvent(button) {
	log.debug "Button ${button} pushed"
	return createEvent([
	name: "button",
	value: "pushed", 
	data:[buttonNumber: button], 
	descriptionText: "${device.displayName} button ${button} was pushed",
	isStateChange: true, 
	displayed: true])

//private createButtonHeldEvent(button) {
//	log.debug "Button ${button} held"
//	return createEvent([
//    	name: "button",
//        value: "held", 
//        data:[buttonNumber: button], 
//        descriptionText: "${device.displayName} button ${button} was held",
//        isStateChange: true])

private getBatteryLevel(rawValue) {
	def intValue = Integer.parseInt(rawValue,16)
	def min = 2.1
	def max = 3.0
	def vBatt = intValue / 10
	return ((vBatt - min) / (max - min) * 100) as int

def getTemperature(value) {
	def celsius = Integer.parseInt(value, 16).shortValue() / 100
	if(getTemperatureScale() == "C"){
		return celsius
	} else {
		return celsiusToFahrenheit(celsius) as Integer

private Map getTemperatureResult(value) {
	log.debug 'TEMP'
	def linkText = getLinkText(device)
	if (tempOffset) {
		def offset = tempOffset as int
		def v = value as int
		value = v + offset
	def descriptionText = "${linkText} was ${value}°${temperatureScale}"
	log.debug "Temp value: "+value
	return [
	name: 'temperature',
	value: value,
	descriptionText: descriptionText

// handle commands
def test() {
	log.debug "Test"
	//zigbee.refreshData("0","4") + zigbee.refreshData("0","5") + zigbee.refreshData("1","0x0020")

@canalrun I just want to clarify in what way it’s working better for you? Do you find that you don’t have to push the button twice when it’s been asleep for a while? i.e. first push wakes it up and second push is then seen by ST as “first” button push?

First, Thanks @dchau11. Can you tell me how you did that.

@marktheknife: The button responds to a single press (specifically the release of a press). Since I’ve been using the modified code, it has responded to every press, and I no longer have to “wake up” the button.


@canalrun I did below steps. After step #3, you will see the auto format on the (right) preview box

1. Cut/Paste codes to the reply box.
2. Select all the codes. 
3. Click the </> button.


Thanks for the update, seems to be working better with the first push.

Thanks @dchau11. I must have clicked something wrong.

I am still using @mitchp’s device type, and I was, at first using the button controller smart app… janky as all get-out in getting it to wake up and recognize a press or hold. I ditched the button controller app, and set it up in Rule Machine. It is MUCH more reliable in Rule Machine, and I have both press and hold set up.

1 Like

How were you able to do it in rule machine? Are you using a virtual switch or IFTTT? I dont see a device type in rule machine for button.

Nope, no virtual devices or IFTTT.

I use the Iris Smart Button (original or II) device type

In Rule Machine I create a new rule with the trigger:

Capability: Button
Button Device: Iris Smart Button 1 (my only button)
Button number: one
Button pushed or held: pushed (this is defined but not used in device type II)

No conditions

Select Actions:
No Delay These Actions
Turn on these switches: I have three bulbs that turn on

Everything else is left blank


Under Trigger. It’s not under condition. Action is toggle this switch. (or dimmer, i suppose)

What is rule machine?

Wow. Looks very cool. I have some stuff to figure out! Can I use this to have a button trigger multiple events?

How do you open the battery compartment on these without prying it open with a steak knife?

Go up and read post 55.

So… No?

There has to be a better way than using a knife

@Todd_Whitehead, I just give a corner a little tap on the countertop and it slides out enough to get a hold of it. It could be better designed in terms of battery type and compartment in my opinion.


I forgot to mention that the battery also disconnects A LOT from a small metal tab on the inside of the button. I’ve had to press the battery tray in a little harder than normal to get a connection. Eventually I was able to use small needle nose pliers to bend that tab a little more to ensure it contacts the battery.

There is a little hole near the corner of the battery unit and slide switch. I slide the switch toward the middle and insert an un-bent paperclip into this hole from the bottom, pull outwards. I can usually slide it out enough to grab a hold with my fingers.

Umm? Here is the post I was referencing.

“It’s a pain in the butt. On one of the sides, you will see a little tab sticking up. Holding the button so that the little tab is on the left, slide it to the right. After this, the best think I found was to grip the sides and make the motion like you were packing new cigarettes but don’t hit the device on your palm.”

1 Like

I had this issue also. I found the tray that holds the battery is a sloppy/loose fit. I took small pieces of card stock to shim the battery so it makes better contact.