Lowe's Iris Sensors (New CentraLite models)

I’ve read through about half this thread… I just set up 3 of the 6 of the contact sensors I purchased from Lowe’s. The 3 I set up are on our doors… now I want some suggestions as to fun things I can do with the other 3. I could do windows… but I’d need about 15 more to cover the whole house… so I’m thinking of doing something “different” and would love to see what you guys have done that falls into that category…

I have a sense on my front door that I have do several things.

  1. door opens, foyer lights come on. Go off after 3 minutes if door closed, 5 if left open.
  2. after 9 pm if the door opens, it also turns on the porch lights

I use one on the foyer closet door to control a smart bulb in there. For some reason they didn’t put one in there. It’s instant on when open and closed.

I do the same for the lights in the laundry room.

I know, kind of lame. I still have 4 more of them to install and trying to figure out the same thing you are lol.

1 Like

I can’t wait to figure out what to do with these! I think I will set my front door to work like yours. I don’t have any control over my porch lights… what did you do to get that? Smartbulbs?

Newbie here. Thanks everyone for the posts to the sensors and discount codes. i was able to order three of the motion sensors:)

I actually struck it rich with the Lowes gee switches last year. I think I got somewhere around 40 of them and average right around $8.00 each! So, I use switches. But, before that I was using ge link bulbs.

Which by the way I no longer recommend, unless you do not have a Phillips hub. If you do have the Phillips hub I recommend their new generation bulbs. They are the same price and a much better quality than the GE bulbs.

I agree with @bamarayne, switches are the way to go (I did not ‘strike it rich’ at lowes) I have about 10 GE switches, mostly dimmers (iike the dim up dim down effect). They are nearly instant, and still perform like regular switches. Smart Bulbs are not rated for out door use. They probably work fine, but I don’t want to risk an expensive bulb. I have hue bulbs in certain situations, and really like them. But the switch has to remain on, and occasionally it gets turned off. I use them mostly in lamps and with physical remotes, which is so much more convenient than using a phone.

For my porch lights I have them slightly smarter, they turn on if the front door opens after sunset. I also have a rule that if anyone is not present at the house after sunset it turns on the porch light. Have not come home to a dark porch in weeks.

1 Like

I have one GE bulb outdoors, so far it’s fine.

I’ve installed eight Iris sensors. One in a closet with a GE bulb, as bamarayne did. The remainder are on windows.

I have eight more. Four more for windows, and the remainder for experimentation.

I just installed an Iris motion sensor which works great but I noticed the reported temperature was 73 degrees when I first paired it but then it dropped down to 46, and then later down to 20 degrees. I know my room temperature is 72 degrees. After a factory reset and repairing, it did a very similar thing reporting an accurate temp but within fifteen minutes, it would drop down to 20 degrees. Anyone else see this odd behavior with the temp sensor?

Yes. Your temperature is in Celsius.

My app setting is in Fahrenheit. Is there some other setting I might’ve missed?

Check your location settings under https://graph.api.smartthings.com/location for the “Temperature Scale” settings:

1 Like

Hey, I was wondering if anyone can help me modify the device handler so that it reports temperatures to the tenth or even hundredth value. So instead of full integers such as 70 degrees, it reports 70.4 or 70.35 for example. I really need this for the Iris open/close sensors and I am confident I’ll be able to modify the motion sensor based on what I learn from the open and closed ones. I am not a coder but I am starting to learn the basics…

Thanks!

Hi, I done have the code in front of me to send it to you.

But…

Look at the code, up at the top somewhere before line 30 you will see something with “number” in the line.

Make that number.number

Then save and publish. Then open your device and tap the reset tile.

You may need to do that several time…

The only place I see number is:
input “tempOffset”, “number”, title: “Degrees”, description: “Adjust temperature by this many degrees”, range: “”, displayDuringSetup: false

It looks like it allows me to enter a more precise offset which is good but then it throws an error in the code below:

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}"
return [
name: ‘temperature’,
value: value,
descriptionText: descriptionText
]
}

I removed “as integer” in the code but then it just appends the offset to the temperature value so if I put 1 and the temperature is 70, it reports the temperature as 701… Let me reiterate I am new to coding lol!

I think you are on the right line. It should look something like this:

input “tempOffset”, “number.number”, title: “Temperature Offset”, description: “Adjust temperature by this many degrees”, range: “”, displayDuringSetup: false
}

You are doing this on the motion sensor right? My code is for the contact sensor, but they should be the same.

