[Help needed] Fibaro FGK-10x z-wave Door window sensor temperature reporting

Hello all,

I can't get the temperature reporting to work on this sensor. Can anyone help please?
The zwave.sensorMultilevelV5.sensorMultilevelGet command never seems to result in a report. The associationGet commands work correctly. I've been trying a lot of settings but never got a report.

The sensor works fine in ST with the same driver which I migrated as I'm not finding a Hubitat version.

Thanks

I moved this from ST to Hubitat and here's what worked. I'm using four of these FGK-10x sensors, but the one I just migrated has newer firmware than three of them ( 3.2 vs 2.5) which I have not migrated yet.

EDIT: For FGK-10x with the older 2.5 firmware, look further down in this thread for a working draft driver (may be newer ones posted after) at post 42:

This works for the 3.2 firmware:

  1. Exclude from ST and factory reset the sensor.

  2. Include into Hubitat. It was detected as "device" so I switched to the Fibaro Door/Window Sensor 2 driver.

  3. Configure the Temperature Reporting Threshold: and Temperature Reporting Interval: parameters so you will actually get temp reporting.

The sensor is reporting temps just fine now in testing with hot/cold water.

I'm using solar charging and NiMh cells so the sensor is never out of power.
5V solar panel connected to 3 x AA NiMh cells :slight_smile:

Will post pics once the forum allows me to.

2 Likes

Sorry...and each sensor is connected to a DS18B20 external temp probe.

The annoying thing about these sensors is that they are using ER14250 batteries. These are difficult to come by, and as reported elsewhere in the forum, when you do get them, they often don't work. I have the same experience - I have loads of the ER14250 batteries. 80% of them don't work out of the plastic despite testing 3.6V. I've been unable to determine why some batteries work and others don't. If the sensor displays a blue LED when the battery is inserted, the battery is good. If not, throw the battery away.
But I've discovered that normal CR2 (3.0V) batteries work fine. The only issue is that the cover won't close with the CR2 battery, but I don't suppose that is much of a problem for this use case.
I have yet to test how long the CR2 battery lasts, but at least this could be an emergency solution if one can't get hold of the ER1420 battery.

That’s why I use the solar setup. It feeds ~ 4 volts to the device and because they are solar charged, the cells never die. I’ve had four sensors running for about 2 years now. They are outside and see -35C in winter.

1 Like

So as many have found (and maybe not realized it) the FGK-10x has a 3.2 firmware that works fine with Hubitat's built in driver, "Fibaro Door/Window Sensor 2"

The older firmware 2.5 versions which sadly I bought later will not work. I've reviewed a few threads here to review what other's were trying and the best I could do is get open/close status to work, but no temperature.

This code will give you open/close with the 2.5 firmware (older) sensors but no temp. If anyone has ideas, I'm all ears :slight_smile:

/**
 *  Device Type Definition File
 *
 *  Device Type:		Fibaro Door/Window Sensor
 *  File Name:			fibaro-door-window-sensor.groovy
 *	Initial Release:	2014-12-10
 *	@author:			Todd Wackford
 *  Email:				todd@wackford.net
 *  @version:			1.0
 *
 *  Copyright 2014 SmartThings
 *
 *  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.
 *
 */
  
 /**
 * Sets up metadata, simulator info and tile definition. The tamper tile is setup, but 
 * not displayed to the user. We do this so we can receive events and display on device 
 * activity. If the user wants to display the tamper tile, adjust the tile display lines
 * with the following:
 *		main(["contact", "temperature", "tamper"])
 *		details(["contact", "temperature", "battery", "tamper"])
 *
 * @param none
 *
 * @return none
 */
 metadata {
	definition (name: "Fibaro Door/Window Sensor", namespace: "smartthings", author: "SmartThings", runLocally: true, minHubCoreVersion: '000.021.00001', executeCommandsLocally: true) {
		capability 	"Temperature Measurement"     
		capability 	"Contact Sensor"
		capability 	"Sensor"
		capability 	"Battery"
		capability 	"Configuration"
		capability  "Health Check"
        
        command		"resetParams2StDefaults"
        command		"listCurrentParams"
        command		"updateZwaveParam"
        command		"test"

		fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x85,0x72,0x70,0x86,0x80,0x56,0x84,0x7A,0xEF,0x2B", deviceJoinName: "Fibaro Open/Closed Sensor"
	}

	simulator {
		// messages the device returns in response to commands it receives
		status "open"  :  	"command: 2001, payload: FF"
		status "closed":	"command: 2001, payload: 00"

		for (int i = 0; i <= 100; i += 20) {
			status "temperature ${i}F": new hubitat.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport(
				scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1).incomingMessage()
		}

		for (int i = 0; i <= 100; i += 20) {
			status "battery ${i}%": new hubitat.zwave.Zwave().batteryV1.batteryReport(
				batteryLevel: i).incomingMessage()
		}
	}

	tiles {
		standardTile("contact", "device.contact", width: 2, height: 2) {
			state "open",   label: '${name}', icon: "st.contact.contact.open",   backgroundColor: "#e86d13"
			state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
		}
		valueTile("temperature", "device.temperature", inactiveLabel: false) {
			state "temperature", label:'${currentValue}°',
			backgroundColors:[
				[value: "", color: "#ffffff"],
                [value: 31, color: "#153591"],
				[value: 44, color: "#1e9cbb"],
				[value: 59, color: "#90d2a7"],
				[value: 74, color: "#44b621"],
				[value: 84, color: "#f1d801"],
				[value: 95, color: "#d04e00"],
				[value: 96, color: "#bc2323"]
			]
		}
        standardTile("tamper", "device.alarm") {
			state("secure", label:'secure',    icon:"st.locks.lock.locked",   backgroundColor:"#ffffff")
			state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#00a0dc")
		}
		valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
			state "battery", label:'${currentValue}% battery', unit:""
		}
		standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
			state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
		}

		//this will display a temperature tile for the DS18B20 sensor
		//main(["contact", "temperature"])//COMMENT ME OUT IF NO TEMP INSTALLED
		//details(["contact", "temperature", "battery"])	//COMMENT ME OUT IF NO TEMP INSTALLED
        
        //this will hide the temperature tile if the DS18B20 sensor is not installed
		main(["contact"])										//UNCOMMENT ME IF NO TEMP INSTALLED
		details(["contact", "battery"])						//UNCOMMENT ME IF NO TEMP INSTALLED
	}
}

