Homekit Beta - Child Device Support

Just a little nudge for support for child devices in the Homekit beta.

The below device (Fibaro Double Smart FGS-224 with driver by @cjcharles & Generic Component Switch child device driver) exposes the child devices separately and works perfectly with both operating as switches within Homekit. I'm not sure how this works if child devices aren't currently supported but it does:

However this one (the older Fibaro Double Switch 2 FGS-223 with drivers by @ericm) doesn't work with Homekit (the device appears without the child devices):

It's a bit of a pain as we use these lights with Siri, so I've had to leave Homebridge running on a Pi just for a few devices (yeah I know I could shell out a few hundred and replace the modules with the newer one but I'd rather avoid that)

2 Likes

I can confirm your findings and add to that, also the FGR-223 roller shutter community driver is not supported and does not show as available to integrate to HomeKit. Is it the right time to ask (again) if Hubitat guys can create a native driver to support Fibaro products?
Tagging @bcopeland as AFAIK he is the z-wave expert.
I have about 15 FGR-223 and the same amount of FGS-223 and as I am located in Israel we do not really have that many alternatives for Z-wave devices working at 866 MHz.

1 Like

Yes, definitely. When I moved from SmartThings a couple of years ago, I was originally going to get the Fibaro Home Center to run everything. It was a post on Vesternets website comparing all of the different hubs that changed my thinking and pushed me toward Hubitat. The 'double' products (FGS-223 and FGS-224) do not have a built in driver and it would definitely be appreciated. I guess the products such as Fibaro which run on the EU Z Wave frequency get less attention than all of the US products that are inaccessible to us (Zooz, Inovelli, Lutron etc) which is a shame (guessing they're not readily available for Hubitat). We have fewer available Z Wave devices here in the UK (probably the same in EU) and it's exacerbated by a lack of built in support (I guess "Why?" should have been one of my questions for @bobbyD in Hubitat Live)

Strangely @amithalp my Roller Shutter 3 (FGR-223) IS working. While everyone laments the red tape that prevents garage doors being integrated, my garage roller door tubular motor is connected directly to a FGR-223 and it is working. I know there are several community drivers for the FGR-223 - I am using this one by 'waytotheweb':

Summary
/**
 *  Further Hubitat modifications for Fibaro FGR-223 by Jonathan Michaelson:
 *	. Added Info and Debug Logging preferences
 *	. Tidy code formatting
 *	. Updated fingerprint
 *	. Fixed Module Parameter preferences
 *	. Fixed Module Parameter preference persistence
 *	. Fixed Module Parameter preference display
 *	. Fixed Module Parameter preference default values
 *	. Reset Module Parameter preference #150 if set to 2 to avoid repeat calibration
 *	. Fixed setPosition
 *
 *
 *  Based on the FGR-222 handler by Julien Bachmann, philh30
 *
 *  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.
 *
 *
 *  Changelog:
 *
 *  v0.01 - Initial public release
 */

metadata {
	definition (name: "Fibaro FGR-223", namespace: "waytotheweb", author: "Jonathan Michaelson", importUrl: "https://raw.githubusercontent.com/waytotheweb/hubitat/main/drivers/Fibaro_FGR-223.groovy") {
		capability "Sensor"
		capability "Contact Sensor"
		capability "Actuator"

		capability "Switch"
		capability "Switch Level"
		capability "Window Shade"

		capability "Polling"
		capability "Power Meter"
		capability "Energy Meter"
		capability "Refresh"
		capability "Configuration"

		attribute "syncStatus", "enum", ["syncing", "synced"]

		command "sync"
		command "stop"		
		command "up"   
		command "down"   

		fingerprint deviceId: "1000", mfr:"010F", deviceType:"0303", inClusters:"0x5E,0x55,0x98,0x9F,0x56,0x6C,0x22", deviceJoinName: "Fibaro Roller Shutter 3"
	}

	preferences {
		input name: "infoLogging", type: "bool", title: "Enable info message logging", description: ""
		input name: "debugLogging", type: "bool", title: "Enable debug message logging", description: ""
		input name: "invert", type: "bool", title: "Invert up/down", description: "Invert up and down actions"
		input name: "openOffset", type: "decimal", title: "Open offset", description: "The percentage from which shutter is displayed as open"
		input name: "closeOffset", type: "decimal", title: "Close offset", description: "The percentage from which shutter is displayed as closed"
		input name: "offset", type: "decimal", title: "Offset", description: "This offset will correct the value returned by the device so it matches the real value"

		input (
			type: "paragraph",
			element: "paragraph",
			title: "DEVICE PARAMETERS:",
			description: "Device parameters are used to customize the physical device. Refer to the product documentation for a full description of each parameter."
		)

		moduleParams().each {
			if (it.type == "number"){
				input (
					name: it.name,
					type: it.type,
					title: "#${it.id}: ${it.title}:",
					description: it.description + " <br>\n[Default Value: " + it.defaultValue + "]",
					range: it.range,
					required: it.required
				)
			}
			else if (it.type == "enum"){
				input (
					name: it.name,
					type: it.type,
					title: "#${it.id}: ${it.title}:",
					description: it.description + " <br>\n[Default Value: " + it.defaultValue + "]",
					options: it.options,
					required: it.required
				)
			}
		}
	}
}

def parse(String description) {
	if (debugLogging) log.debug("New event to parse: ${description}")
	def result = null
	def cmd = zwave.parse(description, [0x20: 1, 0x26: 3, 0x70: 1, 0x32:3])
	if (cmd) {
		if (debugLogging) log.debug "Parsed event: ${cmd}"
		result = zwaveEvent(cmd)
		if (result) {
			if (debugLogging) log.debug("Dispatch events ${result}")
		}
	} else {
		if (debugLogging) log.debug("Couldn't zwave.parse ${description}")
	}
	return result
}

def correctLevel(value) {
	def result = value
	if (value == "off") {
		result = 0;
	}
	if (value == "on" ) {
		result = 100;
	}
	result = result - (offset ?: 0)
	if (invert) {
		result = 100 - result
	}
	return result
}

def createWindowShadeEvent(value) {
	def theWindowShade = "partially open"
	def contactValue = "open"
	def switchValue = "on"
	if (value >= (openOffset ?: 95)) {
		theWindowShade = "open"
		contactValue = "open"
		switchValue = "on"
	}
	if (value <= (closeOffset ?: 5)) {
		theWindowShade = "closed"
		contactValue = "closed"
		switchValue = "off"
	}
	if (debugLogging) log.debug "Updating windowShade state to ${theWindowShade}"
	sendEvent(name: "windowShade", value: theWindowShade)
	if (debugLogging) log.debug "Updating contactSensor state to ${contactValue}"
	sendEvent(name: "contact", value: contactValue, displayed: false)
	if (debugLogging) log.debug "Updating switch state to ${switchValue}"
	return createEvent(name: "switch", value: switchValue, displayed: false)
}

def createSwitchEvent(value) {
	def switchValue = "on"
	if (value >= (openOffset ?: 95)) {
		switchValue = "on"
	}
	if (value <= (closeOffset ?: 5)) {
		switchValue = "off"
	}
	return createEvent(name: "switch", value: switchValue)
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
	def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x26: 3, 0x70: 1, 0x32:3])
	if (debugLogging) log.debug "Secure message parsed: ${encapsulatedCommand}"
	if (encapsulatedCommand) {
		zwaveEvent(encapsulatedCommand)
	}
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) {
	if (debugLogging) log.debug("Basic report: ${cmd.value}; Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	def result = []
	if (cmd.value != null) {
		def level = correctLevel(cmd.value)
		sendEvent(name: "level", value: level, unit: "%")  
		if (device.currentValue('windowShade') == "opening" || device.currentValue('windowShade') == "closing") {
			result << response([secure(zwave.meterV2.meterGet(scale: 2))])
		}
	}
	return result
}

def zwaveEvent(hubitat.zwave.commands.switchmultilevelv3.SwitchMultilevelReport cmd) {
	if (debugLogging) log.debug("Switch multilevel report: ${cmd.value}; Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	def result = []
	if (cmd.value != null) {
		if(cmd.value > 100) {
			log.error "${device.displayName} requires calibration. Position of ${cmd.value}% reported."
		}
		def level = correctLevel(cmd.value)
		sendEvent(name: "level", value: level, unit: "%")   
		if (device.currentValue('windowShade') == "opening" || device.currentValue('windowShade') == "closing") {
			result << response([secure(zwave.meterV2.meterGet(scale: 2))])
		}
		else {
			result << createWindowShadeEvent(level) 
		}
	}
	return result
}

def zwaveEvent(hubitat.zwave.Command cmd) {
	if (debugLogging) log.debug("other event ${cmd}")
}

def zwaveEvent(hubitat.zwave.commands.meterv3.MeterReport cmd) {
	if (debugLogging) log.debug "Meter report: ${cmd.meterType} ${cmd.scale} ${cmd.scaledMeterValue}; Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}"
	if (cmd.meterType == 1) {
		if (cmd.scale == 2) {
			def result = []
			result << createEvent(name: "power", value: Math.round(cmd.scaledMeterValue), unit: "W")
			if (cmd.scaledMeterValue < 1.0) {
			  result << createWindowShadeEvent(device.currentValue('level'))
			  result << response(["delay 500", secure(zwave.switchMultilevelV3.switchMultilevelGet())])
			}
			else {
			  result << response(["delay 2000", secure(zwave.switchMultilevelV3.switchMultilevelGet())])
			}
			if (debugLogging) log.debug("power result ${result}")
			return result
		} else {
			return createEvent(name: "electric", value: cmd.scaledMeterValue, unit: ["pulses", "V", "A", "R/Z", ""][cmd.scale - 3])
		}
	}
}

def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
	if (debugLogging) log.debug("zwaveEvent(): Configuration Report received: ${cmd}")
	if (cmd.parameterNumber == 150) {
		try {
			device.updateSetting("configParam150", cmd.scaledConfigurationValue)
			if (cmd.scaledConfigurationValue == 0) if (debugLogging) log.debug "Calibration report received - device not calibrated. Updating parameter ${cmd.parameterNumber} from ${configParam150} to ${cmd.scaledConfigurationValue}."
			if (cmd.scaledConfigurationValue == 1) if (debugLogging) log.debug "Calibration report received - device is calibrated. Updating parameter ${cmd.parameterNumber} from ${configParam150} to ${cmd.scaledConfigurationValue}."
			if (cmd.scaledConfigurationValue == 2) if (debugLogging) log.debug "Calibration report received - device is calibrating. Updating parameter ${cmd.parameterNumber} from ${configParam150} to ${cmd.scaledConfigurationValue}."
		} catch (e) {
			if (cmd.scaledConfigurationValue == 0) if (debugLogging) log.debug "Calibration report received - device not calibrated."
			if (cmd.scaledConfigurationValue == 1) if (debugLogging) log.debug "Calibration report received - device is calibrated."
			if (cmd.scaledConfigurationValue == 2) if (debugLogging) log.debug "Calibration report received - device is calibrating."
		}
	}
}

def updated() {
	sync();
}

def on() {
	open()
}

def off() {
	close()
}

def stop() {
	if (infoLogging) log.info "Stop - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}"
	secureSequence([
		zwave.switchMultilevelV3.switchMultilevelStopLevelChange(),
		zwave.switchMultilevelV3.switchMultilevelGet()
	], 2000)
}

def up() {
	if (infoLogging) log.info("Up - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	def currentWindowShade = device.currentValue('windowShade')
	if (currentWindowShade == "opening" || currentWindowShade == "closing") {	  
		return stop()		
	}
	return open()
}

def down() {
	if (infoLogging) log.info("Down - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	def currentWindowShade = device.currentValue('windowShade')
	if (currentWindowShade == "opening" || currentWindowShade == "closing") {
		return stop()		
	}
	return close()
}

def open() {
	if (infoLogging) log.info("Open - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	sendEvent(name: "windowShade", value: "opening")
	if (invert) {
		return privateClose()
	}
	else {
		return privateOpen()
	}
}

def close() {
	if (infoLogging) log.info("Close - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	sendEvent(name: "windowShade", value: "closing")	
	if (invert) {
		return privateOpen()
	}
	else {
		return privateClose()
	}
}

def privateOpen() {
	secureSequence([
		zwave.switchMultilevelV3.switchMultilevelSet(value: 99, dimmingDuration: 0x00),
		zwave.switchMultilevelV3.switchMultilevelGet()
	], 2000)
}

def privateClose() {
	secureSequence([
		zwave.switchMultilevelV3.switchMultilevelSet(value: 0, dimmingDuration: 0x00),
		zwave.switchMultilevelV3.switchMultilevelGet()
	], 2000)
}

def presetPosition() {
	setLevel(50)
}

def poll() {
	secureSequence([
		zwave.meterV2.meterGet(scale: 0),
		zwave.meterV2.meterGet(scale: 2)
	], 1000)
}

def refresh() {
	if (infoLogging) log.debug("Refresh - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	secureSequence([
		zwave.switchMultilevelV3.switchMultilevelGet(),
		zwave.meterV2.meterGet(scale: 2)
	], 500)
}

def setPosition(level) {
	setLevel(level)
}

def setLevel(level) {
	if (invert) {
		level = 100 - level
	}
	if(level > 99) level = 99
	if (level <= (openOffset ?: 95) && level >= (closeOffset ?: 5)) {
		level = level - (offset ?: 0)
	}

	if (infoLogging) log.info("Set level ${level} s - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	secureSequence([
		zwave.switchMultilevelV3.switchMultilevelSet(value: level, dimmingDuration: 0x00),
		zwave.switchMultilevelV3.switchMultilevelGet()
	], 10000)
}

def configure() {
	if (infoLogging) log.info("Configure roller shutter - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	secureSequence([
		zwave.configurationV1.configurationSet(parameterNumber: 150, size: 1, scaledConfigurationValue: 2),  // start calibration
		zwave.switchMultilevelV3.switchMultilevelGet(),
		zwave.meterV2.meterGet(scale: 0),
		zwave.meterV2.meterGet(scale: 2),
	], 500)
}

def sync() {
	if (infoLogging) log.info("Sync roller shutter - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	def cmds = []
	sendEvent(name: "syncStatus", value: "syncing", isStateChange: true)
	moduleParams().each {
		if (settings."configParam${it.id}" != null) {
			cmds << secure(zwave.configurationV1.configurationSet(parameterNumber: it.id, size: it.size, scaledConfigurationValue: settings."configParam${it.id}".toInteger()))
			cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: it.id))
		}
		if (it.id == 150 && configParam150 == 2) device.updateSetting("configParam150", 1)
	}
	if (cmds) {
		if (debugLogging) log.debug("Send configuration parameters ${cmds}")
		runIn(cmds.size(), setSynced)
		delayBetween(cmds, 500)
	} else {
		if (debugLogging) log.debug "No configuration parameters set"
	}
}

def setSynced() {
	if (debugLogging) log.debug("Synced - Shade state: ${device.currentValue('windowShade')}; Shade level: ${device.currentValue('level')}")
	sendEvent(name: "syncStatus", value: "synced", isStateChange: true)
}

private secure(hubitat.zwave.Command cmd) {
	if (getDataValue("zwaveSecurePairingComplete") == "true" && getDataValue("S2") == null) {
		return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
	} else {
		return cmd.format()
	}
}

private secureSequence(Collection commands, ...delayBetweenArgs) {
	delayBetween(commands.collect{ secure(it) }, *delayBetweenArgs)
}

private moduleParams() {
	return [
	[id:  20, size: 1, type: "number", range: "0..2", defaultValue: 0, required: false, readonly: false,
		name: "configParam20",
		title: "Switch type",
		description: "This parameter defines how the device should treat the switch connected to the S1 and S2 terminals.<br>\n" +
		"If parameter 20 is set to 1 (toggle switch), change value of parameter 153 to 0 for slats to work properly.<br>\n" +
		"0 - momentary switches.<br>\n" +
		"1 - toggle switches.<br>\n" +
		"2 - single, momentary switch (the switch should be connected to S1 terminal)"],
	[id:  24, size: 1, type: "number", range: "0..1", defaultValue: 0, required: false, readonly: false,
		name: "configParam24",
		title: "Inputs orientation",
		description: "This parameter allows reversing the operation of switches connected to S1 and S2 without changing the wiring.<br>\n" +
		"0 - default (S1 - 1st channel, S2 - 2nd channel)<br>\n" +
		"1 - reversed (S1 - 2nd channel, S2 - 1st channel)<br>\n"],
	[id: 25, size:1, type: "number", range: "0..1", defaultValue: 0, required: false, readonly: false,
		name: "configParam25",
		title: "Outputs orientation",
		description: "This parameter allows reversing the operation of Q1 and Q2 without changing the wiring (in case of invalid motor connection) to ensure proper operation.<br>\n" +
		"0 - default (Q1 - 1st channel, Q2 - 2nd channel)<br>\n" +
		"1 - reversed (Q1 - 2nd channel, Q2 - 1st channel)<br>\n."],
	[id: 60, size:1, type: "number", range: "0..1", defaultValue: 0, required: false, readonly: false,
		name: "configParam60",
		title: "Measuring power consumed by the device itself",
		description: "This parameter determines whether the power metering should include the amount of active power consumed by the device itself.<br>\n" +
		"0 - function inactive<br>\n" +
		"1 - function active"],
	[id: 61, size:2, type: "number", range: "0..500", defaultValue: 15, required: false, readonly: false,
		name: "configParam61",
		title: "Power reports - on change",
		description: "This parameter determines the minimum change in consumed power that will result in sending new power report to the main controller. For loads under 50W, the parameter is not relevant and reports are sent every 5W change. Power report are sent no often then every 30 seconds.<br>\n" +
		"0 - reports are disabled<br>\n" +
		"1-500 (1-500%) - change in power"],
	[id: 62, size:2, type: "number", range: "0..32400", defaultValue: 3600, required: false, readonly: false,
		name: "configParam62",
		title: "Power reports - periodic",
		description: "This parameter determines in what time intervals the periodic power reports are sent to the main controller. Periodic reports do not depend on power change (parameter 61).<br>\n" +
		"0 - periodic reports are disabled<br>\n" +
		"30-32400 - (30-32400s) - report interval"],
	[id: 65, size:2, type: "number", range: "0..500", defaultValue: 10, required: false, readonly: false,
		name: "configParam65",
		title: "Energy reports - on change",
		description: "This parameter determines the minimum change in consumed energy that will result in sending new energy report to the main controller.<br>\n" +
		"0 - reports are disabled<br>\n" +
		"1-500 (0.01 - 5 kWh) - change in energy"],
	[id: 66, size:2, type: "number", range: "0..32400", defaultValue: 3600, required: false, readonly: false,
		name: "configParam66",
		title: "Energy reports - periodic",
		description: "This parameter determines in what time intervals the periodic energy reports are sent to the main controller. Periodic reports do not depend on energy change (parameter 65).<br>\n" +
		"0 - periodic reports are disabled.<br>\n" +
		"30-32400 (30-32400s) - report interval"],
	[id: 150, size:1, type: "number", range: "0..2", defaultValue: 0, required: false, readonly: false,
		name: "configParam150",
		title: "Force calibration",
		description: "By setting this parameter to 2 the device enters the calibration mode. The parameter relevant only if the device is set to work in positioning mode (parameter 151 set to 1, 2 or 4).<br>\n" +
		"0 - device is not calibrated<br>\n" +
		"1 - device is calibrated<br>\n" +
		"2 - force device calibration"],
	[id: 151, size:1, type: "number", range: "1..6", defaultValue: 1, required: false, readonly: false,
		name: "configParam151",
		title: "Operating mode",
		description: "This parameter allows adjusting operation according to the connected device<br>\n" +
		"1 - roller blind (with positioning) <br>\n" +
		"2 - Venetian blind (with positioning)<br>\n" +
		"3 - gate (without positioning)<br>\n" +
		"4 - gate (with positioning)<br>\n" +
		"5 - roller blind with built-in driver<br>\n" +
		"6 - roller blind with built-in driver (impulse)"],
	[id: 152, size:4, type: "number", range: "0..65535", defaultValue: 150, required: false, readonly: false,
		name: "configParam152",
		title: "Venetian blind - time of full turn of the slats ",
		description: "For Venetian blinds (parameter 151 set to 2) the parameter determines time of full turn cycle of the slats. For gates (parameter 151 set to 3 or 4) the parameter determines time after which open gate will start closing automatically (if set to 0, gate will not close). The parameter is irrelevant for other modes.<br>\n" +
		"0-65535 (0 - 655.35, every 0.01s) - time of turn"],
	[id: 153, size:1, type: "number", range: "0..2", defaultValue: 1, required: false, readonly: false,
		name: "configParam153",
		title: "Set slats back to previous position",
		description: "For Venetian blinds (parameter 151 set to 2) the parameter determines slats positioning in various situations.<br>\n" +
		"The parameter is irrelevant for other modes.<br>\n" +
		"If parameter 20 is set to 1 (toggle switch), change value of parameter 153 to 0 for slats to work properly.<br>\n" +
		"0 - slats return to previously set position only in case of the main controller operation.<br>\n" +
		"1 - slats return to previously set position in case of the main controller operation, momentary switch operation, or when the limit switch is reached.<br>\n" +
		"2 - slats return to previously set position in case of the main controller operation, momentary switch operation, when the limit switch is reached or after receiving the Switch Multilevel Stop control frame"],
	[id: 154, size:2, type: "number", range: "0..255", defaultValue: 10, required: false, readonly: false,
		name: "configParam154",
		title: "Delay motor stop after reaching end switch",
		description: "For blinds (parameter 151 set to 1, 2, 5 or 6) the parameter determines the time after which the motor will be stopped after end switch contacts are closed. For gates (parameter 151 set to 3 or 4) the parameter determines time after which the gate will start closing automatically if S2 contacts are  opened (if set to 0, gate will not close).<br>\n" +
		"0-255 (0 - 25.5s) - time"],
	[id: 155, size:2, type: "number", range: "0..255", defaultValue: 10, required: false, readonly: false,
		name: "configParam155",
		title: "Motor operation detection",
		description: "Power threshold to be interpreted as reaching a limit switch.<br>\n" +
		"0 - reaching a limit switch will not be detected<br>\n" +
		"1-255 (1-255W) - report interval"],
	[id: 156, size:4, type: "number", range: "1..65535", defaultValue: 6000, required: false, readonly: false,
		name: "configParam156",
		title: "Time of up movement",
		description: "This parameter determines the time needed for roller blinds to reach the top. For modes with positioning value is set automatically during calibration, otherwise it must be set manually.<br>\n" +
		"1-65535 (0.01 - 655.35s, every 0.01s) - movement time"],
	[id: 157, size:4, type: "number", range: "1..65535", defaultValue: 6000, required: false, readonly: false,
		name: "configParam157",
		title: "Time of down movement",
		description: "This parameter determines time needed for roller blinds to reach the bottom. For modes with positioning value is set automatically during calibration, otherwise it must be set manually.<br>\n" +
		"1-65535 (0.01 - 655.35s, every 0.01s) - movement time"]
	]
}

This is something I will look into later on today. I will compare the driver I am using with the one you are using.
Are you using this driver for rollers and shades as well?

I would assume this is due to the way the custom driver is written. The authors themselves may have to alter them.

1 Like

I'm only using it for my Garage Roller door but it is seen as a shade in HE and Homekit.

In the house I mainly have motorised curtain rails rather than shades. I did originally use the FGR-223 for those, but they wouldn't work correctly with the curtain motors (Dooya). Despite trying different settings suggested on the Fibaro community, they would never successfully complete the calibration process and so the positioning didn't work (stuck at 255). I swapped them out with the Qubino Flush Shutter which works fine with the Hubitat driver in both HE & Homekit.

Yeah agreed. I was just a little surprised that one of the Parent/Child devices works when allegedly there's no support. I guess it's because the community driver that works is using the Hubitat Generic Component Switch for its child devices.

It still would be nice to see Hubitat support for more Fibaro products. To be fair it's not a particularly large range. The Double Switch 2 (FGS-223), Double Smart (FGS-224), and Roller Shutter 3 (FGR-223) are the main ones that are missing. There is support for the Smart Implant (which is a very handy device due its size, supply voltage range and dual inputs/outputs) but it's so basic that it's not worth using. As we don't have Zooz in the EU 868 frequency, the Smart Implant is the only product available for that purpose.

My zooz zen-16 shows up fine (3 child devices) with the zooz custom driver. Though I don't have any fibaro stuff

I think the issue Fibaro item I can't get to work properly is similar to the issue someone was having with Inovelli - where it appears as a button controller and just exposes a load of buttons that don't act on anything and throw errors in the logs.

Well, it turns out that I am using the same driver as you. The reason for not seeing the devices in HomeKit integration was that I have a dedicated Z-wave hub that I use to drive all Z-wave devices. The Z-wave devices are then meshed into my main hub. I installed the HomeKit integration on the main hub and most Z-wave devices did not show up.
The solution for me was to install the HomeKit integration on both hubs.
I am not sure why some of my Z-wave devices did show up in the HomeKit integration in my main hub as I was reading somewhere that Hubitat guys wanted to make sure we do not have another lag till we get to HomeKit.
The issue with Fibaro FGS-223 is still an issue and I really hope we will get a native driver for them soon.

1 Like

If you still have this issue. You can expose the accessory classes in the HomeKit integration app and change the device from Button controller into a different device type.
image

1 Like

Yeah I tried that but when I export as Switch it only shows the parent device and I need to be able to switch the child devices independently of one another.

1 Like

Same here. The only Fibaro double switches I can integrate to HomeKit are Fibaro FGS-222 and FGS-224 where the child devices are getting the Generic component switch driver.

1 Like

Yup same. The 224 Double Smart is the newer device but doesn't have power reporting like the 223 Double Switch. For now I've had to add 3 or 4 of the 223's back in via HomeBridge.

Which drivers are people using for the 223? It should be fairly easy to update so they use the same child device code.

1 Like

I think most people if not all are using this driver.
Your help will be very much appreciated.

Summary

/**

  • Note: This handler requires the "Metering Switch Child Device" to be installed.
  • Copyright 2016 Eric Maycock
  • 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 FGS-223 Dual Relay
  • Author: Eric Maycock (erocm123)
  • 04/25/2017 - Fix for combined energy & power reports & switch endpoints showing correct info.
  • 04/18/2017 - This handler requires the Metering Switch Child device to create the multiple switch endpoints.
    */

metadata {
definition (name: "Fibaro Double Switch 2 FGS-223", namespace: "erocm123", author: "Eric Maycock") {
capability "Sensor"
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Configuration"
capability "Refresh"
capability "Energy Meter"
capability "Power Meter"
capability "Health Check"
capability "PushableButton"
capability "HoldableButton"

command "reset"
command "childOn"
    command "childOff"
    command "childRefresh"
command "childReset"


fingerprint mfr: "010F", prod: "0203", model: "2000", deviceJoinName: "Fibaro Double Switch 2"
fingerprint mfr: "010F", prod: "0203", model: "1000", deviceJoinName: "Fibaro Double Switch 2"

fingerprint deviceId: "0x1001", inClusters:"0x5E,0x86,0x72,0x59,0x73,0x22,0x56,0x32,0x71,0x98,0x7A,0x25,0x5A,0x85,0x70,0x8E,0x60,0x75,0x5B"

}

simulator {
}

tiles(scale: 2){

multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
		tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
		   attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
		   attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00a0dc", nextState:"turningOff"
		   attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
		   attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00a0dc", nextState:"turningOff"
		}
        tileAttribute ("statusText", key: "SECONDARY_CONTROL") {
       		attributeState "statusText", label:'${currentValue}'       		
        }
}
valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
		state "default", label:'${currentValue} W'
}
valueTile("energy", "device.energy", decoration: "flat", width: 2, height: 2) {
		state "default", label:'${currentValue} kWh'
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
	state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.needUpdate", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
        state "NO" , label:'', action:"configuration.configure", icon:"st.secondary.configure"
        state "YES", label:'', action:"configuration.configure", icon:"https://github.com/erocm123/SmartThingsPublic/raw/master/devicetypes/erocm123/qubino-flush-1d-relay.src/configure@2x.png"
}
standardTile("reset", "device.energy", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
	state "default", label:'reset kWh', action:"reset"
}