Thanks, I checked and I’m set to F. I’m beginning to think that I have a defective motion sensor. It’s been 8 hours and the device has not reported temp in all that time plus the temp seems to be stuck at 20 degrees. Thanks for everyone’s help.

I changed it to number.number and removed the “as int” but I have 2 issues… 1. when I make the offset 1.5 and the temperature is 70, it shows up as 701.5 degrees; if I make the offset 2, it would show up as 702 degrees; etc. Issue 2 is that even if I figure out how to add the offset properly, the temperature still is measured as an integer. So if the contact sensor’s raw reading is 69.55, it still rounds it to 70. I want the raw reading from the contact sensor to be a non-integer not just the offset…

Ok, I have to figure out exactly what I did… I’ve not learned groovy… I just know enough to stumble around blindly… lol

edit:
Are you wanting the code for the contact sensor or the motion sensor?

Here is the entire code for the iris contact sensor

/**

  • Iris Open/Closed Sensor for temp
  • 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 Open/Closed Sensor for Temp”, namespace: “mgspivey”, author: “Mark Spivey/SmartThings”) {
capability “Battery”
capability “Configuration”
capability “Contact Sensor”
capability “Refresh”
capability “Temperature Measurement”
command “enrollResponse”
fingerprint endpointId: “01”, inClusters: “0000,0001,0003,0402,0500,0020,0B05”, outClusters: “0019”, model: “3320-L”, manufacturer: “CentraLite”
}
simulator {
}
preferences {
input description: “This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that’s 5 degrees too warm, you’d enter “-5”. If 3 degrees too cold, enter “+3”.”, displayDuringSetup: false, type: “paragraph”, element: “paragraph”
input “tempOffset”, “number.number”, title: “Temperature Offset”, description: “Adjust temperature by this many degrees”, range: "
…*", displayDuringSetup: false
}
tiles(scale: 2) {
multiAttributeTile(name:“contact”, type: “generic”, width: 6, height: 4){
tileAttribute (“device.contact”, key: “PRIMARY_CONTROL”) {
attributeState “open”, label:’${name}’, icon:“st.contact.contact.open”, backgroundColor:"#ffa81e"
attributeState “closed”, label:’${name}’, icon:“st.contact.contact.closed”, backgroundColor:"#79b821"
}
}
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”]
]
}
valueTile(“battery”, “device.battery”, decoration: “flat”, inactiveLabel: false, width: 2, height: 2) {
state “battery”, label:’${currentValue}% battery’, unit:""
}
standardTile(“refresh”, “device.refresh”, inactiveLabel: false, decoration: “flat”, width: 2, height: 2) {
state “default”, action:“refresh.refresh”, icon:“st.secondary.refresh”
}
main ([“contact”, “temperature”])
details([“contact”,“temperature”,“battery”,“refresh”])
}
}
def parse(String description) {
log.debug “description: $description”
Map map = [:]
if (description?.startsWith(‘catchall:’)) {
map = parseCatchAllMessage(description)
}
else if (description?.startsWith(‘read attr -’)) {
map = parseReportAttributeMessage(description)
}
else if (description?.startsWith('temperature: ')) {
map = parseCustomMessage(description)
}
else if (description?.startsWith(‘zone status’)) {
map = parseIasMessage(description)
}
log.debug “Parse returned $map”
def result = map ? createEvent(map) : null
if (description?.startsWith(‘enroll request’)) {
List cmds = enrollResponse()
log.debug “enroll response: ${cmds}”
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return result
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
case 0x0402:
log.debug ‘TEMP’
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2…-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
break
}
}
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private Map parseReportAttributeMessage(String description) {
Map descMap = (description - “read attr - “).split(”,”).inject([:]) { map, param →
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
log.debug “Desc Map: $descMap”
Map resultMap = [:]
if (descMap.cluster == “0402” && descMap.attrId == “0000”) {
def value = getTemperature(descMap.value)
resultMap = getTemperatureResult(value)
}
else if (descMap.cluster == “0001” && descMap.attrId == “0020”) {
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
}
return resultMap
}
private Map parseCustomMessage(String description) {
Map resultMap = [:]
if (description?.startsWith('temperature: ‘)) {
def value = Double.parseDouble(description.split(": ")[1])
resultMap = makeTemperatureResult(convertTemperature(value))
}
return resultMap
}
private Map parseIasMessage(String description) {
List parsedMsg = description.split(’ ')
String msgCode = parsedMsg[2]
Map resultMap = [:]
switch(msgCode) {
case ‘0x0020’: // Closed/No Motion/Dry
resultMap = getContactResult(‘closed’)
log.info “Contact is Closed”
break
case ‘0x0021’: // Open/Motion/Wet
resultMap = getContactResult(‘open’)
log.info “Contact is Open”
break
case ‘0x0022’: // Tamper Alarm
log.info “Tamper Alarm”
break
case ‘0x0023’: // Battery Alarm
log.info “Battery Alarm”
break
case ‘0x0024’: // Supervision Report
resultMap = getContactResult(‘closed’)
log.info “Contact is Closed”
break
case ‘0x0025’: // Restore Report
resultMap = getContactResult(‘open’)
log.info “Contact is Open”
break
case ‘0x0026’: // Trouble/Failure
log.info “Trouble Alarm”
break
case ‘0x0028’: // Test Mode
break
}
return resultMap
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == “C”){
log.info “Temperature is ${celsius}C with an offset of ${tempOffset}C”
return celsius
} else {
def fahrenheit = celsiusToFahrenheit(celsius)
log.info “Temperature is ${fahrenheit}F with an Offset of ${tempOffset}F”
return fahrenheit
}
}
private def convertTemperature(celsius) {
if(getTemperatureScale() == “C”){
return celsius
} else {
def fahrenheit = Math.round(celsiusToFahrenheit(celsius) * 100) /100
return fahrenheit
}
}
private Map getBatteryResult(rawValue) {
log.debug ‘Battery’
def linkText = getLinkText(device)
def result = [
name: ‘battery’,
value: ‘–’
]
def volts = rawValue / 10
def descriptionText
if (rawValue == 0) {}
else {
if (volts > 3.5) {
result.descriptionText = “${linkText} battery has too much power (${volts} volts).”
}
else if (volts > 0){
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = “${linkText} battery was ${result.value}%”
}
}
log.info “Battery is ${result.value}%”
log.info “Battery voltage is ${volts}v”
return result
}
private Map getTemperatureResult(value) {
log.debug ‘TEMP’
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset.toBigDecimal()
def v = value
value = value + tempOffset.toBigDecimal()
}
def descriptionText = “${linkText} was ${value}°${temperatureScale}”
log.info “${linkText} reports as ${value}°${temperatureScale}”
return [
name: ‘temperature’,
value: value,
descriptionText: descriptionText
]
}
private Map makeTemperatureResult(value) {
def linkText = getLinkText(device)
if (tempOffset) {
def offset = tempOffset.toBigDecimal()
def v = value
value = value + tempOffset.toBigDecimal()
}
def descriptionText = “${linkText} was ${value}°${temperatureScale}”
log.info “${linkText} reports as ${value}°${temperatureScale}”
return [
name: ‘temperature’,
value: value,
descriptionText: descriptionText
]
}
private Map getContactResult(value) {
log.debug ‘Contact Status’
def linkText = getLinkText(device)
def descriptionText = “${linkText} was ${value == ‘open’ ? ‘opened’ : ‘closed’}”
return [
name: ‘contact’,
value: value,
descriptionText: descriptionText
]
}
def refresh() {
log.debug “Manual Refresh Initiated”
def refreshCmds = [
“st rattr 0x${device.deviceNetworkId} 1 0x402 0”, “delay 200”,
“st rattr 0x${device.deviceNetworkId} 1 1 0x20”, “delay 200”
]
return refreshCmds + enrollResponse()
}
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug “Configuring Reporting, IAS CIE, and Bindings.”
def configCmds = [
“zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}”, “delay 200”,
“send 0x${device.deviceNetworkId} 1 1”, “delay 500”,
“zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}”, “delay 500”,
“zcl global send-me-a-report 1 0x20 0x20 30 1200 {01}”, //checkin time 20 minutes
“send 0x${device.deviceNetworkId} 1 1”, “delay 500”,
“zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}”, “delay 500”,
“zcl global send-me-a-report 0x402 0 0x29 30 1200 {0A00}”,
“send 0x${device.deviceNetworkId} 1 1”, “delay 500”
]
return configCmds + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
log.debug “Sending enroll response”
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
[
//Resending the CIE in case the enroll request is sent before CIE is written
“zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}”, “delay 200”,
“send 0x${device.deviceNetworkId} 1 ${endpointId}”, “delay 500”,
//Enroll Response
“raw 0x500 {01 23 00 00 00}”,
“send 0x${device.deviceNetworkId} 1 1”, “delay 200”
]
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte reverseArray(byte array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j–;
i++;
}
return array
}