/**
 * Mapping of command classes and associated versions used for this DTH
 */
private getCommandClassVersions() {
	[
		0x30: 1,  // Sensor Binary
		0x31: 2,  // Sensor MultiLevel
		0x56: 1,  // Crc16Encap
		0x60: 3,  // Multi-Channel
		0x70: 2,  // Configuration
		0x72: 2,  // Manufacturer Specific
		0x80: 1,  // Battery
		0x84: 1,  // WakeUp
		0x9C: 1   // Sensor Alarm
	]
}

// Parse incoming device messages to generate events 
def parse(String description)
{
	def result = []
	def cmd = zwave.parse(description, commandClassVersions)
	if (cmd) {
		result += zwaveEvent(cmd)
	}
	log.debug "parsed '$description' to ${result.inspect()}"
	result
}

def zwaveEvent(hubitat.zwave.commands.crc16encapv1.Crc16Encap cmd)
{
	def versions = commandClassVersions
	// def encapsulatedCommand = cmd.encapsulatedCommand(versions)
	def version = versions[cmd.commandClass as Integer]
	def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
	def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
	if (!encapsulatedCommand) {
		log.debug "Could not extract command from $cmd"
	} else {
		return zwaveEvent(encapsulatedCommand)
	}
}

def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
	if (cmd.commandClass == 0x6C && cmd.parameter.size >= 4) { // Supervision encapsulated Message
		// Supervision header is 4 bytes long, two bytes dropped here are the latter two bytes of the supervision header
		cmd.parameter = cmd.parameter.drop(2)
		// Updated Command Class/Command now with the remaining bytes
		cmd.commandClass = cmd.parameter[0]
		cmd.command = cmd.parameter[1]
		cmd.parameter = cmd.parameter.drop(2)
	}
	def encapsulatedCommand = cmd.encapsulatedCommand([0x30: 2, 0x31: 2]) // can specify command class versions here like in zwave.parse
	log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}")
	if (encapsulatedCommand) {
		return zwaveEvent(encapsulatedCommand)
	}
}

def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd)
{
	def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
	def cmds = []
	if (!state.lastbat || now() - state.lastbat > 24*60*60*1000) {
		cmds << zwave.batteryV1.batteryGet().format()
	} else {
		cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
	}
	[event, response(cmds)]
}


def zwaveEvent(hubitat.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd)
{
	def map = [:]
	switch (cmd.sensorType) {
		case 1:
			// temperature
			def cmdScale = cmd.scale == 1 ? "F" : "C"
			map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
			map.unit = getTemperatureScale()
			map.name = "temperature"
			break;
	}
	createEvent(map)
}


def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
	def map = [ name: "battery", 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.lastbat = now()
	[createEvent(map), response(zwave.wakeUpV1.wakeUpNoMoreInformation())]
}

def zwaveEvent(hubitat.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) {
	def map = [:]
	map.value = cmd.sensorValue ? "open" : "closed"
	map.name = "contact"
	if (map.value == "closed") {
		map.descriptionText = "$device.displayName is closed"
	}
	else {
		map.descriptionText = "$device.displayName is open"
	}
	createEvent(map)
}