main(["switch"])
details(["switch", 
         "refresh","reset","configure"])

}
preferences {
input description: "Once you change values on this page, the corner of the "configuration" icon will change orange until all configuration parameters are updated.", title: "Settings", displayDuringSetup: false, type: "paragraph", element: "paragraph"
generate_preferences(configuration_model())
}
}

private getCommandClassVersions() {
[
0x20: 1, // Basic
0x25: 1, // Switch Binary
0x70: 2, // Configuration
0x98: 1, // Security
0x60: 4, // Multi Channel
0x8E: 2, // Multi Channel Association
0x26: 1, // Switch Multilevel
0x87: 1, // Indicator
0x72: 2, // Manufacturer Specific
0x5B: 1, // Central Scene
0x32: 3, // Meter
0x85: 2, // Association
0x86: 1, // Version
0x9B: 1, // Association Command Configuration
0x90: 1, // Energy Production
0x73: 1, // Powerlevel
0x30: 1, // Sensor Binary
0x28: 1, // Switch Toggle Binary
0x2B: 1, // Scene Activation
0x75: 2 // Protection
]
}

def parse(String description) {
//log.debug description
def result =
def cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result += zwaveEvent(cmd)
//log.debug "Parsed ${cmd} to ${result.inspect()}"
} else {
log.debug "Non-parsed event: ${description}"
}

