'Fibaro Z-Wave FGK-101 Temperature & Door/Window Sensor' Full Support Handler

Anyone knows how i can make use of the binary sensor input on this device? I want that so i can use it as a doorbell without the magnet sensor

It is explained in Diagram 3 of the FGK-101 Operating Manual, downloadable here.
Short-circuiting GND+IN lines will change the sensor status (Open->Close AFAIK).
Furthermore, modifying Parameter n°3 (thru a Custom Handler) can allow various behaviors for this switch.

Exactly. But the question was how i can use that in ST? And how do i set parameters ?

@geeji Do you know how I would create a virtual device that would mimic the status of the contact sensor? I am using the FGK-101 with you DH, working great BTW, to monitor the temp and water level in my fishtank. What I am running into is that the open/closed status of the contact sensor is just as important to see on the things page as is the temp. I have a 'Room" created that has the devices for my tank, want to also see the status of both the temp and contact on that page also.

@ecam315 I am not sure I understand your question, or more precisely what is your problem and what you hope to achieve with this “virtual device”.
The “IN” contact of the FGK-101 can be programmed to get various alarms when IN is tied to GND, see FGK-101 Operating Manual, parameters 1, 3, 5, 13.
The SmartThings mobile application does not allow you to customize the “Room” window, only the “Device” window.
So if you want to make believe the SmartThings mobile application you have a “split personality” device, just for the sake of presenting all the informations you like within the same single window, I am afraid it is out of anybody’s control (except SmartThings developers).
The only thing a Custom Handler controls is the Device window, which currently shows Open/Close status, but could as well present instead or in addition the IN Open/Grounded status.

ST does apparently use the standard drivers unless you select yours. However, getting that undone is impossible until you select the proper one of the four drivers visible in a long list.

To select JJs custom driver, you MUST:

  • Go to My Devices
  • Click on the sensor’s Display Name to get the page of its attributes
  • Click Edit
  • The Type box is a loooooong scrolling list. The 3 entries for Fibaro Door/Window sensor are the Wrong ones
  • Scroll All the way to the bottom, where you will see “JJ’s Fibaro FGK-101 Handler” and choose it. Accept no substitutes.
  • Click Update to go back to previous page. You should now see JJ’s shown as the Type.

After wasting three hours due to missing instructions for this step, when I went back to the app, I immediately saw the temperature displayed, and clicking the switch updated it instantly.

@EldRitch : thank you very much for adding this critical step.
When I initially paired my FGK-101 devices with the SmartThings Hub, the list was much shorter and AFAIR, I directly paired the Device with the right Handler.
Anyway, checking AFTER pairing that you have the proper Handler shown in :
My Devices / Type / JJ’s Fibaro FGK-101 Handler
is essential.
But before that, you have to copy my custom Handler code into your own space, going to :
My Device Handlers / +Create New Device handler / From Code / <paste_from_github_my_custom_code>.

Unfortunately, I cannot modify anymore the first post of this thread (SmartThings put a time limit for late modifications :frowning: ), so I apologize to anybody who will have to scan thru there to discover why it is not working for him.

It works great. Thanks!

@geeji
Thank you for all the work you put into this handler, it works very well! I changed/added a few things to your code, which I’ll share below for anyone who wants to try it, and if you’d like to add some of the changes to your next update on GitHub.

Here’s a couple of pictures to show what it looks like (temperature colors on pictures may look different as I’m using Celsius display, and I changed a few of the color values for myself. Temperature colors are not modified in the modded code below) :

I modded the following lines, after the code was pasted to the IDE environment first (so the blank lines are removed), and in order:

-Line # 74 - changed name from “JJ’s Fibaro FGK-101 Handler” to “Modded JJ’s Fibaro FGK-101 Handler” to avoid conflict

-Line # 123 - changed width and height so all tiles are the same size.

-Line # 125 - added “F” (Fahrenheit) to display next to temperature value