// added so UK (non-multichannel) and US device supported by same device file.
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")
	}
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd)
{
	sensorValueEvent(cmd.value)
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd)
{
	sensorValueEvent(cmd.value)
}

def zwaveEvent(hubitat.zwave.commands.sensoralarmv1.SensorAlarmReport cmd)
{
	def map = [:]
	map.value = cmd.sensorState ? "tampered" : "secure"
	map.name = "tamper"
	if (map.value == "tampered") {
		map.descriptionText = "$device.displayName has been tampered with"
	}
	else {
		map.descriptionText = "$device.displayName is secure"
	}
	createEvent(map)
}

def zwaveEvent(hubitat.zwave.Command cmd) {
	log.debug "Catchall reached for cmd: ${cmd.toString()}}"
	[]
}

def zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport cmd) {
	def result = []
	log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'"

	if (cmd.parameterNumber == 15) {
		if (cmd.configurationValue[0] == 1) { //error in temp probe
			result << createEvent(name:"temperature", value:"-99")
		} else if (cmd.configurationValue[0] == 255) { //no temp probe
			result << createEvent(name:"temperature", value:"")
		}
		result += response(zwave.batteryV1.batteryGet().format())  // send this after configure() runs
	}
	result
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
	def result = []

	def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
	log.debug "msr: $msr"
	device.updateDataValue(["MSR", msr])

	result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
	result
}

 /**
 * Configures the device to settings needed by SmarthThings at device discovery time.
 *
 * @param none
 *
 * @return none
 */
def configure() {
	log.debug "Configuring Device..."
	// Device wakes up every 4 hours, this interval allows us to miss one wakeup notification before marking offline
	sendEvent(name: "checkInterval", value: 8 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])

	def cmds = []
	cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 1, size: 2).format()
	// send associate to group 3 to get sensor data reported only to hub
	cmds << zwave.associationV2.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format()
	
	// send associate to group 2 to get tamper alarm data reported
	cmds << zwave.associationV2.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]).format()

	// turn on the tamper alarm
	cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 10, size: 1).format()
	//cmds << zwave.configurationV1.configurationGet(parameterNumber: 10).format()
	
	// temperature change sensitivity
	cmds << zwave.configurationV1.configurationSet(configurationValue: [4], parameterNumber: 12, size: 1).format()
	//cmds << zwave.configurationV1.configurationGet(parameterNumber: 12).format()
	
	// remove group 1 association to stop redundant BasicSet
	cmds << zwave.associationV1.associationRemove(groupingIdentifier:1, nodeId:zwaveHubNodeId).format()
	
	// see if there is a temp probe on board and is it working
	cmds << zwave.configurationV1.configurationGet(parameterNumber: 15).format()

	delayBetween(cmds, 500)
}

//used to add "test" button for simulation of user changes to parameters
def test() {
	def params = [paramNumber:10,value:1,size:1]
	updateZwaveParam(params)
	//zwave.wakeUpV1.wakeUpIntervalSet(seconds: 30, nodeid:zwaveHubNodeId).format()
}

 /**
 * This method will allow the user to update device parameters (behavior) from an app.
 * A "Zwave Tweaker" app will be developed as an interface to do this. Or the user can
 * write his/her own app to envoke this method. No type or value checking is done to
 * compare to what device capability or reaction. It is up to user to read OEM
 * documentation prio to envoking this method.
 *
 * <p>THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION!
 *
 * @param List[paramNumber:80,value:10,size:1]
 *
 *
 * @return none
 */
def updateZwaveParam(params) {
	if ( params ) {
        def pNumber = params.paramNumber
        def pSize	= params.size
        def pValue	= [params.value]
        log.debug "Make sure device is awake and in recieve mode"
        log.debug "Updating ${device.displayName} parameter number '${pNumber}' with value '${pValue}' with size of '${pSize}'"

		def cmds = []
        cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format()
        cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format()
        delayBetween(cmds, 1000)        
    }
}

 /**
 * Sets all of available Fibaro parameters back to the device defaults except for what
 * SmartThings needs to support the stock functionality as released. This will be
 * called from the "Fibaro Tweaker" or user's app.
 *
 * <p>THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION!
 *
 * @param none
 *
 * @return none
 */
def resetParams2StDefaults() {
	log.debug "Resetting ${device.displayName} parameters to SmartThings compatible defaults"
	def cmds = []
	cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 1, size: 2).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 2, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [255], parameterNumber: 5, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [255], parameterNumber: 7, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 9, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 10, size: 1).format() //ST Custom
    cmds << zwave.configurationV1.configurationSet(configurationValue: [4], parameterNumber: 12, size: 1).format() //St Custom
    cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 13, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 14, size: 1).format()
    
    delayBetween(cmds, 500)
}

 /**
 * Lists all of available Fibaro parameters and thier current settings out to the 
 * logging window in the IDE. This will be called from the "Fibaro Tweaker" or 
 * user's own app.
 *
 * <p>THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION!
 *
 * @param none
 *
 * @return none
 */