def statusTextmsg = ""

result.each {
    if ((it instanceof Map) == true && it.find{ it.key == "name" }?.value == "power") {
        statusTextmsg = "${it.value} W ${device.currentValue('energy')? device.currentValue('energy') : "0"} kWh"
    }
    if ((it instanceof Map) == true && it.find{ it.key == "name" }?.value == "energy") {
        statusTextmsg = "${device.currentValue('power')? device.currentValue('power') : "0"} W ${it.value} kWh"
    }
}
if (statusTextmsg != "") sendEvent(name:"statusText", value:statusTextmsg, displayed:false)

return result

}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd)
{
log.debug "BasicReport $cmd"
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd, ep=null) {
logging("BasicSet: $cmd : Endpoint: $ep")
if (ep) {
def event
childDevices.each { childDevice ->
if (childDevice.deviceNetworkId == "$device.deviceNetworkId-ep$ep") {
childDevice.sendEvent(name: "switch", value: cmd.value ? "on" : "off")
}
}
if (cmd.value) {
event = [createEvent([name: "switch", value: "on"])]
} else {
def allOff = true
childDevices.each {
childDevice ->
if (childDevice.deviceNetworkId != "$device.deviceNetworkId-ep$ep")
if (childDevice.currentState("switch").value != "off") allOff = false
}
if (allOff) {
event = [createEvent([name: "switch", value: "off"])]
} else {
event = [createEvent([name: "switch", value: "on"])]
}
}
return event
}
}