-Line # 151 - changed so it displays “BATTERY” instead of “BATT. @” on “Right Now” screen

-After Line # 152 - added/created lines # 153 to 156 to add alarm tile on “Right Now” screen that shows if sensor is in “TAMPERED” or “SECURE” state (when underside tamper button is being pressed and released), and background color for each state on both “Right Now” and “Recently” screens (also requires changes of lines # 161, 441, and 444

-Line # 160 - changed main tile to “contact” (open/close)

-Line # 161 - added detail to display “alarm” tile, and changed order of tiles

-After Line # 440 - added/created line # 441 to differentiate “tampered” and “secure” states

-Line # 444 - changed so it displays proper “tampered” or “secure” states on “Recently” screen, instead of just showing “tampered” twice when pressing and releasing the underside tamper button

-Line # 465 - changed so it displays “battery is low!” instead “has a low battery”

MODDED Code :

/**
* Fibaro Z-Wave FGK-101 Temperature & Door/Window Sensor Handler [v0.9.4.1, 17 May 2016]
*
* 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!
* 0x9C - 156 : SENSOR_ALARM V1
* NOT used in Handler :
* 0x2B - 43 : SCENE_ACTIVATION V1
* 0x86 - 134 : VERSION 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
******************************************************************************************************************************/
/******************************************************************************************************************************
* List of Known Bugs / Oddities / Missing Features :
* - valueTitle does not show displayNames on mobile Dashboard/Things page;
* attempted workaround using : valueTile(){unit:'${displayName}') failed
* - valueTile behaves differently on mobile Dashboard (interpolated colors) from Simulator (step-wise colors)
* - using Preferences values instead of hard-coded values for some parameters would be nicer
*****************************************************************************************************************************/
metadata {
definition (name: "Modded 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"
attribute "reportASAP", "number"
attribute "deviceTime", "number"
// 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 ?!!
}
simulator {
status "open": "command: 2001, payload: FF"
status "closed": "command: 2001, payload: 00"
def T_values=[10,14,14.9,15,17,17.9,18,19,19.9,20,22,22.9,23,24,44,44.9,45,46,100]
def float Ti
for (int i = 0; i <= T_values.size()-1; i += 1) {
Ti=T_values.get(i)
def theSensorValue = [(short)0, (short)0, (short)(Ti*100)/256, (short)(Ti*100)%256]
status "temperature ${Ti}°C": zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint:2, destinationEndPoint:2).encapsulate(zwave.sensorMultilevelV2.sensorMultilevelReport(scaledSensorValue: i, precision: 2, scale: 0, sensorType: 1, sensorValue: theSensorValue, size:4)).incomingMessage()
}
}
tiles {
/* valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) {
// label:'${name}', label:'${currentValue}', unit:"XXX" work, but NOT label:'${device.name}', label:'${displayName}', unit:'${unit}', ...
state "temperature", label:'${currentValue}°', unit:"C", icon: "st.alarm.temperature.normal",
// redondant lines added to avoid color interpolation on Dashboard (a feature or a bug ?!)
backgroundColors:[ // ***on IDE Simulator*** // ***on iPad App***
[value: 14, color: "#0033ff"], // °C <=14 : dark blue // °C <=14 : dark blue
//[value: 14.1, color: "#00ccff"], <- decimal value IGNORED by the Tile !!!
//[value: 14.5], // 15< °C <=19 : light blue // 14< °C <15 : interpolated dark blue<-> light blue
[value: 15, color: "#00ccff"], // 16< °C <=19 : light blue // 15<=°C <=19 : light blue
[value: 17, color: "#00ccff"], // 16< °C <=19 : light blue // 15<=°C <=19 : light blue
//[value: 17.5], // 15< °C <=19 : light blue // 14< °C <15 : interpolated light blue<->blue-green
[value: 18, color: "#ccffcc"], // 15< °C <=19 : light blue // 18<=°C <=19 : blue-green
[value: 19, color: "#ccffcc"], // 15< °C <=19 : light blue // 19°C : blue-green
//[value: 19.5], // 19< °C <=21 : blue-green // 19< °C <20 : interpolated blue-green<->green
[value: 20, color: "#ccff00"], // 19< °C <=21 : blue-green // 20<=°C <=21 : green
[value: 22, color: "#ccff00"], // 21< °C <=23 : green // 22°C : green
//[value: 22.5], // 23< °C <=45 : orange // 22< °C <23 : interpolated green<-> orange
[value: 23, color: "#ffcc33"], // 23< °C <=45 : orange // 23<=°C <=44 : orange
[value: 43, color: "#ffcc33"], // 23< °C <=45 : orange // 44°C : orange
//[value: 43.5], // 45< °C : red // 44< °C <45 : interpolated orange <-> red
[value: 44, color: "#ff3300"] // 45< °C : red // 45<=°C : red
]
}
*/
valueTile("temperatureF", "device.temperature", inactiveLabel: false, canChangeIcon: true, canChangeBackground: true) {
// label:'${name}', label:'${currentValue}', unit:"XXX" work, but NOT label:'${device.name}', label:'${displayName}', unit:'${unit}', ...
state "temperature", label:'${currentValue}° F', unit:"F", icon: "st.alarm.temperature.normal",
// redondant lines added to avoid color interpolation on Dashboard (a feature or a bug ?!)
backgroundColors:[ // ***on IDE Simulator*** // ***on iPad App***
[value: 57, color: "#0033ff"], // °C <=14 : dark blue // °C <=14 : dark blue
//[value: 14.1, color: "#00ccff"], <- decimal value IGNORED by the Tile !!!
//[value: 14.5], // 15< °C <=19 : light blue // 14< °C <15 : interpolated dark blue<-> light blue
[value: 59, color: "#00ccff"], // 16< °C <=19 : light blue // 15<=°C <=19 : light blue
[value: 63, color: "#00ccff"], // 16< °C <=19 : light blue // 15<=°C <=19 : light blue
//[value: 17.5], // 15< °C <=19 : light blue // 14< °C <15 : interpolated light blue<->blue-green
[value: 64, color: "#ccffcc"], // 15< °C <=19 : light blue // 18<=°C <=19 : blue-green
[value: 66, color: "#ccffcc"], // 15< °C <=19 : light blue // 19°C : blue-green
//[value: 19.5], // 19< °C <=21 : blue-green // 19< °C <20 : interpolated blue-green<->green
[value: 68, color: "#ccff00"], // 19< °C <=21 : blue-green // 20<=°C <=21 : green
[value: 72, color: "#ccff00"], // 21< °C <=23 : green // 22°C : green
//[value: 22.5], // 23< °C <=45 : orange // 22< °C <23 : interpolated green<-> orange
[value: 73, color: "#ffcc33"], // 23< °C <=45 : orange // 23<=°C <=44 : orange
[value: 109, color: "#ffcc33"], // 23< °C <=45 : orange // 44°C : orange
//[value: 43.5], // 45< °C : red // 44< °C <45 : interpolated orange <-> red
[value: 111, color: "#ff3300"] // 45< °C : red // 45<=°C : red
]
}
standardTile("contact", "device.contact") {
state "open", label: 'open'/* in English :'${name}' */, icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
state "closed", label: 'closed'/* in English :'${linkText}' */, icon: "st.contact.contact.closed", backgroundColor: "#79b821"
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
state "battery", label:'battery ${currentValue}%' /*battery*/, unit:""
}
standardTile("alarm", "device.alarm") {
state("secure", label:'secure', backgroundColor:"#79b821")
state("tampered", label:'tampered', backgroundColor:"#ffa81e")
}
//Select temperatureF if Location temperature Scale is °F
//main(["temperature"])
//details(["temperature", "contact", "battery"])
main(["contact"])
details(["contact", "temperatureF", "battery", "alarm"])
}
}
////////////////////////////////
// parse events into attributes
////////////////////////////////
def parse(String description) {
state.parseCount=state.parseCount+1
settings.debugLevel = 2 // set to 1 or 2 when experimenting
if (debugLevel>=1) {log.debug "--------------------------Parsing... ; state.parseCount: ${state.parseCount}--------------------------"}
if (debugLevel>=2) {log.debug "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)
if (debugLevel>=1) {log.debug "Parsed ${cmd} to ${result.inspect()}"}
} else {
log.debug "Non-parsed event: ${description}"
}
return result
}
/* Duncan's original fix
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
def version = [0x20:1, 0x30: 2, 0x31: 2, 0x60: 3, 0x70: 2, 0x72: 2, 0x84: 1, 0x9C: 1][cmd.commandClass as Integer] ?: 1
zwaveEvent(zwave.commandClass(cmd.commandClass, version)?.command(cmd.command)?.parse(cmd.data))
}
*/
//SmartThings v2 Hub forces CRC16-encoded replies from FGK-101 Device (v1 Hub did not)
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
log.debug "CRC16.......... cmd : ${cmd}"
def versions = [0x20:1, 0x30: 1, 0x31: 2, 0x60: 3, 0x70: 2, 0x72: 2, 0x84: 2, 0x9C: 1]
// def encapsulatedCommand = cmd.encapsulatedCommand(versions)
def version = versions[cmd.commandClass as Integer]
log.debug "commandClass : ${cmd.commandClass}"
log.debug "version : ${version}"
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
log.debug "ccObj : ${ccObj}"
log.debug "cmd.command : ${cmd.command}"
log.debug "cmd.data : ${cmd.data}"
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (!encapsulatedCommand) {
log.debug "Could not extract command from ${cmd}"
} else {
zwaveEvent(encapsulatedCommand)
}
}
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) {
if (debugLevel>=2) {log.debug "state.isInitialized : ${state.isInitialized}"}
cmdBlock << zwave.wakeUpV2.wakeUpIntervalSet(seconds:60*60, 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 "*"
def byte tempQuantumSixteenth
if (device.displayName.substring(0,1).equals("*")) {
tempQuantumSixteenth = 16 /* 16/16=1°C = 1.8°F */
} else {
tempQuantumSixteenth = 5 /* 5/16=0.31°C = 0.56°F */
}
cmdBlock << zwave.configurationV2.configurationSet(parameterNumber: 12/*for FGK101*/, size: 1, configurationValue: [tempQuantumSixteenth]).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 <=2) {log.debug "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
//cmdBlock << zwave.basicV1.basicGet().format()
//cmdBlock << "delay 1200"
//next 2 lines redondant too : SensorBinaryReport(EndPoint: 1) == BasicReport
//cmdBlock << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: 1, destinationEndPoint: 1, commandClass:0x30 /*Sensor Binary*/, command:2).format()
//cmdBlock << "delay 1200"
//cmdBlock << zwave.sensorAlarmV1.sensorAlarmGet().format()
//cmdBlock << "delay 1200"
//cmdBlock < dynamic: false, endPoints: 2
//cmdBlock << "delay 1200"
//cmdBlock < commandClass: [48], dynamic: false, endPoint: 1, genericDeviceClass: 32, specificDeviceClass: 1
//cmdBlock << "delay 1200"
//cmdBlock < commandClass: [49], dynamic: false, endPoint: 2, genericDeviceClass: 33, specificDeviceClass: 1
//cmdBlock << "delay 1200"
//next Command should normally be needed only once, at configuration time, but because of a random SmartThings platform bug, the wakeUp period may be reset to 1mn !
//cmdBlock << zwave.wakeUpV2.wakeUpIntervalSet(seconds:60*60, nodeid:zwaveHubNodeId).format() // NB : may have to wait 30mn for that value to be refreshed !
//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 <=2) {
log.debug "wakeUpNoMoreInformation()"
log.debug "cmdBlock : ${cmdBlock}"
}
return cmdBlock
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
if (debugLevel>=2) {log.debug "wakeupv2.WakeUpNotification $cmd"}
def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false)
def cmdBlock = []
cmdBlock=wakeUpResponse(cmdBlock)
return [event, response(cmdBlock)]
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) {
def float scaledSensorValue = cmd.scaledSensorValue
// Adjust measured temperature based on previous manual calibration; FGK-101 is natively °C
switch (device.name) {
case 'T005' : //JJG
scaledSensorValue = scaledSensorValue + 0.0554
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T006' : //MLE
scaledSensorValue = scaledSensorValue + 0.0297
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T003' : //MPT
scaledSensorValue = scaledSensorValue - 0.0603
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T002' : //NBN
scaledSensorValue = scaledSensorValue - 0.0758
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T004' : //SCU
scaledSensorValue = scaledSensorValue + 0.0011
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T007' : //FSU
scaledSensorValue = scaledSensorValue + 0.0025
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T008' :
scaledSensorValue = scaledSensorValue - 0.0146
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T009' :
scaledSensorValue = scaledSensorValue + 0.0383
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T010' :
scaledSensorValue = scaledSensorValue + 0.0383
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T011' :
scaledSensorValue = scaledSensorValue - 0.0889
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T012' :
scaledSensorValue = scaledSensorValue - 0.0532
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T013' :
scaledSensorValue = scaledSensorValue + 0.0383
log.debug "Temp Adjust for : ${device.name}"
break;
case 'T014' : //*ext*//
scaledSensorValue = scaledSensorValue - 0.0160
log.debug "Temp Adjust for : ${device.name}"
break;
}
//Round to nearest 1 decimal temperature value; convert to °F if needed
def float ftempSign = temperatureScaleFC(scaledSensorValue) < 0 ? -1 : +1
def float ftemp = ftempSign * ((((int) (temperatureScaleFC(scaledSensorValue).abs()*100+5)/10)*1.0)/10)
if (debugLevel>=2) {
log.debug "ftempSign : ${ftempSign}"
log.debug "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
def long nowTime = new Date().getTime()
if (debugLevel>=2) {
log.debug "cmd.scaledSensorValue : ${cmd.scaledSensorValue}"
log.debug "correction : ${scaledSensorValue-cmd.scaledSensorValue}"
log.debug "device.displayName : ${device.displayName}"
log.debug "'Date().getTime()' : ${new Date().getTime()}"
log.debug "state.forcedWakeUp : ${state.forcedWakeUp}"
log.debug "state.maxEventInterval : ${state.maxEventInterval}"
log.debug "state.lastReportTime : ${state.lastReportTime}"
log.debug "nowTime : ${nowTime}"
log.debug "(nowTime-state.lastReportTime > state.maxEventInterval) : ${(nowTime-state.lastReportTime > state.maxEventInterval)}"
log.debug "ftemp : ${ftemp}"
log.debug "state.lastReportedTemp: ${state.lastReportedTemp}"
}
// Adjust temperature report sensitivity for outside thermometers whose displayName starts with "*"
def float tempQuantum
if (device.displayName.substring(0,1).equals("*")) {
tempQuantum = temperatureScaleFC(0.9999)-temperatureScaleFC(0)
} else {
tempQuantum = temperatureScaleFC(0.2999)-temperatureScaleFC(0)
}
log.debug "((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
log.debug "map.value : ${map.value}"
log.debug "map.unit : ${map.unit}"
break;
}
if (debugLevel>=2) {
log.debug "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 createEvent(map)
}
}
def sensorValueEvent(value) {
if (value) {
createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")
} else {
createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
}
}
// BasicReport should never occur since all status change notifications are asynchronous via BasicSet
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
sensorValueEvent(cmd.value)
if (debugLevel>=2) {log.debug "basicv1.BasicReport $cmd.value"}
}
// To check that WakeUpInterval does not revert to 1mn instead of 1h
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpIntervalReport cmd) {
if (debugLevel>=2) {log.debug "WakeUpIntervalReport $cmd"}
if (cmd.seconds!=60*60) {
def result = createEvent(name:"WakeUpIntervalReport", 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"
if (debugLevel>=2) {log.debug "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 = createEvent(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(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
if (debugLevel>=2) {log.debug "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(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) {
if (debugLevel>=2) {log.debug "sensorbinaryv1.SensorBinaryReport $cmd"}
def cmdValue = cmd.sensorValue
return openClosed(cmd, cmdValue)
}
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
def theStateTwo = cmd.sensorState == 0 ? "tampered" : "secure"
//def event = sensorValueEvent(cmd.sensorState)
if (debugLevel>=2) {log.debug "sensoralarmv1.SensorAlarmReport $cmd.sensorState"}
def event = createEvent(name:"alarm", value:"${theStateTwo}", descriptionText:"${device.displayName} is ${theStateTwo}", isStateChange:true, displayed:true, linkText:"${device.displayName}")
def cmdBlock = []
state.forcedWakeUp = true
cmdBlock=wakeUpResponse(cmdBlock)
return [event, response(cmdBlock)]
}
def zwaveEvent(physicalgraph.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()
if (debugLevel>=2) {
log.debug "batteryv1.BatteryReport ${cmd.batteryLevel}"
log.debug "nowTime : ${nowTime}"
log.debug "state.lastReportBattery : ${state.lastReportBattery}"
log.debug "state.batteryInterval : ${state.batteryInterval}"
log.debug "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} battery is low!"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
}
state.lastReportBattery = nowTime
log.debug "battery map : ${map}"
return [createEvent(map)]
}
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
if (debugLevel>=2) {log.debug "ConfigurationReport - Parameter#${cmd.parameterNumber}: ${cmd.configurationValue}"}
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelEndPointReport cmd) {
if (debugLevel>=2) {log.debug "multichannelv3.MultiChannelCapabilityReport: ${cmd}"}
}
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCapabilityReport cmd) {
if (debugLevel>=2) {log.debug "multichannelv3.MultiChannelCapabilityReport: ${cmd}"}
}
// 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(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x30: 1, 0x31: 2]) // can specify command class versions here like in zwave.parse
if (debugLevel>=2) {log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}")}
if (encapsulatedCommand) {
return zwaveEvent(encapsulatedCommand)
}
}
// Catch All command Handler in case of unexpected message
def zwaveEvent(physicalgraph.zwave.Command cmd) {
createEvent(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
def reportNext(commandMsg) {
log.debug "reportNext !"
log.debug "commandMsg : ${commandMsg}"
state.forcedWakeUp = true
return []
}
///////////////////
// For Tests Purpose
///////////////////
// Executed each time the Handler is updated
def updated() {
log.debug "Updated !"
// 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 // at least 1 Battery Report event every 23:15 hours
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}
log.debug "state.deviceID: ${state.deviceID}"
log.debug "state.batteryInterval : ${state.batteryInterval}"
log.debug "state.maxEventInterval : ${state.maxEventInterval}"
// For Test purpose; redondant with reportNext() => state.forcedWakeUp=1
sendEvent(name: "reportASAP", value: 1, isStateChange: true)
log.debug "device.currentValue('reportASAP') : ${device.currentValue('reportASAP')}"
infos()
}
// 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() {
log.debug "Configuring..."
// Adjust temperature report sensitivity for outside thermometers whose displayName starts with "*"
def byte tempQuantumSixteenth
log.debug "device.displayName.substring(0,1) : ${device.displayName.substring(0,1)}"
if (device.displayName.substring(0,1).equals("*")) {
tempQuantumSixteenth = 16 /* 16/16=1°C = 1.8°F */
} else {
tempQuantumSixteenth = 5 /* 5/16=0.31°C = 0.56°F */
}
log.debug "tempQuantumSixteenth : ${tempQuantumSixteenth}"
delayBetween([
// Make sure sleepy battery-powered sensors send their WakeUpNotifications to the hub
zwave.wakeUpV2.wakeUpIntervalSet(seconds:60*60, 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: 12/*for FGK101*/, size: 1, configurationValue: [tempQuantumSixteenth]).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()
])
}
def infos() {
if (!state.devices) { state.devices = [:] }
log.debug "zwaveHubNodeId: ${zwaveHubNodeId}" // -> "1"
log.debug "device.displayName: ${device.displayName}" // -> "JJG"
log.debug "device.id: ${device.id}" // -> "75841488-ae76-4cac-b523-a2694e72c25a"
log.debug "device.name: ${device.name}" // -> "T001"
log.debug "device.label: ${device.label}" // -> "JJG"
log.debug "device.data: ${device.data}" // -> "[MSR:010F-0700-2000, endpointId:0]"
//log.debug "'device.rawDescription': ${device.rawDescription}" // -> "0 0 0x2001 0 0 0 c 0x30 0x9C 0x60 0x85 0x72 0x70 0x86 0x80 0x84 0x7A 0xEF 0x2B"
}