def listCurrentParams() {
	log.debug "Listing of current parameter settings of ${device.displayName}"
    def cmds = []
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 1).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 2).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 3).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 5).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 7).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 9).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 10).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 12).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 13).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 14).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 15).format()
    
	delayBetween(cmds, 500)
}

This really sucks as I had a pretty slick solar charged setup (5V panel charging 3 x AA wired to three door/window sensors) for these to run my pool solar heating system.

If anyone has a the older version working, I'd love to see them post a driver here. I've tried a few and none of them works. The older sensors are also a real pain to reset etc but the specific procedure is here: https://manuals.fibaro.com/content/manuals/us/FGK-10x/FGK-10x-USA-A-v1.01.pdf in the older manual section.

I'm a few hours in and like many aleady may just give up.

If you are prepared to buy a new device, the Fibaro Implant might be your answer?

Yes...just trying to to preserve the wireless nature of the setup due to logistics. It's looking like the implant though..

I guess i have the older version too in my pool temp monitor. I use it with 2 standard AA batteries now :wink:. Works fine in Smartthings and no temp report in hubitat with the same driver.
Could it be a config command that's not sent on hubitat?
I can't imagine why it wouldn't work except a configuration that disables or doesn't enable temperature reports.

No idea...I've played with them for a few hours. One of the issues with that firmware is just issuing a config command from a buggy driver seems to mess them up. They don't reset consistently and also have issues excluding/including. I give up.

Ordered an implant. I'll use my one working FGK-10x (3.2 firmware) for the pool temp, the rest of the sensors live in the pool shed anyway...and there is power there for the Fibaro Implant.

I wasted several weeks trying to make the periodic temperature reporting work on the Fibaro smart implant. It had to do with the association groups/multi-channel association groups settings.

Not having a FGK-10x to play with, I cannot offer much, The only thing I notice is you removed the hub from the group 1 association:

I just copied that driver from another thread with minor edits. Can you explain what that means?

Clicking “configure” seems to break the sensor.

If the configure doesn't work then the device is probably not set properly for association group 3 which is required for temperature. Also, the statement "sendEvent(name: "checkInterval"...." in configure() probably doesn't do anything in Hubitat. You could try something like this:

def configure() {
	log.debug "Configuring Device..."

	def cmds = []
	resetParams2StDefaults()
	
	cmds << zwave.wakeUpV2.wakeUpIntervalSet(seconds: 1*60, nodeid:zwaveHubNodeId).format()

	// Set associations
	cmds << zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format()
	cmds << zwave.associationV2.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]).format()
	cmds << zwave.associationV2.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format()

	cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: 2, destinationEndPoint: 2, commandClass:0x31, command:4).format()  //sensorMultiLevel.get()

	delayBetween(cmds, 500)
}

This should wake-up the device every minute. Temperature measurement should be sent according to parameter 12 description (periodic if param 12 is 0 or based on temp change amount otherwise). Parameter 12 is set to 4 (0.25 degC) in resetParams2StDefaults().

In the same function resetParams2StDefaults() there is a parameter 10 and a parameter 15 that are set. I didn't find them in the documentation so I would suggest you comment them out in the code unless you know what they are.

I used a multiChannel command to manually get the temperature in the code above because that's what I saw in the driver from Jean-Jacques GUILLEMAUD...

Let me know what shows up in the log.

I'm a zwave code neophyte, but I'll fire up a sensor and see what I can accomplish. I appreciate the help :slight_smile:

This is what the log fires out when the newly reset FGK-10x (2.5 firmware) is added after a factory reset and clicking configure. You can see it is providing open/close information but that's it. The good news is that clicking configure does not toast the sensor this time though.