def zwaveEvent(hubitat.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd, ep=null)
{
logging("SwitchBinaryReport: $cmd : Endpoint: $ep")
if (ep) {
def event
childDevices.each { childDevice ->
if (childDevice.deviceNetworkId == "$device.deviceNetworkId-ep$ep") {
childDevice.sendEvent(name: "switch", value: cmd.value ? "on" : "off")
}
}
if (cmd.value) {
event = [createEvent([name: "switch", value: "on"])]
} else {
def allOff = true
childDevices.each {
childDevice ->
if (childDevice.deviceNetworkId != "$device.deviceNetworkId-ep$ep")
if (childDevice.currentState("switch")?.value != "off") allOff = false
}
if (allOff) {
event = [createEvent([name: "switch", value: "off"])]
} else {
event = [createEvent([name: "switch", value: "on"])]
}
}
return event
} else {
def cmds =
cmds << encap(zwave.switchBinaryV1.switchBinaryGet(), 1)
cmds << encap(zwave.switchBinaryV1.switchBinaryGet(), 2)
return response(commands(cmds)) // returns the result of reponse()
}
}

def zwaveEvent(hubitat.zwave.commands.meterv3.MeterReport cmd, ep=null) {
logging("MeterReport: $cmd : Endpoint: $ep")
def result
def cmds =
if (cmd.scale == 0) {
result = [name: "energy", value: cmd.scaledMeterValue, unit: "kWh"]
} else if (cmd.scale == 1) {
result = [name: "energy", value: cmd.scaledMeterValue, unit: "kVAh"]
} else {
result = [name: "power", value: cmd.scaledMeterValue, unit: "W"]
}
if (ep) {
def childDevice = childDevices.find{it.deviceNetworkId == "$device.deviceNetworkId-ep$ep"}
if (childDevice)
childDevice.sendEvent(result)
def combinedValue = 0.00
childDevices.each {
if(it.currentValue(result.name)) combinedValue += it.currentValue(result.name)
}
return createEvent([name: result.name, value: combinedValue])
} else {
(1..2).each { endpoint ->
cmds << encap(zwave.meterV2.meterGet(scale: 0), endpoint)
cmds << encap(zwave.meterV2.meterGet(scale: 2), endpoint)
}
return response(commands(cmds))
}
}

