Compare commits

..

10 Commits

Author SHA1 Message Date
Vinay Rao
1977b5c2cc Merge pull request #2208 from SmartThingsCommunity/staging
Rolling up staging to production
2017-08-01 12:24:56 -07:00
Vinay Rao
8bd02707f4 Merge pull request #2196 from varzac/smartsense-battery-smoothing
Smooth battery values for smartsense devices
2017-07-27 13:11:04 -07:00
Zach Varberg
c86d61a862 Smooth battery values for smartsense devices
Since we have been receiving a lot of reports and complaints about the
battery readings on the smartsense devices that received battery
updates (multi and motion), we are adding this code to smooth out the
readings so it doesn't constantly oscillate between two values.  The
basic logic is, only create an event if we have two readings in a row
that are the same, or a reading is more than 100 millivolts off of the
current battery percentage.
2017-07-27 11:19:31 -05:00
Vinay Rao
8126203164 Merge pull request #2156 from dkirker/ICP-1267
ICP-1267 Move lock state check code from updated() to installed() to reduce chance that hub has gone back to pjoin mode thus blocking Z-Wave.
2017-07-26 14:27:39 -07:00
Vinay Rao
04f56060c9 Merge pull request #2194 from SmartThingsCommunity/master
Rolling up master to staging
2017-07-25 16:47:54 -07:00
Vinay Rao
f6e99745ce Merge pull request #2192 from SmartThingsCommunity/staging
Rolling up staging to production
2017-07-25 15:39:20 -07:00
Donald Kirker
f1a8d58c40 ICP-1267 Call lock state check code from installed() with precise timing to threduce chance that hub has gone back to pjoin mode thus blocking Z-Wave. 2017-07-21 21:44:44 -07:00
Vinay Rao
fc70b5ce55 Merge pull request #2167 from SmartThingsCommunity/staging
Rolling up staging to production
2017-07-18 11:43:24 -07:00
Vinay Rao
6996a07969 Merge pull request #2153 from SmartThingsCommunity/staging
Rolling up staging to production
2017-07-11 13:59:29 -07:00
Vinay Rao
728b169a08 Merge pull request #2143 from SmartThingsCommunity/staging
Rolling up staging to production
2017-07-05 14:16:16 -07:00
4 changed files with 52 additions and 317 deletions

View File

@@ -173,11 +173,27 @@ private Map getBatteryResult(rawValue) {
} else {
def minVolts = 2.4
def maxVolts = 2.7
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
// Get the current battery percentage as a multiplier 0 - 1
def curValVolts = Integer.parseInt(device.currentState("battery")?.value ?: "100") / 100.0
// Find the corresponding voltage from our range
curValVolts = curValVolts * (maxVolts - minVolts) + minVolts
// Round to the nearest 10th of a volt
curValVolts = Math.round(10 * curValVolts) / 10.0
// Only update the battery reading if we don't have a last reading,
// OR we have received the same reading twice in a row
// OR we don't currently have a battery reading
// OR the value we just received is at least 2 steps off from the last reported value
if(state?.lastVolts == null || state?.lastVolts == volts || device.currentState("battery")?.value == null || Math.abs(curValVolts - volts) > 0.1) {
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
} else {
// Don't update as we want to smooth the battery values
result = null
}
state.lastVolts = volts
}
}

View File

@@ -275,11 +275,27 @@ private Map getBatteryResult(rawValue) {
} else {
def minVolts = 2.1
def maxVolts = 2.7
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
// Get the current battery percentage as a multiplier 0 - 1
def curValVolts = Integer.parseInt(device.currentState("battery")?.value ?: "100") / 100.0
// Find the corresponding voltage from our range
curValVolts = curValVolts * (maxVolts - minVolts) + minVolts
// Round to the nearest 10th of a volt
curValVolts = Math.round(10 * curValVolts) / 10.0
// Only update the battery reading if we don't have a last reading,
// OR we have received the same reading twice in a row
// OR we don't currently have a battery reading
// OR the value we just received is at least 2 steps off from the last reported value
if(state?.lastVolts == null || state?.lastVolts == volts || device.currentState("battery")?.value == null || Math.abs(curValVolts - volts) > 0.1) {
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
if (roundedPct <= 0)
roundedPct = 1
result.value = Math.min(100, roundedPct)
} else {
// Don't update as we want to smooth the battery values
result = null
}
state.lastVolts = volts
}
}

View File

@@ -88,19 +88,21 @@ import physicalgraph.zwave.commands.usercodev1.*
def installed() {
// Device-Watch pings if no device events received for 1 hour (checkInterval)
sendEvent(name: "checkInterval", value: 1 * 60 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
try {
if (!state.init) {
state.init = true
// Wait long enough for behind-the-scenes z-wave magic to finish, but be quick enough before hub goes back into inclusion and blocks us
response(["delay 2000"] + secureSequence([zwave.doorLockV1.doorLockOperationGet(), zwave.batteryV1.batteryGet()], 2200))
}
} catch (e) {
log.warn "installed() threw $e"
}
}
def updated() {
// Device-Watch pings if no device events received for 1 hour (checkInterval)
sendEvent(name: "checkInterval", value: 1 * 60 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
try {
if (!state.init) {
state.init = true
response(secureSequence([zwave.doorLockV1.doorLockOperationGet(), zwave.batteryV1.batteryGet()]))
}
} catch (e) {
log.warn "updated() threw $e"
}
}
def parse(String description) {

View File

@@ -1,299 +0,0 @@
/**
* Visonic Door/Window Sensor
*
* Copyright 2017 Tomas Axerot
*
* 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.
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata {
definition (name: "Visonic Door/Window Sensor", namespace: "tomasaxerot", author: "Tomas Axerot") {
capability "Battery"
capability "Configuration"
capability "Contact Sensor"
capability "Refresh"
capability "Temperature Measurement"
capability "Health Check"
capability "Sensor"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "Visonic", model: "MCT-340 SMA"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "Visonic", model: "MCT-340 E"
}
simulator {
}
preferences {
input title: "Temperature Offset", 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", title: "Degrees", 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: "#e86d13"
attributeState "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
}
}
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"
}
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main(["contact", "temperature"])
details(["contact", "temperature", "battery", "refresh", "configure"])
}
}
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 = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
resultMap = getTemperatureResult(value)
}
return resultMap
}
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
}
def getTemperature(value) {
def celsius = Integer.parseInt(value, 16).shortValue() / 100
if(getTemperatureScale() == "C"){
return celsius
} else {
return celsiusToFahrenheit(celsius) as Integer
}
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [:]
if (!(rawValue == 0 || rawValue == 255)) {
def volts = rawValue / 10
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
def roundedPct = Math.round(pct * 100)
result.value = Math.min(100, roundedPct)
result.descriptionText = "${linkText} battery was ${result.value}%"
result.name = 'battery'
}
return result
}
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,
unit: temperatureScale
]
}
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
]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x0402, 0x0000) // Read the Temperature Cluster
}
def refresh()
{
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
]
return refreshCmds + enrollResponse()
}
def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [
"delay 1000",
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
//"raw 0x500 {01 23 00 00 00}", "delay 200",
//"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
]
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
}
def enrollResponse() {
log.debug "Sending enroll response"
[
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1"
]
}
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
}