The device shows open and close instantly and works as door sensor in the dashboard so someone just needing that function is good to go with this driver. Just need to sort temp!

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:40:00.338 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: FF , isMulticast: false' to [['name':'contact', 'value':'open', 'descriptionText':fgk-01x is open]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:59.736 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: 00 , isMulticast: false' to [['name':'contact', 'value':'closed', 'descriptionText':fgk-01x is closed]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:59.102 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: FF , isMulticast: false' to [['name':'contact', 'value':'open', 'descriptionText':fgk-01x is open]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:58.446 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: 00 , isMulticast: false' to [['name':'contact', 'value':'closed', 'descriptionText':fgk-01x is closed]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:57.887 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: FF , isMulticast: false' to [['name':'contact', 'value':'open', 'descriptionText':fgk-01x is open]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:57.428 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: 00 , isMulticast: false' to [['name':'contact', 'value':'closed', 'descriptionText':fgk-01x is closed]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:56.818 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: FF , isMulticast: false' to [['name':'contact', 'value':'open', 'descriptionText':fgk-01x is open]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:56.333 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: 00 , isMulticast: false' to [['name':'contact', 'value':'closed', 'descriptionText':fgk-01x is closed]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:36.752 pm [debug](http://192.168.0.50/device/edit/229)Resetting fgk-01x parameters to SmartThings compatible defaults

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:36.751 pm [debug](http://192.168.0.50/device/edit/229)Configuring Device...

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:22.225 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: FF , isMulticast: false' to [['name':'contact', 'value':'open', 'descriptionText':fgk-01x is open]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:21.341 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: 00 , isMulticast: false' to [['name':'contact', 'value':'closed', 'descriptionText':fgk-01x is closed]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:02.084 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: FF , isMulticast: false' to [['name':'contact', 'value':'open', 'descriptionText':fgk-01x is open]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:39:01.452 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: 00 , isMulticast: false' to [['name':'contact', 'value':'closed', 'descriptionText':fgk-01x is closed]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:38:31.516 pm [debug](http://192.168.0.50/device/edit/229)Resetting fgk-01x parameters to SmartThings compatible defaults

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:38:31.514 pm [debug](http://192.168.0.50/device/edit/229)Configuring Device...

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:38:05.709 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: FF , isMulticast: false' to [['name':'contact', 'value':'open', 'descriptionText':fgk-01x is open]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:38:05.189 pm [debug](http://192.168.0.50/device/edit/229)parsed 'zw device: 14, command: 2001, payload: 00 , isMulticast: false' to [['name':'contact', 'value':'closed', 'descriptionText':fgk-01x is closed]]

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:35:59.886 pm [info](http://192.168.0.50/device/edit/229)fingerprint mfr:"010F", prod:"0700", deviceId:"2000", inClusters:"0x30,0x9C,0x60,0x85,0x72,0x70,0x86,0x80,0x56,0x84,0x7A", outClusters:"0x2B"

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:35:59.884 pm [debug](http://192.168.0.50/device/edit/229)building fingerprint for unknown Z-Wave device...

[dev:229](http://192.168.0.50/logs?device=229#pastdev229)2022-09-19 06:35:57.865 pm [debug](http://192.168.0.50/device/edit/229)configure() called...

and here is the mod'd code as per your suggestions:


/**
 *  Device Type Definition File
 *
 *  Device Type:		Fibaro Door/Window Sensor
 *  File Name:			fibaro-door-window-sensor.groovy
 *	Initial Release:	2014-12-10
 *	@author:			Todd Wackford
 *  Email:				todd@wackford.net
 *  @version:			1.0
 *
 *  Copyright 2014 SmartThings
 *
 *  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.
 *
 */
  
 /**
 * Sets up metadata, simulator info and tile definition. The tamper tile is setup, but 
 * not displayed to the user. We do this so we can receive events and display on device 
 * activity. If the user wants to display the tamper tile, adjust the tile display lines
 * with the following:
 *		main(["contact", "temperature", "tamper"])
 *		details(["contact", "temperature", "battery", "tamper"])
 *
 * @param none
 *
 * @return none
 */
 metadata {
	definition (name: "Fibaro Door/Window Sensor", namespace: "smartthings", author: "SmartThings", runLocally: true, minHubCoreVersion: '000.021.00001', executeCommandsLocally: true) {
		capability 	"Temperature Measurement"     
		capability 	"Contact Sensor"
		capability 	"Sensor"
		capability 	"Battery"
		capability 	"Configuration"
		capability  "Health Check"
        
        command		"resetParams2StDefaults"
        command		"listCurrentParams"
        command		"updateZwaveParam"
        command		"test"

		fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x85,0x72,0x70,0x86,0x80,0x56,0x84,0x7A,0xEF,0x2B", deviceJoinName: "Fibaro Open/Closed Sensor"
	}

	simulator {
		// messages the device returns in response to commands it receives
		status "open"  :  	"command: 2001, payload: FF"
		status "closed":	"command: 2001, payload: 00"

		for (int i = 0; i <= 100; i += 20) {
			status "temperature ${i}F": new hubitat.zwave.Zwave().sensorMultilevelV2.sensorMultilevelReport(
				scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1).incomingMessage()
		}

		for (int i = 0; i <= 100; i += 20) {
			status "battery ${i}%": new hubitat.zwave.Zwave().batteryV1.batteryReport(
				batteryLevel: i).incomingMessage()
		}
	}

	tiles {
		standardTile("contact", "device.contact", width: 2, height: 2) {
			state "open",   label: '${name}', icon: "st.contact.contact.open",   backgroundColor: "#e86d13"
			state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
		}
		valueTile("temperature", "device.temperature", inactiveLabel: false) {
			state "temperature", label:'${currentValue}°',
			backgroundColors:[
				[value: "", color: "#ffffff"],
                [value: 31, color: "#153591"],
				[value: 44, color: "#1e9cbb"],
				[value: 59, color: "#90d2a7"],
				[value: 74, color: "#44b621"],
				[value: 84, color: "#f1d801"],
				[value: 95, color: "#d04e00"],
				[value: 96, color: "#bc2323"]
			]
		}
        standardTile("tamper", "device.alarm") {
			state("secure", label:'secure',    icon:"st.locks.lock.locked",   backgroundColor:"#ffffff")
			state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#00a0dc")
		}
		valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
			state "battery", label:'${currentValue}% battery', unit:""
		}
		standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
			state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
		}

		//this will display a temperature tile for the DS18B20 sensor
		main(["contact", "temperature"])    //COMMENT ME OUT IF NO TEMP INSTALLED
		details(["contact", "temperature", "battery"])	//COMMENT ME OUT IF NO TEMP INSTALLED
        
        //this will hide the temperature tile if the DS18B20 sensor is not installed
		//main(["contact"])										//UNCOMMENT ME IF NO TEMP INSTALLED
		//details(["contact", "battery"])						//UNCOMMENT ME IF NO TEMP INSTALLED
	}
}

/**
 * Mapping of command classes and associated versions used for this DTH
 */
private getCommandClassVersions() {
	[
		0x30: 1,  // Sensor Binary
		0x31: 2,  // Sensor MultiLevel
		0x56: 1,  // Crc16Encap
		0x60: 3,  // Multi-Channel
		0x70: 2,  // Configuration
		0x72: 2,  // Manufacturer Specific
		0x80: 1,  // Battery
		0x84: 1,  // WakeUp
		0x9C: 1   // Sensor Alarm
	]
}

// Parse incoming device messages to generate events 
def parse(String description)
{
	def result = []
	def cmd = zwave.parse(description, commandClassVersions)
	if (cmd) {
		result += zwaveEvent(cmd)
	}
	log.debug "parsed '$description' to ${result.inspect()}"
	result
}

def zwaveEvent(hubitat.zwave.commands.crc16encapv1.Crc16Encap cmd)
{
	def versions = commandClassVersions
	// def encapsulatedCommand = cmd.encapsulatedCommand(versions)
	def version = versions[cmd.commandClass as Integer]
	def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
	def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
	if (!encapsulatedCommand) {
		log.debug "Could not extract command from $cmd"
	} else {
		return zwaveEvent(encapsulatedCommand)
	}
}

def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
	if (cmd.commandClass == 0x6C && cmd.parameter.size >= 4) { // Supervision encapsulated Message
		// Supervision header is 4 bytes long, two bytes dropped here are the latter two bytes of the supervision header
		cmd.parameter = cmd.parameter.drop(2)
		// Updated Command Class/Command now with the remaining bytes
		cmd.commandClass = cmd.parameter[0]
		cmd.command = cmd.parameter[1]
		cmd.parameter = cmd.parameter.drop(2)
	}
	def encapsulatedCommand = cmd.encapsulatedCommand([0x30: 2, 0x31: 2]) // can specify command class versions here like in zwave.parse
	log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}")
	if (encapsulatedCommand) {
		return zwaveEvent(encapsulatedCommand)
	}
}

def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd)
{
	def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
	def cmds = []
	if (!state.lastbat || now() - state.lastbat > 24*60*60*1000) {
		cmds << zwave.batteryV1.batteryGet().format()
	} else {
		cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
	}
	[event, response(cmds)]
}


def zwaveEvent(hubitat.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd)
{
	def map = [:]
	switch (cmd.sensorType) {
		case 1:
			// temperature
			def cmdScale = cmd.scale == 1 ? "F" : "C"
			map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
			map.unit = getTemperatureScale()
			map.name = "temperature"
			break;
	}
	createEvent(map)
}


def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
	def map = [ name: "battery", 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.lastbat = now()
	[createEvent(map), response(zwave.wakeUpV1.wakeUpNoMoreInformation())]
}

def zwaveEvent(hubitat.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) {
	def map = [:]
	map.value = cmd.sensorValue ? "open" : "closed"
	map.name = "contact"
	if (map.value == "closed") {
		map.descriptionText = "$device.displayName is closed"
	}
	else {
		map.descriptionText = "$device.displayName is open"
	}
	createEvent(map)
}

// added so UK (non-multichannel) and US device supported by same device file.
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")
	}
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd)
{
	sensorValueEvent(cmd.value)
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd)
{
	sensorValueEvent(cmd.value)
}

def zwaveEvent(hubitat.zwave.commands.sensoralarmv1.SensorAlarmReport cmd)
{
	def map = [:]
	map.value = cmd.sensorState ? "tampered" : "secure"
	map.name = "tamper"
	if (map.value == "tampered") {
		map.descriptionText = "$device.displayName has been tampered with"
	}
	else {
		map.descriptionText = "$device.displayName is secure"
	}
	createEvent(map)
}

def zwaveEvent(hubitat.zwave.Command cmd) {
	log.debug "Catchall reached for cmd: ${cmd.toString()}}"
	[]
}

def zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport cmd) {
	def result = []
	log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'"

	if (cmd.parameterNumber == 15) {
		if (cmd.configurationValue[0] == 1) { //error in temp probe
			result << createEvent(name:"temperature", value:"-99")
		} else if (cmd.configurationValue[0] == 255) { //no temp probe
			result << createEvent(name:"temperature", value:"")
		}
		result += response(zwave.batteryV1.batteryGet().format())  // send this after configure() runs
	}
	result
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
	def result = []

	def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
	log.debug "msr: $msr"
	device.updateDataValue(["MSR", msr])

	result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
	result
}

 /**
 * Configures the device to settings needed by SmarthThings at device discovery time.
 *
 * @param none
 *
 * @return none
 */
def configure() {
	log.debug "Configuring Device..."

	def cmds = []
	resetParams2StDefaults()
	
	cmds << zwave.wakeUpV2.wakeUpIntervalSet(seconds: 1*60, nodeid:zwaveHubNodeId).format()

	// Set associations
	cmds << zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format()
	cmds << zwave.associationV2.associationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]).format()
	cmds << zwave.associationV2.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format()

	cmds << zwave.multiChannelV3.multiChannelCmdEncap(sourceEndPoint: 2, destinationEndPoint: 2, commandClass:0x31, command:4).format()  //sensorMultiLevel.get()

	delayBetween(cmds, 500)
}

//used to add "test" button for simulation of user changes to parameters
def test() {
	def params = [paramNumber:10,value:1,size:1]
	updateZwaveParam(params)
	//zwave.wakeUpV1.wakeUpIntervalSet(seconds: 30, nodeid:zwaveHubNodeId).format()
}

 /**
 * This method will allow the user to update device parameters (behavior) from an app.
 * A "Zwave Tweaker" app will be developed as an interface to do this. Or the user can
 * write his/her own app to envoke this method. No type or value checking is done to
 * compare to what device capability or reaction. It is up to user to read OEM
 * documentation prio to envoking this method.
 *
 * <p>THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION!
 *
 * @param List[paramNumber:80,value:10,size:1]
 *
 *
 * @return none
 */
def updateZwaveParam(params) {
	if ( params ) {
        def pNumber = params.paramNumber
        def pSize	= params.size
        def pValue	= [params.value]
        log.debug "Make sure device is awake and in recieve mode"
        log.debug "Updating ${device.displayName} parameter number '${pNumber}' with value '${pValue}' with size of '${pSize}'"

		def cmds = []
        cmds << zwave.configurationV1.configurationSet(configurationValue: pValue, parameterNumber: pNumber, size: pSize).format()
        cmds << zwave.configurationV1.configurationGet(parameterNumber: pNumber).format()
        delayBetween(cmds, 1000)        
    }
}

 /**
 * Sets all of available Fibaro parameters back to the device defaults except for what
 * SmartThings needs to support the stock functionality as released. This will be
 * called from the "Fibaro Tweaker" or user's app.
 *
 * <p>THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION!
 *
 * @param none
 *
 * @return none
 */
def resetParams2StDefaults() {
	log.debug "Resetting ${device.displayName} parameters to SmartThings compatible defaults"
	def cmds = []
	cmds << zwave.configurationV1.configurationSet(configurationValue: [0,0], parameterNumber: 1, size: 2).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 2, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [255], parameterNumber: 5, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [255], parameterNumber: 7, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 9, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 10, size: 1).format() //ST Custom
    cmds << zwave.configurationV1.configurationSet(configurationValue: [4], parameterNumber: 12, size: 1).format() //St Custom
    cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 13, size: 1).format()
    cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 14, size: 1).format()
    
    delayBetween(cmds, 500)
}

 /**
 * Lists all of available Fibaro parameters and thier current settings out to the 
 * logging window in the IDE. This will be called from the "Fibaro Tweaker" or 
 * user's own app.
 *
 * <p>THIS IS AN ADVANCED OPERATION. USE AT YOUR OWN RISK! READ OEM DOCUMENTATION!
 *
 * @param none
 *
 * @return none
 */