def zwaveEvent(hubitat.zwave.commands.multichannelv4.MultiChannelCapabilityReport cmd)
{
//log.debug "multichannelv4.MultiChannelCapabilityReport $cmd"
if (cmd.endPoint == 2 ) {
def currstate = device.currentState("switch2").getValue()
if (currstate == "on")
sendEvent(name: "switch2", value: "off", isStateChange: true, display: false)
else if (currstate == "off")
sendEvent(name: "switch2", value: "on", isStateChange: true, display: false)
}
else if (cmd.endPoint == 1 ) {
def currstate = device.currentState("switch1").getValue()
if (currstate == "on")
sendEvent(name: "switch1", value: "off", isStateChange: true, display: false)
else if (currstate == "off")
sendEvent(name: "switch1", value: "on", isStateChange: true, display: false)
}
}

def zwaveEvent(hubitat.zwave.commands.multichannelv4.MultiChannelCmdEncap cmd) {
//logging("MultiChannelCmdEncap ${cmd}")
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer)
}
}

def zwaveEvent(hubitat.zwave.commands.associationv2.AssociationReport cmd) {
log.debug "AssociationReport $cmd"
if (zwaveHubNodeId in cmd.nodeId) state."association${cmd.groupingIdentifier}" = true
else state."association${cmd.groupingIdentifier}" = false
}