@dzhimc : Congratulations for your successful modifications ! Modifying a custom handler is definitely the proper way to understand many intricacies of the convoluted SmartThings way to handlers

Just a small comment : I am not too sure about your “Secure” / “Tampered” states separation : AFAIK, you get an EVENT when the bottom TMP switch changes state one way (“pushed”->“released”), but you cannot read the current switch STATE (such as : pushed = secure), and the reverse transition (“released”->“pushed”) does not generate any event.

What this means is that you never have 100% sure knowledge this TMP switch is “pushed” (or “released”) : the best you can do is :

  1. assume some initial state (ex: “pushed” = “secure”)
  2. make sure you track from within the handler, with a remanent variable, this state
  3. change the state (“secure”->“tampered”) whenever you get a “tampering event”
  4. use the IDE to manually signal to the handler the reverse “tampered”->“secure” transition

But steps (3) and (4) are problematic, since step (4) is manual and step (3) assumes no event is ever lost, which is definitely not my experience with SmartThings, for a multiplicity of reasons unrelated to the FGK-101 sensor or this handler.
For instance, changing the battery within the FGK-101 will generate a “tampered” event, but no “secured again” event will ever be automatically be generated.

If what you want is just a reminder of the more or less recent occurrence of a TMP “tampered with” alarm, you could replace step (4) with an automatic return to “secure” after some time-out (ex: 24h), managed from within the handler.
But from a security point of view, knowing 24h later that one of your door sensors has been tampered with may be of dubious utility and a case of closing the barn door after the horse/thief escaped