def listCurrentParams() {
	log.debug "Listing of current parameter settings of ${device.displayName}"
    def cmds = []
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 1).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 2).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 3).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 5).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 7).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 9).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 10).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 12).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 13).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 14).format()
    cmds << zwave.configurationV1.configurationGet(parameterNumber: 15).format()
    
	delayBetween(cmds, 500)
}

and this is the driver in the device GUI:

Did you try to change the temperature of the sensor to trigger a report? You can also try to set parameter 12 to 0 so that reports are sent every minute:

    cmds << zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 12, size: 1).format() //St Custom

1 Like

I put the probes in hot water...no report, nothing in the logs. The device has not reported anything since I fired it back up with this drive. Open and close work though :slight_smile: Bringing a magnet close to the unit immediately triggers the open/close log entries.

I see this in the logs after pressing "Configure" button in the driver...it looks like it's running the default value reset as well?

dev:2292022-09-19 09:49:15.280 pm debugResetting fgk-01x parameters to SmartThings compatible defaults

dev:2292022-09-19 09:49:15.278 pm debugConfiguring Device...

Below is the untouched ST code. This was the driver the sensors were using on ST for the last 3-4 years with no issues right up to exclusion a few days ago. There is code in there which I mod'd to calibrate my sensors. Maybe there are some clues there on the nature of these sensors?