def zwaveEvent(hubitat.zwave.commands.multichannelassociationv2.MultiChannelAssociationReport cmd) {
log.debug "MultiChannelAssociationReport $cmd"
if (cmd.groupingIdentifier == 1) {
if ([0,zwaveHubNodeId,0] == cmd.nodeId) state."associationMC${cmd.groupingIdentifier}" = true
else state."associationMC${cmd.groupingIdentifier}" = false
}
}

def zwaveEvent(hubitat.zwave.Command cmd) {
log.debug "Unhandled event $cmd"
// This will capture any commands not handled by other instances of zwaveEvent
// and is recommended for development so you can see every command the device sends
return createEvent(descriptionText: "${device.displayName}: ${cmd}")
}

def zwaveEvent(hubitat.zwave.commands.switchallv1.SwitchAllReport cmd) {
log.debug "SwitchAllReport $cmd"
}

def zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport cmd) {
update_current_properties(cmd)
logging("${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd2Integer(cmd.configurationValue)}'")
}

def refresh() {
def cmds =
(1..2).each { endpoint ->
cmds << encap(zwave.switchBinaryV1.switchBinaryGet(), endpoint)
cmds << encap(zwave.meterV2.meterGet(scale: 0), endpoint)
cmds << encap(zwave.meterV2.meterGet(scale: 2), endpoint)
}
commands(cmds, 1000)
}

def reset() {
logging("reset()")
def cmds =
(1..2).each { endpoint ->
cmds << encap(zwave.meterV2.meterReset(), endpoint)
cmds << encap(zwave.meterV2.meterGet(scale: 0), endpoint)
cmds << encap(zwave.meterV2.meterGet(scale: 2), endpoint)
}
commands(cmds, 1000)
}

def ping() {
def cmds =
cmds << encap(zwave.switchBinaryV1.switchBinaryGet(), 1)
cmds << encap(zwave.switchBinaryV1.switchBinaryGet(), 2)
commands(cmds, 1000)
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
log.debug "msr: $msr"
updateDataValue("MSR", msr)
}

def poll() {
def cmds =
cmds << encap(zwave.switchBinaryV1.switchBinaryGet(), 1)
cmds << encap(zwave.switchBinaryV1.switchBinaryGet(), 2)
commands(cmds, 1000)
}

def configure() {
state.enableDebugging = settings.enableDebugging
logging("Configuring Device For SmartThings Use")
def cmds =

cmds = update_needed_settings()

if (cmds != []) commands(cmds)

}

def zwaveEvent(hubitat.zwave.commands.centralscenev1.CentralSceneNotification cmd) {
logging("CentralSceneNotification: $cmd")
logging("sceneNumber: $cmd.sceneNumber")
logging("sequenceNumber: $cmd.sequenceNumber")
logging("keyAttributes: $cmd.keyAttributes")

buttonEvent(cmd.keyAttributes + 1, (cmd.sceneNumber == 1? "pushed" : "held"))

}

def buttonEvent(button, value) {
logging("buttonEvent() Button:$button, Value:$value")
sendEvent(name: value, value: button, isStateChange:true)
}

def installed() {
def cmds = initialize()
if (cmds != ) commands(cmds)
}

def updated()
{
logging("updated() is being called")
def cmds = initialize()
sendEvent(name:"needUpdate", value: device.currentValue("needUpdate"), displayed:false, isStateChange: true)
if (cmds != ) commands(cmds)
}

