Porting DTH for Fibaro Heat Contoller

Hi, has anyone created a driver for the Fibaro heat controller (Radiator Valve)?

I tried porting one from smartthings but can’t find any clear instructions or documentation about writing studs for Hubitat.

Tim

1 Like

You've seen this, right?

It presupposes that there's a ST DH hanging around and all it needs is to be ported over to Hubitat. I have no way of knowing, but it sure seems like that doc covers "around 90%" of the devices.

I am still having trouble porting over this device to habitat.

@Patrick please can the support team help me understand the layout, capabilities and definitions that need to go into a Hubitat device handler. I can't find any documentation anywhere (other than the rather unclear post (App and driver porting to Hubitat) that @csteele pointed me to a few months ago.

I am trying to port this device:
https://github.com/timowen001gh/Fibaro-HeatController-Valve

and got this far:

  • Fibaro Heat Controller FGT-001
  • Branch from TomĂĄĹĄ MrĂĄzek 2018 original file to port radiator valve to Hubitat.
  • 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:
  • Apache License, Version 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.

*/
metadata {
definition (name: "Fibaro Heat Controller", namespace: "Tomas-Mrazek", author: "TomĂĄĹĄ MrĂĄzek") {
fingerprint mfr: "010F", prod: "1301", model: "1000"

capability "Battery"
capability "Thermostat"
capability "Valve"
capability "Thermostat Setpoint"
capability "Refresh"
capability "Polling"

attribute "externalSensorConnected", "string"
attribute "openWindowDetected", "string"
attribute "batterySensor", "number"

command "setThermostatSetpointUp"
command "setThermostatSetpointDown"

}

standardTile("off", "device.valve", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
  state "thermostatMode", action: "thermostatMode.off", icon: "st.vents.vent-closed"
}


standardTile("heat", "device.thermostatMode", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
  state "thermostatMode", action: "thermostatMode.heat", icon: "st.vents.vent-open-text"
}

valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
  state "battery", label: 'valve battery\n${currentValue}%', unit: "%"
}

valueTile("batterySensor", "device.batterySensor", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
  state "batterySensor", label: 'sensor battery\n${currentValue}%', unit: "%"
}

standardTile("refresh", "command.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
  state "refresh", label: 'refresh', action: "refresh.refresh", icon: "st.secondary.refresh-icon"
}

}

preferences {
input name: "overrideScheduleDuration", title: "Override Schedule duration (minutes between 10 and 10000)", description: "", type: "number", range: "10..10000", defaultValue: "240", required: true
input name: "openWindowDetector", title: "Open Window Detector", description: "", type: "bool", defaultValue: true
input name: "fastOpenWindowDetector", title: "Fast Open Window Detector", description: "", type: "bool", defaultValue: false
input name: "increaseRecieverSensitivity", title: "Increase Receiver Sensitivity (shortens battery life)", description: "shortens battery life", type: "bool", defaultValue: false
input name: "ledWhenRemoteControll", title: "LED Indications When Controlling Remotely", description: "", type: "bool", defaultValue: false
input name: "protectManualOnOff", title: "Protect from setting Full ON and Full OFF mode by turning the knob manually", description: "", type: "bool", defaultValue: false
}

/**

  • SMARTTHINGS UX EVENTS

*/

def off() {
setThermostatMode("off")
}

def auto() {
setThermostatMode("auto")
}

def heat() {
setThermostatMode("heat")
}

def setThermostatMode(String mode) {
sendEvent(name: "thermostatMode", value: mode, isStateChange: true)
switch (mode) {
case "auto":
encapsulate(zwave.thermostatModeV2.thermostatModeSet(mode: 1), 1)
break
case "heat":
encapsulate(zwave.thermostatModeV2.thermostatModeSet(mode: 31), 1)
break
case "off":
encapsulate(zwave.thermostatModeV2.thermostatModeSet(mode: 0), 1)
break
}
}