*  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
 ******************************************************************************************************************************/

/******************************************************************************************************************************
 *	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: "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"]
        
        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}°\n', 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, 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}°\n', 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:'batt. @ ${currentValue}%' /*battery*/, unit:""
		} 
        
        //Select temperatureF if Location temperature Scale is °F
        main(["temperature"])
		details(["temperature", "contact", "battery"])
        //main(["temperatureF"])
		//details(["temperatureF", "contact", "battery"])
	}
}

////////////////////////////////
// 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)
	}
}

// Devices that support the Security command class can send messages in an
// encrypted form; they arrive wrapped in a SecurityMessageEncapsulation
// command and must be unencapsulated
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
        def encapsulatedCommand = cmd.encapsulatedCommand([0x98: 1, 0x20: 1])
        // can specify command class versions here like in zwave.parse
        if (encapsulatedCommand) {
    			log.debug "encapsulatedCommand : ${encapsulatedCommand}"
                return 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 << "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"
        state.isInitialized = true
        if (debugLevel>=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 << zwave.multiChannelV3.multiChannelEndPointGet().format()				// MultiChannelEndPointReport  -> dynamic: false, endPoints: 2
    //cmdBlock << "delay 1200"
    //cmdBlock << zwave.multiChannelV3.multiChannelCapabilityGet(endPoint:1).format()	// MultiChannelCapabilityReport -> commandClass: [48], dynamic: false, endPoint: 1, genericDeviceClass: 32, specificDeviceClass: 1
    //cmdBlock << "delay 1200"
    //cmdBlock << zwave.multiChannelV3.multiChannelCapabilityGet(endPoint:2).format()	// MultiChannelCapabilityReport -> 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 << "delay 2000"
    if (debugLevel>=2) {
        log.debug "wakeUpNoMoreInformation()"
        log.debug "cmdBlock : ${cmdBlock}"
    }
    return cmdBlock
}

def zwaveEvent(physicalgraph.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"
    	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) {
	// 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
    	log.debug "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
        // Adjust measured temperature based on previous manual calibration; FGK-101 is natively °C
        switch (device.name) {
            case 'Z-Wave Door/Window Sensor' :										//JJG	
            	scaledSensorValue = scaledSensorValue + 0.7999
    			log.debug "Temp Adjust for : ${device.name}"
                break;
            case 'Z-Wave Door/Window Sensor 2' :										//MLE
            	scaledSensorValue = scaledSensorValue + 0.6800
    			log.debug "Temp Adjust for : ${device.name}"
                break;
            case 'Z-Wave Door/Window Sensor 3' :										//MPT
            	scaledSensorValue = scaledSensorValue + 0.4055
    			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 * ((((temperatureScaleFC(scaledSensorValue).abs()*100+5)/10).intValue()*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
        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 event = sensorValueEvent(cmd.sensorState)
    if (debugLevel>=2) {log.debug "sensoralarmv1.SensorAlarmReport $cmd.sensorState"}
    def event = createEvent(name:"alarm", 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(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} has a low battery"
			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}"}
}
 
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
    if (debugLevel>=2) {log.debug "versionv1.VersionReport: ${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 Handler
def reportNext(commandMsg) {
	log.debug "reportNext !"
    log.debug "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
    	log.debug "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() {
	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  // 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}
    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(),
        // get zwave version information
        zwave.versionV1.versionGet().format()
	],1200)
}

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}"   					// -> "[endpointId:0, version: 2.1, MSR:010F-0700-2000]"
    //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"
}

The configure() function I gave you calls resetParams2StDefaults(). I was suggesting that you change the value in there to zero for parameter 12. Also comment-out the lines for parameters 10 and 15.

I will take a closer look at the code.

1 Like

I'll try commenting out those lines...

You are a prince amongst men :slight_smile:

1 Like