def initialize() {
log.debug "initialize()"
state.enableDebugging = settings.enableDebugging
if (!childDevices) {
createChildDevices()
}
else if (device.label != state.oldLabel) {
childDevices.each {
if (it.label == "${state.oldLabel} (S${channelNumber(it.deviceNetworkId)})") {
def newLabel = "${device.displayName} (S${channelNumber(it.deviceNetworkId)})"
it.setLabel(newLabel)
}
}
state.oldLabel = device.label
}
sendEvent(name: "checkInterval", value: 2 * 30 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
return update_needed_settings()
}

def on() {
commands([
encap(zwave.basicV1.basicSet(value: 0xFF), 1),
encap(zwave.basicV1.basicSet(value: 0xFF), 2)
])
}
def off() {
commands([
encap(zwave.basicV1.basicSet(value: 0x00), 1),
encap(zwave.basicV1.basicSet(value: 0x00), 2)
])
}

def childOn(String dni) {
logging("childOn($dni)")
def cmds =
cmds << new hubitat.device.HubAction(command(encap(zwave.basicV1.basicSet(value: 0xFF), channelNumber(dni))), hubitat.device.Protocol.ZWAVE)
cmds
}

def childOff(String dni) {
logging("childOff($dni)")
def cmds =
cmds << new hubitat.device.HubAction(command(encap(zwave.basicV1.basicSet(value: 0x00), channelNumber(dni))), hubitat.device.Protocol.ZWAVE)
cmds
}

def childRefresh(String dni) {
logging("childRefresh($dni)")
def cmds =
cmds << new hubitat.device.HubAction(command(encap(zwave.switchBinaryV1.switchBinaryGet(), channelNumber(dni))), hubitat.device.Protocol.ZWAVE)
cmds << new hubitat.device.HubAction(command(encap(zwave.meterV2.meterGet(scale: 0), channelNumber(dni))), hubitat.device.Protocol.ZWAVE)
cmds << new hubitat.device.HubAction(command(encap(zwave.meterV2.meterGet(scale: 2), channelNumber(dni))), hubitat.device.Protocol.ZWAVE)
cmds
}

def childReset(String dni) {
logging("childReset($dni)")
def cmds =
cmds << new hubitat.device.HubAction(command(encap(zwave.meterV2.meterReset(), channelNumber(dni))), hubitat.device.Protocol.ZWAVE)
cmds << new hubitat.device.HubAction(command(encap(zwave.meterV2.meterGet(scale: 0), channelNumber(dni))), hubitat.device.Protocol.ZWAVE)
cmds << new hubitat.device.HubAction(command(encap(zwave.meterV2.meterGet(scale: 2), channelNumber(dni))), hubitat.device.Protocol.ZWAVE)
cmds
}

private command(hubitat.zwave.Command cmd) {
if (state.sec) {
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
} else {
cmd.format()
}
}

private commands(commands, delay=1000) {
delayBetween(commands.collect{ command(it) }, delay)
}

private encap(cmd, endpoint) {
if (endpoint) {
zwave.multiChannelV4.multiChannelCmdEncap(destinationEndPoint:endpoint).encapsulate(cmd)
} else {
cmd
}
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
//log.debug cmd
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions) // can specify command class versions here like in zwave.parse
if (encapsulatedCommand) {
state.sec = 1
return zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
createEvent(descriptionText: cmd.toString())
}
}

def generate_preferences(configuration_model)
{
def configuration = new XmlSlurper().parseText(configuration_model)

configuration.Value.each
{
    switch(it.@type)
    {   
        case ["byte","short","four"]:
            input "${it.@index}", "number",
                title:"${it.@label}\n" + "${it.Help}",
                range: "${it.@min}..${it.@max}",
                defaultValue: "${it.@value}"
        break
        case "list":
            def items = []
            it.Item.each { items << ["${it.@value}":"${it.@label}"] }
            input "${it.@index}", "enum",
                title:"${it.@label}\n" + "${it.Help}",
                defaultValue: "${it.@value}",
                options: items
        break
        case "decimal":
           input "${it.@index}", "decimal",
                title:"${it.@label}\n" + "${it.Help}",
                range: "${it.@min}..${it.@max}",
                defaultValue: "${it.@value}"
        break
        case "boolean":
           input "${it.@index}", "bool",
                title: it.@label != "" ? "${it.@label}\n" + "${it.Help}" : "" + "${it.Help}",
                defaultValue: "${it.@value}"
        break
        case "paragraph":
           input title: "${it.@label}",
                description: "${it.Help}",
                type: "paragraph",
                element: "paragraph"
        break
    }  
}

}

def update_current_properties(cmd)
{
def currentProperties = state.currentProperties ?: [:]

currentProperties."${cmd.parameterNumber}" = cmd.configurationValue

if (settings."${cmd.parameterNumber}" != null)
{
    if (convertParam(cmd.parameterNumber, settings."${cmd.parameterNumber}") == cmd2Integer(cmd.configurationValue))
    {
        sendEvent(name:"needUpdate", value:"NO", displayed:false, isStateChange: true)
    }
    else
    {
        sendEvent(name:"needUpdate", value:"YES", displayed:false, isStateChange: true)
    }
}

state.currentProperties = currentProperties

}

def update_needed_settings()
{
def cmds =
def currentProperties = state.currentProperties ?: [:]

def configuration = new XmlSlurper().parseText(configuration_model())
def isUpdateNeeded = "NO"

sendEvent(name:"numberOfButtons", value:"5")

if(!state.associationMC1) {
   logging("Adding MultiChannel association group 1")
   cmds << zwave.associationV2.associationRemove(groupingIdentifier: 1, nodeId: [])
   //cmds << zwave.associationV2.associationGet(groupingIdentifier: 1)
   cmds << zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier: 1, nodeId: [0,zwaveHubNodeId,0])
   cmds << zwave.multiChannelAssociationV2.multiChannelAssociationGet(groupingIdentifier: 1)
}
if(state.association2){
   logging("Removing association group 2")
   cmds << zwave.associationV2.associationRemove(groupingIdentifier:2, nodeId:zwaveHubNodeId)
   cmds << zwave.associationV2.associationGet(groupingIdentifier:2)
}
if(state.association4){
   logging("Removing association group 4")
   cmds << zwave.associationV2.associationRemove(groupingIdentifier:4, nodeId:zwaveHubNodeId)
   cmds << zwave.associationV2.associationGet(groupingIdentifier:4)
}

configuration.Value.each
{     
    if ("${it.@setting_type}" == "zwave"){
        if (currentProperties."${it.@index}" == null)
        {
            isUpdateNeeded = "YES"
            logging("Current value of parameter ${it.@index} is unknown")
            cmds << zwave.configurationV1.configurationGet(parameterNumber: it.@index.toInteger())
        }
        else if (settings."${it.@index}" != null && cmd2Integer(currentProperties."${it.@index}") != convertParam(it.@index.toInteger(), settings."${it.@index}"))
        { 
            isUpdateNeeded = "YES"

            logging("Parameter ${it.@index} will be updated to " + convertParam(it.@index.toInteger(), settings."${it.@index}"))
            def convertedConfigurationValue = convertParam(it.@index.toInteger(), settings."${it.@index}")
            cmds << zwave.configurationV1.configurationSet(configurationValue: integer2Cmd(convertedConfigurationValue, it.@byteSize.toInteger()), parameterNumber: it.@index.toInteger(), size: it.@byteSize.toInteger())
            cmds << zwave.configurationV1.configurationGet(parameterNumber: it.@index.toInteger())
        } 
    }
}

sendEvent(name:"needUpdate", value: isUpdateNeeded, displayed:false, isStateChange: true)
return cmds

}

def convertParam(number, value) {
def parValue
switch (number){
case 28:
parValue = (value == true ? 1 : 0)
parValue += (settings."fc_2" == true ? 2 : 0)
parValue += (settings."fc_3" == true ? 4 : 0)
parValue += (settings."fc_4" == true ? 8 : 0)
log.debug parValue
break
case 29:
parValue = (value == true ? 1 : 0)
parValue += (settings."sc_2" == true ? 2 : 0)
parValue += (settings."sc_3" == true ? 4 : 0)
parValue += (settings."sc_4" == true ? 8 : 0)
break
default:
parValue = value
break
}
return parValue.toInteger()
}

