Iris Smart Button

Yes, That would explain it.

It probably also explains why sometimes when I push the button for the first time after several hours or half a day it does not trigger the events, but all subsequent button presses within a short time do trigger events.

Barry.

OK. I got the Iris Fob to work using this method.

The Iris button seems to be a little different? I got it to pair. But it doesn’t show any status or appear to respond in any way.

I have found for me, that if I added the device handler via code first and then paired the device, it works a lot better. I still have the sleepy button thing but I think you have to" wake it up " first.

That solved my problem. It’s all the way in the back. I pulled it out a little.

Swapped it out for a new button works great … but the IFTT doesn’t seem to recognize the button as a valid switch for use with IFTT. Or am I missing something?

Thanks @JvH. I tried pulling the battery contact and initially I wasn’t sure if there was any difference. But hitting the “refresh” button in the ST iOS app caused the temperature and battery to report. I still couldn’t get the button to do anything though. Resetting the device and then re-adding it to my hub (with @mitchp’s device type already added through the IDE) did the trick though! The button now works as intended. It seems as though having the device type handler already installed when adding the smart button makes the pairing process go a lot more smoothly. Thanks Mitch!

@mitchp Any chance the device type could be modified to recognize both a single or double tap, etc? I have two LIFX bulbs in a room, and sometimes I want one or the other on, and sometimes both. I was hoping to find a way to use the smart button to toggle between light one on, light two on, and both on.

I think it can tell the difference between a (short) press and (long) hold.

1 Like

It does, and it reports either pressed or held. The timing is funny though, holding it down till the red light comes on sometimes acts as “held” but not consistently, holding another second or two seems to work better, but it almost seems like it randomly chooses which to trigger, right now, have both press and held set to the same device.

I’ve been waiting for this for awhile and my local lowes still didn’t have any, but I was able to pick up a couple when I was driving through Atlanta which was nice.

Setup went smoothly for me fortunately. Put the code in prior to setting up. Kept pressing the button till I saw a blue flashing light, and it found the device. I did have to press and hold the back of oneof the buttons to get a connection for pairing though. Thanks for the advice and code!

I see others are having the sleepy problems with this device as well. The problem that I am running into is while its in the “sleepy” phase and I press it, it recognizes it as a hold instead of press. Has anyone see this, or come up with a resolution?

It looks like the device handler determine “pushed or held” based on the time different between current time and startOfPress time and comparing with Hold time. I am wondering if because of the delay of response or my button is a bad. Sometimes i see the current time is high which cause the time different spike up very high even though I only did (short) press. This caused it report as HELD. I will exchange for a different one and try again.

a15751d3-beaa-4095-b630-024412002683  8:16:02 PM: debug Button 1 held
a15751d3-beaa-4095-b630-024412002683  8:16:02 PM: debug TimeDiff: 10146
a15751d3-beaa-4095-b630-024412002683  8:16:02 PM: debug Hold Time: 3000
a15751d3-beaa-4095-b630-024412002683  8:16:02 PM: debug startOfPress Time: 1453169752076
a15751d3-beaa-4095-b630-024412002683  8:16:02 PM: debug Current Time:      1453169762222
a15751d3-beaa-4095-b630-024412002683  8:16:02 PM: debug Parsing 'catchall: 0104 0006 01 01 0140 00 E510 01 00 0000 00 00 '
a15751d3-beaa-4095-b630-024412002683  8:15:52 PM: debug Button 1 pushed
a15751d3-beaa-4095-b630-024412002683  8:15:52 PM: debug TimeDiff: 804
a15751d3-beaa-4095-b630-024412002683  8:15:52 PM: debug Hold Time: 3000
a15751d3-beaa-4095-b630-024412002683  8:15:52 PM: debug startOfPress Time: 1453169752076
a15751d3-beaa-4095-b630-024412002683  8:15:52 PM: debug Current Time:      1453169752880
a15751d3-beaa-4095-b630-024412002683  8:15:52 PM: debug Parsing 'catchall: 0104 0006 01 01 0140 00 E510 01 00 0000 00 00 '
a15751d3-beaa-4095-b630-024412002683  8:15:52 PM: debug Parsing 'catchall: 0104 0006 01 01 0140 00 E510 01 00 0000 01 00 '
a15751d3-beaa-4095-b630-024412002683  8:15:24 PM: debug Button 1 held
a15751d3-beaa-4095-b630-024412002683  8:15:24 PM: debug TimeDiff: 87001
a15751d3-beaa-4095-b630-024412002683  8:15:24 PM: debug Hold Time: 3000
a15751d3-beaa-4095-b630-024412002683  8:15:24 PM: debug startOfPress Time: 1453169637177
a15751d3-beaa-4095-b630-024412002683  8:15:24 PM: debug Current Time:      1453169724178
a15751d3-beaa-4095-b630-024412002683  8:15:24 PM: debug Parsing 'catchall: 0104 0006 01 01 0140 00 E510 01 00 0000 00 00 '
a15751d3-beaa-4095-b630-024412002683  8:15:23 PM: debug Parsing 'catchall: 0104 0006 01 01 0140 00 E510 01 00 0000 01 00 '