def setThermostatSetpointUp() {
def setpoint = device.latestValue("thermostatSetpoint")
if (setpoint < 24) {
setpoint = setpoint + 1
}
setThermostatSetpoint(setpoint)
}

def setThermostatSetpointDown() {
def setpoint = device.latestValue("thermostatSetpoint")
if (setpoint > 16) {
setpoint = setpoint - 1
}
setThermostatSetpoint(setpoint)
}

def setThermostatSetpoint(setpoint) {
sendEvent(name: "thermostatSetpoint", unit: "C", value: setpoint.setScale(0, BigDecimal.ROUND_DOWN), isStateChange: true)
encapsulate(zwave.thermostatSetpointV2.thermostatSetpointSet([precision: 1, scale: 0, scaledValue: setpoint, setpointType: 1, size: 2]), 1)
}

def updated() {
if ( state.lastUpdated && (now() - state.lastUpdated) < 500 ) return
def paramsString = [settings.openWindowDetector, settings.fastOpenWindowDetector, settings.increaseRecieverSensitivity, settings.ledWhenRemoteControll, settings.protectManualOnOff]
def params = paramsString.collect({(it == true) ? 1 : 0})
def cmds =
cmds << [cmd: zwave.configurationV1.configurationSet(parameterNumber: 1, scaledConfigurationValue: settings.overrideScheduleDuration)]
cmds << [cmd: zwave.configurationV1.configurationSet(parameterNumber: 2, scaledConfigurationValue: getIntegerFromParams(params))]
for (int i = 1; i <= 2; i++) {
cmds << [cmd: zwave.configurationV1.configurationGet(parameterNumber: i)]
}
state.lastUpdated = now()
response(encapsulateSequence(cmds, 2000))
}

def refresh() {
log.debug "refresh()"
def cmds =
cmds << [cmd: zwave.batteryV1.batteryGet(), endpoint: 1]
cmds << [cmd: zwave.batteryV1.batteryGet(), endpoint: 2]
cmds << [cmd: zwave.thermostatModeV2.thermostatModeGet(), endpoint: 1]
cmds << [cmd: zwave.sensorMultilevelV5.sensorMultilevelGet(), endpoint: 2]
cmds << [cmd: zwave.configurationV1.configurationGet(parameterNumber: 3)]
encapsulateSequence(cmds, 2000)
}

def poll() {
log.debug "poll()"
def cmds =
cmds << [cmd: zwave.batteryV1.batteryGet(), endpoint: 1]
cmds << [cmd: zwave.batteryV1.batteryGet(), endpoint: 2]
cmds << [cmd: zwave.sensorMultilevelV5.sensorMultilevelGet(), endpoint: 2]
encapsulateSequence(cmds, 2000)
}

private encapsulate(hubitat.zwave.Command cmd, endpoint = null) {
if (zwaveInfo.zw.contains("s")) {
if (endpoint) {
secureEncapsulate(multichannelEncapsulate(cmd, endpoint)).format()
} else {
secureEncapsulate(cmd).format()
}
} else {
log.warn "${device.displayName} - no encapsulation supported for command: ${cmd}"
cmd.format()
}
}

private encapsulateSequence(cmds, delay) {
def commands = cmds.collect{[it.get('cmd'), it.get('endpoint')]}
delayBetween(commands.collect{encapsulate(it)}, delay)
}

private secureEncapsulate(hubitat.zwave.Command cmd) {
//log.trace "${device.displayName} - encapsulating command using Secure Encapsulation, command: ${cmd}"
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
}

private multichannelEncapsulate(hubitat.zwave.Command cmd, endpoint) {
//log.trace "${device.displayName} - encapsulating command using Multi Channel Encapsulation, command: ${cmd}"
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint: endpoint).encapsulate(cmd)
}

/**

  • Z-WAVE DEVICE EVENTS

*/