private def logging(message) {
if (state.enableDebugging == null || state.enableDebugging == true) log.debug "$message"
}

/**

  • Convert 1 and 2 bytes values to integer
    */
    def cmd2Integer(array) {

switch(array.size()) {
case 1:
array[0]
break
case 2:
((array[0] & 0xFF) << 8) | (array[1] & 0xFF)
break
case 3:
((array[0] & 0xFF) << 16) | ((array[1] & 0xFF) << 8) | (array[2] & 0xFF)
break
case 4:
((array[0] & 0xFF) << 24) | ((array[1] & 0xFF) << 16) | ((array[2] & 0xFF) << 8) | (array[3] & 0xFF)
break
}
}

def integer2Cmd(value, size) {
switch(size) {
case 1:
[value]
break
case 2:
def short value1 = value & 0xFF
def short value2 = (value >> 8) & 0xFF
[value2, value1]
break
case 3:
def short value1 = value & 0xFF
def short value2 = (value >> 8) & 0xFF
def short value3 = (value >> 16) & 0xFF
[value3, value2, value1]
break
case 4:
def short value1 = value & 0xFF
def short value2 = (value >> 8) & 0xFF
def short value3 = (value >> 16) & 0xFF
def short value4 = (value >> 24) & 0xFF
[value4, value3, value2, value1]
break
}
}

def zwaveEvent(hubitat.zwave.commands.crc16encapv1.Crc16Encap cmd) {
def encapsulatedCommand = zwave.getCommand(cmd.commandClass, cmd.command, cmd.data,1)
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand)
} else {
log.warn "Unable to extract CRC16 command from ${cmd}"
}
}

private channelNumber(String dni) {
dni.split("-ep")[-1] as Integer
}

private void createChildDevices() {
state.oldLabel = device.label
try {
for (i in 1..2) {
addChildDevice("Metering Switch Child Device", "${device.deviceNetworkId}-ep${i}",
[completedSetup: true, label: "${device.displayName} (S${i})",
isComponent: false, componentName: "ep$i", componentLabel: "Switch $i"])
}
} catch (e) {
runIn(2, "sendAlert")
}
}

private sendAlert() {
sendEvent(
descriptionText: "Child device creation failed. Please make sure that the "Metering Switch Child Device" is installed and published.",
eventType: "ALERT",
name: "childDeviceCreation",
value: "failed",
displayed: true,
)
}

def configuration_model()
{
'''



The device will return to the last state before power failure.
0 - the Dimmer 2 does not save the state before a power failure, it returns to the "off" position
1 - the Dimmer 2 restores its state before power failure
Range: 0~1
Default: 1 (Previous State)






This parameter allows you to choose the operating mode for the 1st channel controlled by the S1 switch.
Range: 0~5
Default: 0 (Standard)










This parameter determines how the device in timed mode reacts to pushing the switch connected to the S1 terminal.
Range: 0~2
Default: 0 (Cancel)







This parameter allows to set time parameter used in timed modes.
Range: 0~32000 (0.1s, 1-32000s)
Default: 50




This parameter allows to set time of switching to opposite state in flashing mode.
Range: 1~32000 (0.1s-3200.0s)
Default: 5 (0.5s)




This parameter allows you to choose the operating mode for the 2nd channel controlled by the S2 switch.
Range: 0~5
Default: 0 (Standard)










This parameter determines how the device in timed mode reacts to pushing the switch connected to the S2 terminal.
Range: 0~2
Default: 0 (Cancel)







This parameter allows to set time parameter used in timed modes.
Range: 0~32000 (0.1s, 1-32000s)
Default: 50




This parameter allows to set time of switching to opposite state in flashing mode.
Range: 1~32000 (0.1s-3200.0s)
Default: 5 (0.5s)




Choose between momentary and toggle switch.
Range: 0~2
Default: 2 (Toggle)







This parameter determines how the device will react to General Alarm frame.
Range: 0~3
Default: 3 (Flash)








This parameter determines how the device will react to Flood Alarm frame.
Range: 0~3
Default: 2 (OFF)








This parameter determines how the device will react to CO, CO2 or Smoke frame.
Range: 0~3
Default: 3 (Flash)








This parameter determines how the device will react to Heat Alarm frame.
Range: 0~3
Default: 1 (ON)








This parameter allows to set duration of flashing alarm mode.
Range: 1~32000 (1s-32000s)
Default: 600 (10 min)




The parameter defines the power level change that will result in a new power report being sent. The value is a percentage of the previous report.
Range: 0~100 (1-100%)
Default: 10




Parameter 51 defines a time period between consecutive reports. Timer is reset and counted from zero after each report.
Range: 0~120 (1-120s)
Default: 10




Energy level change which will result in sending a new energy report.
Range: 0~32000 (0.01-320 kWh)
Default: 100




The parameter defines the power level change that will result in a new power report being sent. The value is a percentage of the previous report.
Range: 0~100 (1-100%)
Default: 10




Parameter 55 defines a time period between consecutive reports. Timer is reset and counted from zero after each report.
Range: 0~120 (1-120s)
Default: 10




Energy level change which will result in sending a new energy report.
Range: 0~32000 (0.01-320 kWh)
Default: 100




This parameter determines in what time interval the periodic power reports are sent to the main controller.
Range: 0~32000 (1-32000s)
Default: 3600




This parameter determines in what time interval the periodic energy reports are sent to the main controller.
Range: 0~32000 (1-32000s)
Default: 3600




Send scene ID on single press




Send scene ID on double press




Send scene ID on tripple press




Send scene ID on hold and release




Send scene ID on single press




Send scene ID on double press




Send scene ID on tripple press




Send scene ID on hold and release




Toggle Mode
1 pushed - S1 1x toggle
4 pushed - S1 2x toggle
5 pushed - S1 3x toggle
1 held - S2 1x toggle
4 held - S2 2x toggle
5 held - S2 3x toggle
Momentary Mode
1 pushed - S1 1x click
2 pushed - S1 release
3 pushed - S1 hold
4 pushed - S1 2x click
5 pushed - S1 3x click
1 held - S2 1x click
2 held - S2 release
3 held - S2 hold
4 held - S2 2x click
5 held - S2 3x click







'''
}

1 Like

And just to confirm from a quick scan of this page which seems to be the original from your message, the power readings do not show in the child device? The only thing the child devices do is on/off?

And I assume you have the metering switch child device installed from here ?

This is how the child device looks like:


It does show power metering.

True