The button is likely fine. The “held” condition is not very reliable at the moment. From what I can tell, it’s mostly due to lag.

I see the same thing using the Iris button. Briefly looking at the code what I found is that it’s using the time difference between the button pressed event and the button released event – a short time gives you a press, a long time gives you a hold.

This is what I think is happening, although keep in mind I may be totally wrong.

The timestamps for the two events are not generated until the event notification reaches the SmartThings cloud. There is a variable latency for packets traveling over the Internet. One packet could be delayed while the other makes it through lickety-split. This would give a very inaccurate time difference – it could even be negative.

Internet latency is an annoyance viewing webpages, but it’s an absolute killer if you’re trying to handle real-time triggers and events. it just can’t be done. I think the button code would be much more reliable if only the released event were detected and acted upon. This would only give you a “press”, but it may be more reliable.

Barry.

I’m noticing the “held” issue as well. The first time I push a button after a long time of not pushing it, it registers as a “hold” and not a “push.” I have a button that when pushed, turns on the deck lights outside and when held, puts the house into “relax mode” which dims lights in certain areas.

I’m thinking I might just kill the “hold” task and only have a “push” task for each button. Then maybe getting a few more buttons until one of the smart guys here figures out a solution.

I made some modifications (actually just commented out two areas) to Mitch Pond’s code. I made it so there is now only one event, a button release event. This gets rid of the timing between pressed and released.

In my five minutes of testing it seems more reliable for single button presses. For example, I use it to turn off lights that may still be on because of motion detection.

The code is not pretty in that it still has the parameters and variables in it that Mitch required. Be sure to thank Mitch if you use this code.

/**

  • 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:
  •  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: “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"
}

preferences{
	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}°',
			backgroundColors:[
				[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"])
	details(["button","temperature","battery","refresh"])
}

}

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())
createTempEvent(value)
}
}

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
	parseTempAttributeMsg(descMap)

}

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 = descMap.data[-2…-1].reverse().join()
createTempEvent(getTemperature(temp))
}

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”)
zigbee.refreshData(“0x402”,“0”)
}

It appears the code you posted is identical to his original? I did an exam diff and there were two formatting errors compared to the original but I don’t see any commented out sections.

This button is VERY unreliable for me, keeps not responding, but temp seems to almost consistently work fine. Worked great yesterday the 10 or so times I used it, but not at all this morning.

Hello,
For me the modifications make the button much more reliable.

I look at the code I posted, and I see is not formatted in the post correctly. I searched the forms for how to post code correctly, but did not find anything. If someone could give me a brief tutorial, that would be helpful.

I see that most code listings seem to originate from Git hub. I’m not familiar with that either.

The modifications I made to the code are really not much more than commenting out two sections. These modifications are probably distorted by the way I posted my code listing.

If someone could guide me to the proper way to post, I will repost the code.

Barry.

There’s an option in the post window called “Preformatted text” looks like </>, click that with the code highlighted and it should paste exactly as it looks in the IDE.

I hope this works better…

From before:
I made some modifications (actually just commented out two areas) to Mitch Pond’s code. I made it so there is now only one event, a button release event. This gets rid of the timing between pressed and released.

In my five minutes of testing it seems more reliable for single button presses. For example, I use it to turn off lights that may still be on because of motion detection.

The code is not pretty in that it still has the parameters and variables in it that Mitch required. Be sure to thank Mitch if you use this code.

/**
 *  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:
 *
 *      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: "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”
}

preferences{
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}°',
				backgroundColors:[
					[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"])
		details(["button","temperature","battery","refresh"])
	}
}

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())
		createTempEvent(value)
	}
}

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
parseTempAttributeMsg(descMap)
}

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 = descMap.data[-2..-1].reverse().join()

createTempEvent(getTemperature(temp))
}

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”)
zigbee.refreshData(“0x402”,“0”)
}