def parse(String description) {
//log.debug "PARSE – description – ${description}"
def result = null
def cmd = zwave.parse(description)
if (cmd) {
result = zwaveEvent(cmd)
} else {
log.warn "${device.displayName} - non-parsed event: ${description}"
}
return result
}

def zwaveEvent(hubitat.zwave.Command cmd) {
log.info "${device.displayName} - unhandled parsed event without result ${cmd}"
return result
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand(cmdVersions())
if (encapsulatedCommand) {
//log.debug "${device.displayName} - parsed SecurityMessageEncapsulation into: ${encapsulatedCommand}"
zwaveEvent(encapsulatedCommand)
} else {
//log.warn "${device.displayName} – unable to extract secure command from $cmd"
createEvent(descriptionText: cmd.toString())
}
}

def zwaveEvent(hubitat.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand(cmdVersions())
if (encapsulatedCommand) {
//log.debug "${device.displayName} - parsed MultiChannelCmdEncap into: ${encapsulatedCommand}"
zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint)
} else {
//log.warn "${device.displayName} – unable to extract multi channel command from $cmd"
createEvent(descriptionText: cmd.toString())
}
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd, sourceEndPoint = null) {
def result = createEvent(descriptionText: "${device.displayName}: ${cmd}")
log.info "${device.displayName} - parsed event ${cmd} into: ${result}"
return result
}

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd, sourceEndPoint = null) {
def result
switch(sourceEndPoint) {
case 1:
result = createEvent([name: "battery", unit: "%", value: cmd.batteryLevel])
break
case 2:
result = createEvent([name: "batterySensor", unit: "%", value: cmd.batteryLevel])
break
default:
result = createEvent([name: "battery", unit: "%", value: cmd.batteryLevel])
break
}
log.info "${device.displayName} - parsed event ${cmd} into: ${result}"
return result
}

def zwaveEvent(hubitat.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd, sourceEndPoint = null) {
def result = createEvent([name: "thermostatSetpoint", unit: "C", value: cmd.scaledValue.setScale(0, BigDecimal.ROUND_DOWN)])
log.info "${device.displayName} - parsed event ${cmd} into: ${result}"
return result
}

def zwaveEvent(hubitat.zwave.commands.thermostatmodev2.ThermostatModeReport cmd, sourceEndPoint = null) {
def result
switch (cmd.mode) {
case 1:
result = createEvent([name: "thermostatMode", value: "auto"])
break
case 31:
result = createEvent([name: "thermostatMode", value: "heat"])
break
case 0:
result = createEvent([name: "thermostatMode", value: "off"])
break
}
log.info "${device.displayName} - parsed event ${cmd} into: ${result}"
return result
}

def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd, sourceEndPoint = null) {
def result = createEvent([name: "temperature", unit: "C", value: cmd.scaledSensorValue])
log.info "${device.displayName} - parsed event ${cmd} into: ${result}"
return result
}

def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd, sourceEndPoint = null) {
switch (cmd.parameterNumber) {
case 1:
settings.overrideScheduleDuration = cmd.scaledConfigurationValue
break
case 2:
def params = getParamsFromInteger(cmd.scaledConfigurationValue, 5)
settings.openWindowDetector = params.get(0)
settings.fastOpenWindowDetector = params.get(1)
settings.increaseRecieverSensitivity = params.get(2)
settings.ledWhenRemoteControll = params.get(3)
settings.protectManualOnOff = params.get(4)
break
case 3:
def params = getParamsFromInteger(cmd.scaledConfigurationValue, 2)
def result1 = createEvent([name: "externalSensorConnected", value: (params.get(0) == 1) ? "true" : "false"])
def result2 = createEvent([name: "openWindowDetected", value: (params.get(1) == 1) ? "true" : "false"])
log.info "${device.displayName} - parsed event ${cmd} into: ${result1} | ${result2}"
return [result1, result2]
}
log.info "${device.displayName} - parsed event without result ${cmd}"
}