By definition, any tamper alarm is useful ONLY if notified and taken care of IMMEDIATELY.

BTW, you may also want to remove one of your duplicated posts.
Have fun with SmartThings and Fibaro sensors ! :slight_smile:

@geeji
I appreciate you taking the time to provide this information. I’ve only had my SmartThings system for a few weeks and there’s a lot to learn about how it all works, and I must say this helps a lot to understand a few things better!

Thanks for your feedback and keep up the great work! :slight_smile:

@geeji or anyone else, how do I change the frequency at which it checked the temp? I have a DS18B20 connected, and need to check/report the temp every 5 minutes. I understand this will run the battery down quicker, but am willing to pay that price.

@ecam315 : to change the wakeup period to 5mn and to produce a temperature event EVEN when no temperature change occurred during those 5mn, make a private copy of the handler, and change lines 233 and 580 from :

zwave.wakeUpV2.wakeUpIntervalSet(seconds:60*60

to

zwave.wakeUpV2.wakeUpIntervalSet(seconds:5*60

and insert

tempQuantum = 0.0

before line 389

if (((ftemp-state.lastReportedTemp).abs()>tempQuantum)

But this will change the battery autonomy from about 1 year to less than 1 month !
I am not sure what is your exact need, but if you want to track accurately the temperature in some place, the handler as it stands will generate an event FOR ANY TEMPERATURE CHANGE > 0.3°C (= 0.5°F), independently of the FGK-101 wakeUpInterval.

I will try those changes.

I am reading the temp of my aquarium and then using that via the virtual thermostat smart app to turn the heater on and off. I am getting a wild swing in temp right now, so trying to figure out if it is the temp sensor or the smart app.

I will be switching this out for a wemos d1 to control this at some point, with reporting to SmartThings, just need something in the meantime.

Well, I do not know how the “virtual thermostat” SmartApp works, but it should base the regulation on the last temperature accurately read, wether it was 5mn ago or 2 hours ago (with less than 0.3°C change since).
Beware that your wild swings could very likely be the result of your feedback loop : a feedback loop with too much inertia (read : phase shift) and 0/100% control of a too powerful heater turns very easily into an oscillator. Search the web for “Feedback Control of Dynamic Systems”, “root-locus design”, etc


I just bought a FGK-101 and some DS18B20 sensors, and I cannot get this code to work. Similar to some other users, once I switch to the custom device handler, it does not work at all. Neither the open/close or the temperature show up in the SmartThings app. I am using a V2 hub, and am in the US.

Well this is odd. Mine was working perfectly and then I noticed it wasn’t changing it’s reporting. I assumed it was because my battery was dead or I just lost connection to the sensor. I’ll change the battery and see if anything changes.

Woo

I just checked, and all my 13 FGK-101 work now.
But I am using V1 hubs, and am not in the US, so the problem may be with the US cloud.

Here is a debug log snippet. This is logged on a tamper switch press:

45834722-628a-4293-b5a1-a1f26e6d8503  7:52:23 AM: debug Parsed SecurityMessageEncapsulation(commandByte: [], commandClassIdentifier: 132, commandIdentifier: 7, reserved13: 0, secondFrame: false, sequenceCounter: 0, sequenced: false) to ['descriptionText':!!! Fibaro Door/Window Sensor ZW5: SecurityMessageEncapsulation(commandByte: [], commandClassIdentifier: 132, commandIdentifier: 7, reserved13: 0, secondFrame: false, sequenceCounter: 0, sequenced: false), 'displayed':false, 'isStateChange':false, 'linkText':'Fibaro Door/Window Sensor ZW5']
45834722-628a-4293-b5a1-a1f26e6d8503  7:52:23 AM: debug --------------------------Parsing... ; state.parseCount: 4--------------------------

@ProHill : As short as your “snippet” is, it looks like SmartThings (or Fibaro ?) changed AGAIN unilaterally the way Hub v2 exchanges messages with FGK-101 Fibaro Devices : the previous problems we had, and solved, at the beginning of 2016 was coming for a new forced encryption using “crc16encapv1.Crc16Encap” commands.
From your snippet, it looks like the “crc16encapv1.Crc16Encap” commands have been switched to “securityv1.SecurityMessageEncapsulation” commands : same function, different commands.
And since the current version of the FGK-101 custom handler expects either no encryption (Hub v1) or Crc16Encap encryption (Hub v2), it fails at decoding the new messages (in your snippet a wakeup/alarm command).

Unfortunately, I do not have a v2 Hub, so testing a correction will, again, be a lengthy transatlantic process, involving a patient tester working with me for tests purposes and having a v2 Hub.
If such a person volunteers, I can try to make it work again.
But to be plain, I am pissed off that SmartThings regularly breaks down custom handlers by making unwarranted changes in the lower Z-wave layers : the whole idea of a semi-open platform and customers-made custom handlers should involve a minimum of upward compatibility : obviously it does NOT :thumbsdown: