I made a few changes in this version:
- Logging enable/disable/level in preferences
- Sensor settings in preferences
- Added driver versioning
- Getting/displaying sensor firmware version // Not sure about displayed format...
- Initialize all the sensor parameters on first wake-up of the device
- Cleaned-up unused code for Hubitat application
I hope I didn't break anything...
/**
* Fibaro Z-Wave FGK-101 Temperature & Door/Window Sensor Handler [v0.9.5.4, 3 December 2018]
*
* Copyright 2014 Jean-Jacques GUILLEMAUD
*
* 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.
*
*/
/******************************************************************************************************************************
* Fibaro Z-Wave FGK-101 Marketing Description is at :
* http://www.fibaro.com/en/the-fibaro-system/door-window-sensor
*
* Fibaro FGK-10x Operating Manual can be downloaded at :
* http://www.fibaro.com/files/instrukcje/eng/DoorWindowSensor%20FGK-101-107%20ENG_v21-v23.pdf
*
* The current version of this Handler is parameterized to force Device's wakeup :
* - on any open<->closed state change
* - in case of Tampering Alarm triggering
* - every 60mn (wakeUpIntervalSet(seconds:60*60), hard coded)
* - whenever Temperature delta change since last report is greater than 0.31°C (Parameter#12, hard coded)
* also :
* - Temperature is natively reported by sensor in Celsius (SensorMultilevelReport[scale:0]);
* convertion is needed for Fahrenheit display
*
* A few specificities of this device that are relevant to better understand some parts of this Handler :
* - it is a battery operated device, so Commands can only be sent to it whenever it wakes up
* - it is a multi-channel Device, and the multi-level temperature sensor reports only from EndPoint#2
* - specific configurable parameters are documented in the above Operating Manual
* - some of those parameters must be modified to activate the anti-Tampering Alarm
* - some of the "scaffolding" has been left in place as comments, since it may help other people to understand/modify this Handler
* - BEWARE : the optional DS18B20 sensor must be connected BEFORE the Device is activated (otherwise, reset the Device)
* - IMPORTANT : for debugging purpose, it is much better to change the wake-up period from the default 60mn to 1mn or so;
* but unless you force the early wake up of the sensor (forcing open/closed for instance), you will have to
* wait up to 60mn for the new value to become effective.
*
* Z-Wave Device Class: GENERIC_TYPE_SENSOR_BINARY / SPECIFIC_TYPE_ROUTING_SENSOR_BINARY
* FGK-101 Raw Description [EndPoint:0] : "0 0 0x2001 0 0 0 c 0x30 0x9C 0x60 0x85 0x72 0x70 0x86 0x80 0x84 0x7A 0xEF 0x2B"
* Command Classes supported according to Z-Wave Certificate ZC08-14070004 for FGK-101\US :
* Used in Handler :
* - 0x20 - 32 : BASIC V1
* 0x30 - 48 : SENSOR_BINARY !V1! V2
* - 0x31 - 49 : SENSOR_MULTILEVEL V1 !V2! V3 V4 V5
* - 0x56 - 86 : CRC_16_ENCAP V1
* 0x60 - 96 : MULTI_CHANNEL V3
* 0x70 - 112 : CONFIGURATION V1 !V2!
* 0x72 - 114 : MANUFACTURER_SPECIFIC V1 !V2!
* 0x80 - 128 : BATTERY V1
* 0x84 - 132 : WAKE_UP V1 !V2!
* 0x85 - 133 : ASSOCIATION V1 !V2!
* 0x86 - 134 : VERSION V1
* 0x98 - 152 : SECURITY V1 [only latest versions of FGK-101]
* 0x9C - 156 : SENSOR_ALARM V1
* NOT used in Handler :
* 0x2B - 43 : SCENE_ACTIVATION V1
*
* also found in FGK-101 Raw Description, in addition to Z-Wave Certificate for FGK-101\US [?!!] :
* + 0x7A - 122 : FIRMWARE_UPDATE_MD V1 V2
* + 0xEF - 239 : MARK V1
*
* Version Control:
*
* 0.1 - 2022-09-21 - Initial port to Hubitat of @geejiit Github code for ST by christi999@hubitat
*
*
*
******************************************************************************************************************************/
public static String version() { return "0.1" }
metadata {
definition (name: "JJ's Fibaro FGK-101 Handler", namespace: "JJG2014", author: "Jean-Jacques GUILLEMAUD") {
capability "Contact Sensor"
capability "Battery"
capability "Configuration"
capability "Temperature Measurement"
capability "Sensor"
capability "Alarm"
command "reportNext", ["string"]
command "test"
attribute "reportASAP", "number"
attribute "deviceTime", "number"
attribute "driverVersion", "string"
// FGK-101 Raw Description [EndPoint:0] : "0 0 0x2001 0 0 0 c 0x30 0x9C 0x60 0x85 0x72 0x70 0x86 0x80 0x84 0x7A 0xEF 0x2B"
fingerprint deviceId: "0x2001", inClusters: "0x30, 0x60, 0x70, 0x72, 0x80, 0x84, 0x85, 0x9C" // should include "0x20, 0x31" too ?!!
}
preferences {
input name:"wakeUpInterval", type:"number", title: "<b>Wake-up Interval?</b>", description:"seconds (default: 3600)", defaultValue:3600, range: "0..65535", required: true //Actual min max???
input name:"param1", type:"number", title: "<b>Param. 1 - Input IN alarm cancellation delay?</b>", description:"seconds (default: 0)", defaultValue:0, range: "0..65535", required: true
input "param2", "enum", title: "<b>Param. 2 - Status change signalled by LED?</b>", description:"(default: 0 - OFF)", options: [0:"OFF",1:"ON"], defaultValue:0, required: true
input "param3", "enum", title: "<b>Param. 3 - Type of IN input?</b>", description:"(default: 0 – INPUT_NC)", options: [0:"INPUT_NC (Normal Close)", 1:"INPUT_NO (Normal Open)", 2:"INPUT_MONOSTABLE", 3:"INPUT_BISTABLE"], defaultValue:0, required: true
input "param5", "enum", title: "<b>Param. 5 - Type of control frame transmitted for association group 1?</b>", description:"(Default value: 255 – BASIC SET)", options: [0:"ALARM GENERIC frame", 1:"ALARM SMOKE frame", 2:"ALARM CO frame",3:"ALARM CO2 frame",4:"ALARM HEAT frame",5:"ALARM WATER frame",255:"Control frame BASIC_SET"], defaultValue:255, required: true
input name:"param7",type:"number", title: "<b>Param. 7 - forced level of dimming/opening ?</b>", description:"1-99 or 255 (default: 255 - activate to previous level)", defaultValue:255, range: "1..255", required: true // need changes to limit values to valid range...
input "param9", "enum", title: "<b>Param. 9 - Deactivating transmission of the alarm cancelling frame or the control frame deactivating the device?</b>", description:"(default: 0, Sent)", options: [0:"Sent",1:"Not Sent"], defaultValue:0, required: true
input name:"param12", type:"number", title: "<b>Param. 12 - Sensitivity to temperature changes?</b>", description:"In 1/16 degC units, If the value is set to 0 and wake-up interval is set to 255 seconds, temperature report will be sent according to the interval. If the value is set to 0 and the wake-up interval is set to over 255, temperature report will be sent each ca. 4 minutes (default: 8 [0.5degC])", defaultValue:8, range: "0..255", required: true
input "param13", "enum", title: "<b>Param. 13 - Sending an alarm or control frame (for IN input, depending on parameter no.5 value), and TMP button alarm frame?</b>", description:"(default: 0, IN and TMP Broadcast mode inactive)", options: [0:"IN and TMP Broadcast mode inactive", 1:"IN broadcast mode active, TMP broadcast mode inactive", 2:"IN broadcast mode inactive, TMP broadcast mode active", 3:"IN and TMP broadcast mode active"], defaultValue:0, required: true
input "param14", "enum", title: "<b>Param. 14 - Scene activation functionality?</b>", description:"(default: 0 - functionality deactivated)", options: [0:"functionality deactivated", 1:"functionality activated"], defaultValue:0, required: true
input name:"sensorOffset", type:"decimal", title:"<b>Sensor Temperature Offset</b>", description:"degrees", defaultValue:0.0, range: "-20..20", required: true
input name: "debugOutput", type: "bool", title: "<b>Enable debug logging?</b>", description: "<br>", defaultValue: true , required: true
input "debugLevel", "enum", title: "<b>Debug Level?</b>", options: [1:"1",2:"2",3:"3"], defaultValue: 1, required: true
}
}
//---------------------------
//
//---------------------------
def test() {
parse("zw device: 14, command: 8407, payload: , isMulticast: false")
parse("zw device: 16, command: 600D, payload: 02 02 31 05 01 44 00 00 0B 79 , isMulticast: false")
parse("zw device: 16, command: 2001, payload: 00 , isMulticast: false") // Basic set
parse("zw device: 16, command: 3003, payload: 00 , isMulticast: false") //SensorBinaryReport
parse("zw device: 16, command: 2001, payload: FF , isMulticast: false")
parse("zw device: 16, command: 3003, payload: FF , isMulticast: false")
parse("zw device: 14, command: 7006, payload: 0F 01 00 , isMulticast: false")
parse("zw device: 14, command: 9C02, payload: 14 00 FF 00 00 , isMulticast: false")
parse("zw device: 14, command: 9C02, payload: 14 00 00 00 00 , isMulticast: false")
parse("zw device: 16, command: 600D, payload: 02 02 31 05 01 44 00 00 05 79 , isMulticast: false")
}
//---------------------------
//
//---------------------------
def parse(String description) {
state.parseCount=state.parseCount+1
logDebug 1, "--------------------------Parsing... ; state.parseCount: ${state.parseCount}--------------------------"
logDebug 2, "Parsing... '${description}'"
def result = null
def cmd = zwave.parse(description, [0x20:1, 0x30:1, 0x31:2, 0x56:1, 0x60:3, 0x70:2, 0x72:2, 0x80:1, 0x84:2, 0x85:2, 0x9C:1])
if (cmd) {
result = zwaveEvent(cmd)
logDebug 1, "Parsed ${cmd} to ${result.inspect()}"
} else {
logDebug 3, "Non-parsed event: ${description}"
}
return result
}
//---------------------------
//
//---------------------------
def temperatureScaleFC(tempvalue) {
//FGK-101 is natively °C; convert to °F if selected in settings
def float tempFC = tempvalue
if (location.temperatureScale == "F") {
tempFC = tempvalue * 1.8 + 32
}
return tempFC
}
//---------------------------
//
//---------------------------
def wakeUpResponse(cmdBlock) {
//Initialization... (executed only once, when the Handler has been updated)
//All untouched parameters are supposed to be DEFAULT (as factory-set)
if (state.isInitialized == false) {
logDebug 2, "state.isInitialized : ${state.isInitialized}"
cmdBlock << zwave.wakeUpV2.wakeUpIntervalSet(seconds:wakeUpInterval, nodeid:zwaveHubNodeId).format() // NB : may have to wait 60mn for that value to be refreshed !
cmdBlock << "delay 1200"
// NOTE : any asynchronous temperature query thru SensorMultilevelGet() does NOT reset the delta-Temp base value (managed by DS18B20 hardware)
// Adjust temperature report sensitivity for outside thermometers whose displayName starts with "*"
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 1, size: 2, configurationValue: (1..0).collect { (param1.toInteger() >> (it * 8)) & 0xFF}).format()
cmdBlock << "delay 1200"
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 2, size: 1, configurationValue: [param2.toInteger()]).format()
cmdBlock << "delay 1200"
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 3, size: 1, configurationValue: [param3.toInteger()]).format()
cmdBlock << "delay 1200"
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 5, size: 1, configurationValue: [param5.toInteger()]).format()
cmdBlock << "delay 1200"
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 7, size: 1, configurationValue: [param7.toInteger()]).format()
cmdBlock << "delay 1200"
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 9, size: 1, configurationValue: [param9.toInteger()]).format()
cmdBlock << "delay 1200"
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 12, size: 1, configurationValue: [param12.toInteger()]).format()
cmdBlock << "delay 1200"
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 13, size: 1, configurationValue: [param13.toInteger()]).format()
cmdBlock << "delay 1200"
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 14, size: 1, configurationValue: [param14.toInteger()]).format()
cmdBlock << "delay 1200"
// inclusion of Device in Association#3 is needed to get delta-Temperature notification messages [cf Parameter#12 above]
cmdBlock << zwave.associationV2.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format()
cmdBlock << "delay 1200"
// inclusion of Device in Association#2 is needed to enable SensorAlarmReport() Command [anti-Tampering protection]
cmdBlock << zwave.associationV2.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]).format()
cmdBlock << "delay 1200"
// inclusion of Device in Association#4 is needed for backward compatibility with non Z-Wave+ controlers
cmdBlock << zwave.associationV2.associationSet(groupingIdentifier:4, nodeId:[zwaveHubNodeId]).format()
cmdBlock << "delay 1200"
// inclusion of Device in Association#5 is needed for backward compatibility with non Z-Wave+ controlers
cmdBlock << zwave.associationV2.associationSet(groupingIdentifier:5, nodeId:[zwaveHubNodeId]).format()
cmdBlock << "delay 1200"
// Firmware Version
cmdBlock << zwave.versionV1.versionGet().format()
cmdBlock << "delay 1200"
state.isInitialized = true
logDebug 2, "state.isInitialized : ${state.isInitialized}"
}
//Regular Commands...
def long nowTime = new Date().getTime()
// Next line needed because "update()" does not seem to work anymore
state.batteryInterval = (long) (24*60-45)*60*1000 // 1 day
if (nowTime-state.lastReportBattery > state.batteryInterval) {
cmdBlock << zwave.batteryV1.batteryGet().format()
cmdBlock << "delay 1200"
//next 2 lines redondant since any open/closed status change is asynchronously notified... but useful in case of missing basicSet notification
cmdBlock << zwave.basicV1.basicGet().format()
cmdBlock << "delay 1200"
}
cmdBlock << zwave.wakeUpV2.wakeUpIntervalGet().format() // NB : may have to wait 60mn for that value to be refreshed !
cmdBlock << "delay 1200"
cmdBlock << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: 2, destinationEndPoint: 2, commandClass:0x31/*Sensor Multilevel*/, command:4/*Get*/).format()
cmdBlock << "delay 1200"
cmdBlock << zwave.wakeUpV2.wakeUpNoMoreInformation().format()
cmdBlock << "delay 2000"
logDebug 2, "wakeUpNoMoreInformation()"
logDebug 2, "cmdBlock : ${cmdBlock}"
return cmdBlock
}
//---------------------------
//
//---------------------------
def zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpNotification cmd) {
// IMPORTANT NOTE : when the batteryLevel becomes too low, Device reports become erratic, all periodic wakeUpNotifications stop
// and consequently BATTERYLEVEL IS NOT UPDATED ANYMORE every 24 hours, continuing to display the last (and obsolete) reported value.
// Curiously, asynchronous sensorMultilevelReports continue to arrive, for some time, making the Device look (partially) "alive"
logDebug 2, "wakeupv2.WakeUpNotification $cmd"
def event = sendEvent(descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false)
def cmdBlock = []
cmdBlock=wakeUpResponse(cmdBlock)
return [event, response(cmdBlock)]
}
//---------------------------
//
//---------------------------
def zwaveEvent(hubitat.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) {
// IMPORTANT NOTE : when the batteryLevel becomes too low, Device reports become erratic, all periodic wakeUpNotifications stop
// and consequently BATTERYLEVEL IS NOT UPDATED ANYMORE every 24 hours, continuing to display the last (and obsolete) reported value.
// Curiously, asynchronous sensorMultilevelReports continue to arrive, for some time, making the Device look (partially) "alive"
// This section resets the displayed battery level to 1% when the battery level is obsolete by more than 48h.
state.batteryInterval = (long) (24*60-45)*60*1000 // 1 day
def long nowTime = new Date().getTime()
if (nowTime-state.lastReportBattery > 3*state.batteryInterval) { // reset batteryLevel to 1% if no update for 48-72 hours
logDebug 3, "obsolete (likely low) battery value : ${((nowTime-state.lastReportBattery)/3600000)} hours old"
sendEvent(name: "battery", displayed: true, isStateChange:true, unit: "%", value: 1, descriptionText: "${device.displayName} has a low battery")
state.lastReportBattery = nowTime
}
// Dirty temporary recovery fix for remote Devices which lost wakeUp capability but still get asynchromous SensorMultilevelReports
// Forcing with the magnet a close/open transition after replacing the battery should (in most cases...) restore wakeUps
//def cmdBlock = []
//cmdBlock=wakeUpResponse(cmdBlock)
//return [response(cmdBlock)]
//configure()
def float scaledSensorValue = cmd.scaledSensorValue
scaledSensorValue = scaledSensorValue + 1.0*sensorOffset
//Round to nearest 1 decimal temperature value; convert to °F if needed
def float ftempSign = temperatureScaleFC(scaledSensorValue) < 0 ? -1 : +1
def float ftemp = ftempSign * ((((temperatureScaleFC(scaledSensorValue).abs()*100+5)/10).intValue()*1.0)/10)
logDebug 2, "ftempSign : ${ftempSign}"
logDebug 2, "ftemp : ${ftemp}"
// Next line needed because "update()" does not seem to work anymore
state.maxEventInterval = (long) (4*60-10)*60*1000 // at least 1 Temperature Report event every 4 hours
nowTime = new Date().getTime()
logDebug 2, "cmd.scaledSensorValue : ${cmd.scaledSensorValue}"
logDebug 2, "correction : ${scaledSensorValue-cmd.scaledSensorValue}"
logDebug 2, "device.displayName : ${device.displayName}"
logDebug 2, "'Date().getTime()' : ${new Date().getTime()}"
logDebug 2, "state.forcedWakeUp : ${state.forcedWakeUp}"
logDebug 2, "state.maxEventInterval : ${state.maxEventInterval}"
logDebug 2, "state.lastReportTime : ${state.lastReportTime}"
logDebug 2, "nowTime : ${nowTime}"
logDebug 2, "(nowTime-state.lastReportTime > state.maxEventInterval) : ${(nowTime-state.lastReportTime > state.maxEventInterval)}"
logDebug 2, "ftemp : ${ftemp}"
logDebug 2, "state.lastReportedTemp: ${state.lastReportedTemp}"
def float tempQuantum
tempQuantum = temperatureScaleFC(param12.toFloat()/16.0)-temperatureScaleFC(0)
logDebug 1, "((ftemp-state.lastReportedTemp).abs()>${tempQuantum}): ${(ftemp-state.lastReportedTemp).abs()>tempQuantum}"
if (((ftemp-state.lastReportedTemp).abs()>tempQuantum) || ((nowTime-state.lastReportTime) > state.maxEventInterval) || state.forcedWakeUp) {
def map = [ displayed: true, value: ftemp.toString(), isStateChange:true, linkText:"${device.displayName}" ]
switch (cmd.sensorType) {
case 1:
map.name = "temperature"
map.unit = cmd.scale == 1 ? "F" : "C"
//ignores Device's native temperature scale, ftemp already converted to °F if settings as such
map.unit = location.temperatureScale
logDebug 1, "map.value : ${map.value}"
logDebug 1, "map.unit : ${map.unit}"
break;
}
logDebug 2, "temperature Command : ${map.inspect()}"
state.lastReportedTemp = ftemp
state.lastReportTime = nowTime
state.forcedWakeUp = false
// For Test purpose; redondant with reportNext() => state.forcedWakeUp=1
if (device.currentValue('reportASAP')==1) {sendEvent(name: "reportASAP", value: 0, isStateChange: true)}
return sendEvent(map)
}
}
//---------------------------
//
//---------------------------
def sensorValueEvent(value) {
if (value) {
sendEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")
} else {
sendEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
}
}
//---------------------------
// BasicReport should not be necessary since all status change notifications are asynchronous via BasicSet
// But useful as defensive programming, in case of missed notifications, to make sure latest change has been properly reported and registered
//---------------------------
def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) {
sensorValueEvent(cmd.value)
logDebug 2, "basicv1.BasicReport $cmd.value"
def cmdValue = cmd.value
return openClosed(cmd, cmdValue)
}
//---------------------------
// To check that WakeUpInterval does not revert to 1mn instead of 1h
//---------------------------
def zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpIntervalReport cmd) {
logDebug 2, "WakeUpIntervalReport $cmd"
if (cmd.seconds!=wakeUpInterval) {
def result = sendEvent(name:"WakeUpIntervalReport", value:"${cmd.seconds}", descriptionText:"${device.displayName} had ${cmd.seconds} seconds wakeUp period", isStateChange:true, displayed:true, linkText:"${device.displayName}")
configure()
}
return result
}
//---------------------------
//
//---------------------------
def openClosed(cmd, cmdValue) {
def theState = cmdValue == 0 ? "closed" : "open"
logDebug 2, "openClosed $cmd"
// Use closed/open sensor notification to trigger push of updated Temperature value and immediate setting of updated device parameters
// Sometimes, Temperature forced refresh stops working : SensorMultilevelGet() Commands are stacked but not executed immediately;
// will restart after some time, and stacked Commands will be executed !
def event = sendEvent(name:"contact", value:"${theState}", descriptionText:"${device.displayName} is ${theState}", isStateChange:true, displayed:true, linkText:"${device.displayName}")
state.forcedWakeUp = true
def cmdBlock = []
cmdBlock=wakeUpResponse(cmdBlock)
return [event, response(cmdBlock)]
}
//---------------------------
//
//---------------------------
def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {
logDebug 2, "basicv1.BasicSet $cmd"
def cmdValue = cmd.value
return openClosed(cmd, cmdValue)
}
//---------------------------
// SensorBinaryReport should never occur since all status change notifications are asynchronous via BasicSet
//---------------------------
def zwaveEvent(hubitat.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) {
logDebug 2, "sensorbinaryv1.SensorBinaryReport $cmd"
def cmdValue = cmd.sensorValue
return openClosed(cmd, cmdValue)
}
//---------------------------
//
//---------------------------
def zwaveEvent(hubitat.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
//def event = sensorValueEvent(cmd.sensorState)
logDebug 2, "sensoralarmv1.SensorAlarmReport $cmd.sensorState"
def event = sendEvent(name:"alarm", value:"$cmd.sensorState", descriptionText:"${device.displayName} is tampered with !", isStateChange:true, displayed:true, linkText:"${device.displayName}")
def cmdBlock = []
state.forcedWakeUp = true
cmdBlock=wakeUpResponse(cmdBlock)
return [event, response(cmdBlock)]
}
//---------------------------
//
//---------------------------
def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
// Next line needed because "update()" does not seem to work anymore
state.batteryInterval = (long) (24*60-45)*60*1000 // 1 day
def long nowTime = new Date().getTime()
logDebug 2, "batteryv1.BatteryReport ${cmd.batteryLevel}"
logDebug 2, "nowTime : ${nowTime}"
logDebug 2, "state.lastReportBattery : ${state.lastReportBattery}"
logDebug 2, "state.batteryInterval : ${state.batteryInterval}"
logDebug 2, "state.forcedWakeUp : ${state.forcedWakeUp}"
if ((nowTime-state.lastReportBattery > state.batteryInterval) || state.forcedWakeUp) {
def map = [ name: "battery", displayed: true, isStateChange:true, unit: "%" ]
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
state.lastReportBattery = nowTime
logDebug 1, "battery map : ${map}"
return [sendEvent(map)]
}
}
//---------------------------
//
//---------------------------
def zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport cmd) {
logDebug 2, "ConfigurationReport - Parameter#${cmd.parameterNumber}: ${cmd.configurationValue}"
}
//---------------------------
//
//---------------------------
def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelEndPointReport cmd) {
logDebug 2, "multichannelv3.MultiChannelCapabilityReport: ${cmd}"
}
//---------------------------
//
//---------------------------
def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCapabilityReport cmd) {
logDebug 2, "multichannelv3.MultiChannelCapabilityReport: ${cmd}"
}
//---------------------------
//
//---------------------------
def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {
logDebug 2, "versionv1.VersionReport: ${cmd}"
BigDecimal fw = cmd.firmware0Version //applicationVersion
fw = fw + cmd.firmware0SubVersion/10 // applicationSubVersion / 100
state.firmware = fw
}
//---------------------------
// MultiChannelCmdEncap and MultiInstanceCmdEncap are ways that devices can indicate that a message
// is coming from one of multiple subdevices or "endpoints" that would otherwise be indistinguishable
//---------------------------
def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x30: 1, 0x31: 2]) // can specify command class versions here like in zwave.parse
logDebug 2, ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}")
if (encapsulatedCommand) {
return zwaveEvent(encapsulatedCommand)
}
}
//---------------------------
//
//---------------------------
// Catch All command Handler in case of unexpected message
def zwaveEvent(hubitat.zwave.Command cmd) {
sendEvent(descriptionText: "!!! $device.displayName: ${cmd}", displayed: false)
}
//---------------------------
// When a Temperature Event got lost in transit, the Watchdog requests a forced report at next wake up
// The "reportNext()" alarm command is used to signal back from the Watchdog SmartApp to the sleepy Device Handler
//---------------------------
def reportNext(commandMsg) {
logDebug 3, "reportNext !"
logDebug 3, "commandMsg : ${commandMsg}"
state.forcedWakeUp = true
// IMPORTANT NOTE : when the batteryLevel becomes too low, Device reports become erratic, all periodic wakeUpNotifications stop
// and consequently BATTERYLEVEL IS NOT UPDATED ANYMORE every 24 hours, continuing to display the last (and obsolete) reported value.
// Curiously, asynchronous sensorMultilevelReports continue to arrive, for some time, making the Device look (partially) "alive"
// This section resets the displayed battery level to 1% when the battery level is obsolete by more than 48h.
// Next line may be needed because "update()" does not seem to work reliably anymore
state.batteryInterval = (long) (24*60-45)*60*1000 // 1 day
def long nowTime = new Date().getTime()
if (nowTime-state.lastReportBattery > 3*state.batteryInterval) { // reset batteryLevel to 1% if no update for 48-72 hours
logDebug 3, "obsolete (likely low) battery value : ${((nowTime-state.lastReportBattery)/3600000)} hours old"
sendEvent(name: "battery", displayed: true, isStateChange:true, unit: "%", value: 1, descriptionText: "${device.displayName} has a low battery")
state.lastReportBattery = nowTime
}
return []
}
///////////////////
// For Tests Purpose
///////////////////
//---------------------------
// Executed each time the Handler is updated
//---------------------------
def updated() {
logDebug 1, "Updated !"
logDebug 1, "param1 = $param1"
logDebug 1, "param2 = $param2"
logDebug 1, "param3 = $param3"
logDebug 1, "param5 = $param5"
logDebug 1, "param7 = $param7"
logDebug 1, "param9 = $param9"
logDebug 1, "param12 = $param12"
logDebug 1, "param13 = $param13"
logDebug 1, "param14 = $param14"
// All state.xxx attributes are Device-local, NOT Location-wide
state.isInitialized = false
state.lastReportedTemp = (float) -1000
state.lastReportTime = (long) 0
state.lastReportBattery = (long) 0
// Real-time clock of sensors (ceramic resonator) is up to 3% inaccurate
state.batteryInterval = (long) (24*60-45)*60*1000 // 1 Battery Report event every 24 hours, rounded up to the nearest hourly wakeup
state.maxEventInterval = (long) (4*60-10)*60*1000 // at least 1 Temperature Report event every 3:50 hours
state.parseCount=(int) 0
state.forcedWakeUp = true
if (!(state.deviceID)) {state.deviceID = device.name}
logDebug 1, "state.deviceID: ${state.deviceID}"
logDebug 1, "state.batteryInterval : ${state.batteryInterval}"
logDebug 1, "state.maxEventInterval : ${state.maxEventInterval}"
// For Test purpose; redondant with reportNext() => state.forcedWakeUp=1
sendEvent(name: "reportASAP", value: 1, isStateChange: true)
logDebug 1, "device.currentValue('reportASAP') : ${device.currentValue('reportASAP')}"
infos()
}
//---------------------------
//
//---------------------------
def installed()
{
logDebug 1, "installed"
state.driverVersion = "${version()}"
}
//---------------------------
// If you add the Configuration capability to your device type, this command will be called right
// after the device joins to set device-specific configuration commands.
//---------------------------
def configure() {
logDebug 1, "Configuring..."
logDebug 1, "device.displayName.substring(0,1) : ${device.displayName.substring(0,1)}"
delayBetween([
// Make sure sleepy battery-powered sensors send their WakeUpNotifications to the hub
zwave.wakeUpV2.wakeUpIntervalSet(seconds:wakeUpInterval, nodeid:zwaveHubNodeId).format(),
// NOTE : any asynchronous temperature query thru SensorMultilevelGet() does NOT reset the delta-Temp base value (managed by DS18B20 hardware)
zwave.configurationV2.configurationSet(parameterNumber: 1, size: 2, configurationValue: (1..0).collect { (param1.toInteger() >> (it * 8)) & 0xFF}).format(),
zwave.configurationV2.configurationSet(parameterNumber: 2, size: 1, configurationValue: [param2.toInteger()]).format(),
zwave.configurationV2.configurationSet(parameterNumber: 3, size: 1, configurationValue: [param3.toInteger()]).format(),
zwave.configurationV2.configurationSet(parameterNumber: 5, size: 1, configurationValue: [param5.toInteger()]).format(),
zwave.configurationV2.configurationSet(parameterNumber: 7, size: 1, configurationValue: [param7.toInteger()]).format(),
zwave.configurationV2.configurationSet(parameterNumber: 9, size: 1, configurationValue: [param9.toInteger()]).format(),
zwave.configurationV2.configurationSet(parameterNumber: 12, size: 1, configurationValue: [param12.toInteger()]).format(),
zwave.configurationV2.configurationSet(parameterNumber: 13, size: 1, configurationValue: [param13.toInteger()]).format(),
zwave.configurationV2.configurationSet(parameterNumber: 14, size: 1, configurationValue: [param14.toInteger()]).format(),
// inclusion of Device in Association#3 is needed to get delta-Temperature notification messages [cf Parameter#12 above]
zwave.associationV2.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format(),
// inclusion of Device in Association#2 is needed to enable SensorAlarmReport() Command [anti-Tampering protection]
zwave.associationV2.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]).format(),
// get zwave version information
zwave.versionV1.versionGet().format()
],1200)
}
//---------------------------
//
//---------------------------
def infos() {
if (!state.devices) { state.devices = [:] }
logDebug 1, "zwaveHubNodeId: ${zwaveHubNodeId}" // -> "1"
logDebug 1, "device.displayName: ${device.displayName}" // -> "JJG"
logDebug 1, "device.id: ${device.id}" // -> "75841488-ae76-4cac-b523-a2694e72c25a"
logDebug 1, "device.name: ${device.name}" // -> "T001"
logDebug 1, "device.label: ${device.label}" // -> "JJG"
logDebug 1, "device.data: ${device.data}" // -> "[endpointId:0, version: 2.1, MSR:010F-0700-2000]"
//logDebug 1, "'device.rawDescription': ${device.rawDescription}" // -> "0 0 0x2001 0 0 0 c 0x30 0x9C 0x60 0x85 0x72 0x70 0x86 0x80 0x84 0x7A 0xEF 0x2B"
}
//---------------------------
//
//---------------------------
private logDebug(level, msg) {
if ((level>=debugLevel.toInteger()) && (settings?.debugOutput || settings?.debugOutput == null)) {
log.debug "$msg"
}
}