/**

  • UTILS

*/

private Map cmdVersions() {
[0x80: 1, 0x40: 2, 0x43: 2, 0x31: 5, 0x70: 1]
}

private getParamsFromInteger(decimal, numberOfParams) {
def bit = Math.pow(new Double(2), new Double(numberOfParams - 1))
def params =
for(int i = 0; i < numberOfParams; i++) {
if (decimal >= bit) {
params << 1
decimal = decimal - bit
bit = bit / 2
} else {
params << 0
if (numberOfParams != i) {
bit = bit / 2
}
}
}
params = params.reverse()
return params
}

private getIntegerFromParams(params) {
def bit = 1
def decimal = 0
for(int i = 0; i < params.size(); i++) {
decimal = decimal + params.get(i) * bit
bit = bit * 2
}
return decimal
}

The driver displays correctly on the device page, however I cannot send command to the device.

Thank you in advance for your time.

Tim

1 Like

Did you get any further with this? I'm considering getting some of these to replace my existing TRVs.

Hi,

No I even offered to send my test unit to Hubitat so that they could do it.

Tim

I may get one and see if I can work it out, I've never worked on a ZWave driver before, so it might be a good learning experience :slight_smile:

Somehow I missed this, if this device is using EU, US or AU frequencies I can have a look at it.

Hi Mike,

It is a UK unit. I have the external temperature sensor too. I got completely stuck trying to port the driver.

DM me where you want it sent to.

Tim

Any luck with the fibaro heat valve?

Partly yes. @mike.maxwell has written a driver for it and the external sensor and I am wait to get hold of the beta version to test. They (Hubitat) are really busy at the moment so it may take a few weeks before the driver is finished.

1 Like

Hi,

I have the Fibaro Heat controller up and running using the beta driver from @mike.maxwell.

Currently it is working with the Fibaro external sensor. I am using Hubitat to set the mode and set point then the device is automatically controlling the room temperature.

Hi, where can i find this driver?

It is not publicly available yet. You could ask @mike.maxwell if you could help beta test it. It is in pretty good shape and has been working here for a month.

I would suggest sending him a private message.

Tim

I'll get this released in 2.1.7

1 Like

Thanks Mike.

I heard this week that there is now an official Smartthings device handler for the Fibaro Heat Controller that creature a child device for the external sensor. Apparently it is in their Github repository. See the email below. Don’t know if this would help you. Tomáš was they guy who wrote the third party ST DH and it sounds like he is moving to the ST version.

Tim

Thanks Mike, i got 2.1.7 and got driver for Fibaro Heat Controller, but somehow it doesnt work with google, its get rejected when I add it in a list: "The following devices are not supported by Google Home and will be removed from your device list:[Radiator Balcony, Radiator Window]". Can you please tweeak a little this driver to statisfy google :slight_smile:

It will be also great for this driver to support parameters, I used your driver "Basic Z-Wave tool" to do the job, but it would be great to integrate those two params in this driver.

Google currently isn't setup to support a thermostat that doesn't include air conditioning, since this is a heating only controller the integration is going to reject the device.

How do you determine call for heat so you can turn the boiler on/off?
Does the status change from heat to idle when its at temperature?

Thanks,

Jim

Hi James,

Welcome to the Hubitat community.

I do not need to what you are asking about just yet (Not until we move house). However I was planning on trying a rule that would monitor the Fibaro radiator TRVs and then set the boiler to "Heat" if they call for it.

Rule would look a bit like this...
Trigger: If the thermostat mode of TRV is "Heat" and the virtual switch "Disable Heating" is "OFF"
Actions: Set the thermostat connected to the boiler to "Heat"
Actions for False: Set the thermostat connected to the boiler to "Off"

The way rule machine presents the options for triggers / conditions / actions is a but unintuitive.
I have not tested this rule yet but is should work.

Tim

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.