Fibaro co2 fgcd-001

Hi, does anyone have a Hubitat Elevation device handler for the Fibaro C0 [Carbon Monoxide] sensor?

This device is very neat, reasonably priced battery powered unit with wake-up dependencies and conditional reporting.. I am struggling to extract the PPM data, temperature and battery status.. Can anyone help out please?


1 Like

I haven't got one but here is a driver for the device on SmartThings.
Cahnge all references of "physicalgraph" to "hubitat". It may work but if not it's a good starting point.

Hi, thank you. I found this too, but as said this is quite complex and some of the outputs appear to be conditional.. I was hoping that someone else might have been down this path and converted the SmartThings driver.

The product is very well designed and reasonably priced. I am a little surprised that something so useful and we all need to be mindful (Carbon Monoxide levels) of is not yet implemented by Hubitat itself. I suppose it might in time. I'll try and work it through myself. Thanks anyway for your response.

Here is the modified code that does load into the Hubitat drivers area.
I do not have one so do not know if it works or not.
Give it a try and see if it works OK.

 *  FIBARO CO Sensor
 *  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:
 *  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 CO Sensor ZW5", namespace: "FibarGroup", author: "Fibar Group") {
        capability "Carbon Monoxide Detector"
        capability "Tamper Alert"
        capability "Temperature Measurement"
        capability "Configuration"
        capability "Battery"
        capability "Sensor"
        capability "Health Check"

        attribute "temperatureAlarm", "string"
        attribute "coLevel", "number"

        fingerprint mfr: "010F", prod: "1201"
        fingerprint deviceId: "0x0701", inClusters:"0x5E,0x59,0x73,0x80,0x22,0x56,0x31,0x98,0x7A,0x5A,0x85,0x84,0x71,0x70,0x8E,0x9C,0x86,0x72"
        fingerprint deviceId: "0x0701", inClusters:"0x5E,0x59,0x73,0x80,0x22,0x56,0x31,0x7A,0x5A,0x85,0x84,0x71,0x70,0x8E,0x9C,0x86,0x72"

    tiles (scale: 2) {
        multiAttributeTile(name:"FGDW", type:"lighting", width:6, height:4) {
            tileAttribute("device.carbonMonoxide", key:"PRIMARY_CONTROL") {
                attributeState("clear", label:"clear", icon:"", backgroundColor:"#ffffff")
                attributeState("detected", label:"detected", icon:"", backgroundColor:"#e86d13")
                attributeState("tested", label:"tested", icon:"", backgroundColor:"#e86d13")
            tileAttribute("device.multiStatus", key:"SECONDARY_CONTROL") {
                attributeState("multiStatus", label:'${currentValue}')

        valueTile("coLevel", "device.coLevel", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
            state "coLevel", label:'${currentValue}\nppm', unit:"ppm"

        valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
            state "temperature", label:'${currentValue}°',
                            [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"]

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

        standardTile("temperatureAlarm", "device.temperatureAlarm", inactiveLabel: false, width: 3, height: 2, decoration: "flat") {
            state "default", label: "No temp. alarm", backgroundColor:"#ffffff"
            state "clear", label:'', backgroundColor:"#ffffff" , icon: ""
            state "overheat", label:'Overheat', backgroundColor:"#d04e00", icon: ""

        standardTile("tamper", "device.tamper", inactiveLabel: false, width: 3, height: 2, decoration: "flat") {
            state "clear", label:'', icon: ""
            state "detected", label:'', icon: ""

        main "FGDW"

    preferences {

        input (
                title: "Fibaro CO Sensor ZW5 manual",
                description: "Tap to view the manual.",
                image: "",
                url: "",
                type: "href",
                element: "href"

        parameterMap().findAll{(it.num as Integer) != 54}.each {
            input (
                    title: "${it.num}. ${it.title}",
                    description: it.descr,
                    type: "paragraph",
                    element: "paragraph"

            input (
                    name: it.key,
                    title: null,
                    description: "Default: $it.def" ,
                    type: it.type,
                    options: it.options,
                    range: (it.min != null && it.max != null) ? "${it.min}..${it.max}" : null,
                    defaultValue: it.def,
                    required: false

        input ( name: "logging", title: "Logging", type: "boolean", required: false )

def updated() {
    if ( state.lastUpdated && (now() - state.lastUpdated) < 500 ) return
    logging("${device.displayName} - Executing updated()","info")

    sendEvent(name: "checkInterval", value: 86520, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])

    if ( (settings.zwaveNotifications as Integer) >= 2 ) {
        sendEvent(name: "temperatureAlarm", value: "clear", displayed: false)

    } else {
        sendEvent(name: "temperatureAlarm", value: null, displayed: false)

    state.lastUpdated = now()

def configure() {
    def cmds = []
    sendEvent(name: "coLevel", unit: "ppm", value: 0, displayed: true)
    cmds << zwave.batteryV1.batteryGet()
    cmds << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1)
    cmds << zwave.wakeUpV1.wakeUpNoMoreInformation()

private syncStart() {
    boolean syncNeeded = false
    parameterMap().each {
        if(settings."$it.key" != null || it.num == 54) {
            if (state."$it.key" == null) { state."$it.key" = [value: null, state: "synced"] }
            if (state."$it.key".value != (settings."$it.key" as Integer) || state."$it.key".state != "synced" ) {
                state."$it.key".value = (settings."$it.key" as Integer)
                state."$it.key".state = "notSynced"
                syncNeeded = true

    if ( syncNeeded ) {
        logging("${device.displayName} - sync needed.", "info")
        multiStatusEvent("Sync pending. Please wake up the device by pressing the Test button.", true)

private syncNext() {
    logging("${device.displayName} - Executing syncNext()","info")
    def cmds = []
    for ( param in parameterMap() ) {
        if ( state."$param.key"?.value != null && state."$param.key"?.state in ["notSynced","inProgress"] ) {
            multiStatusEvent("Sync in progress. (param: ${param.num})", true)
            state."$param.key"?.state = "inProgress"
            cmds << response(encap(zwave.configurationV2.configurationSet(configurationValue: intToParam(state."$param.key".value, param.size), parameterNumber: param.num, size: param.size)))
            cmds << response(encap(zwave.configurationV2.configurationGet(parameterNumber: param.num)))
    if (cmds) {
        runIn(10, "syncCheck")
    } else {
        runIn(1, "syncCheck")

private syncCheck() {
    logging("${device.displayName} - Executing syncCheck()","info")
    def failed = []
    def incorrect = []
    def notSynced = []
    parameterMap().each {
        if (state."$it.key"?.state == "incorrect" ) {
            incorrect << it
        } else if ( state."$it.key"?.state == "failed" ) {
            failed << it
        } else if ( state."$it.key"?.state in ["inProgress","notSynced"] ) {
            notSynced << it

    if (failed) {
        multiStatusEvent("Sync failed! Verify parameter: ${failed[0].num}", true, true)
    } else if (incorrect) {
        multiStatusEvent("Sync mismatch! Verify parameter: ${incorrect[0].num}", true, true)
    } else if (notSynced) {
        multiStatusEvent("Sync incomplete! Wake up the device again by pressing the tamper button.", true, true)
    } else {
        if (device.currentValue("multiStatus")?.contains("Sync")) { multiStatusEvent("Sync OK.", true, true) }

private multiStatusEvent(String statusValue, boolean force = false, boolean display = false) {
    if (!device.currentValue("multiStatus")?.contains("Sync") || device.currentValue("multiStatus") == "Sync OK." || force) {
        sendEvent(name: "multiStatus", value: statusValue, descriptionText: statusValue, displayed: display)

def zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpNotification cmd) {
    logging("${device.displayName} woke up", "info")
    def cmds = []
    sendEvent(descriptionText: "$device.displayName woke up", isStateChange: true)

    cmds << zwave.batteryV1.batteryGet()
    cmds << zwave.sensorMultilevelV5.sensorMultilevelGet()

def zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport cmd) {
    def paramKey = parameterMap().find( {it.num == cmd.parameterNumber } ).key
    logging("${device.displayName} - Parameter ${paramKey} value is ${cmd.scaledConfigurationValue} expected " + state."$paramKey".value, "info")
    state."$paramKey".state = (state."$paramKey".value == cmd.scaledConfigurationValue) ? "synced" : "incorrect"

def zwaveEvent(hubitat.zwave.commands.applicationstatusv1.ApplicationRejectedRequest cmd) {
    logging("${device.displayName} - rejected request!","warn")
    for ( param in parameterMap() ) {
        if ( state."$param.key"?.state == "inProgress" ) {
            state."$param.key"?.state = "failed"

def zwaveEvent(hubitat.zwave.commands.alarmv2.AlarmReport cmd) {
    logging("${device.displayName} - AlarmReport received, zwaveAlarmType: ${cmd.zwaveAlarmType}, zwaveAlarmEvent: ${cmd.zwaveAlarmEvent}", "info")
    def lastTime = new Date().format("yyyy MMM dd EEE h:mm:ss a", location.timeZone)
    switch (cmd.zwaveAlarmType) {
        case 2:
            switch (cmd.zwaveAlarmEvent) {
                case 0:
                    sendEvent(name: "carbonMonoxide", value: "clear");
                    multiStatusEvent("CO Clear - $lastTime");
                case 2:
                    sendEvent(name: "carbonMonoxide", value: "detected");
                    multiStatusEvent("CO Detected - $lastTime");
                case 3:
                    if ( cmd.numberOfEventParameters == 0 ) {
                        sendEvent(name: "carbonMonoxide", value: "tested");
                        multiStatusEvent("CO Tested - $lastTime");
                    } else if (cmd.numberOfEventParameters == 1 && cmd.eventParameter == [1]) {
                        sendEvent(name: "carbonMonoxide", value: "clear");
                        multiStatusEvent("CO Test OK - $lastTime");
        case 7:
            sendEvent(name: "tamper", value: (cmd.zwaveAlarmEvent == 3)? "detected":"clear");
            if (cmd.zwaveAlarmEvent == 3) { multiStatusEvent("Tamper - $lastTime") }
        case 4:
            if (device.currentValue("temperatureAlarm")?.value != null) {
                switch (cmd.zwaveAlarmEvent) {
                    case 0: sendEvent(name: "temperatureAlarm", value: "clear"); break;
                    case 2: sendEvent(name: "temperatureAlarm", value: "overheat"); break;
        default: logging("${device.displayName} - Unknown zwaveAlarmType: ${cmd.zwaveAlarmType}","warn");

def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
    logging("${device.displayName} - SensorMultilevelReport received, sensorType: ${cmd.sensorType}, scaledSensorValue: ${cmd.scaledSensorValue}", "info")
    switch (cmd.sensorType) {
        case 1:
            def cmdScale = cmd.scale == 1 ? "F" : "C"
            sendEvent(name: "temperature", unit: getTemperatureScale(), value: convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision), displayed: true)
        case 40:
            sendEvent(name: "coLevel", unit: "ppm", value: cmd.scaledSensorValue, displayed: true)
            logging("${device.displayName} - Unknown sensorType: ${cmd.sensorType}","warn")

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
    logging("${device.displayName} - BatteryReport received, value: ${cmd.batteryLevel}", "info")
    sendEvent(name: "battery", value: cmd.batteryLevel.toString(), unit: "%", displayed: true)

def parse(String description) {
    def result = []
    logging("${device.displayName} - Parsing: ${description}")
    if (description.startsWith("Err 106")) {
        result = createEvent(
                descriptionText: "Failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
                eventType: "ALERT",
                name: "secureInclusion",
                value: "failed",
                displayed: true,
    } else if (description == "updated") {
        return null
    } else {
        def cmd = zwave.parse(description, cmdVersions())
        if (cmd) {
            logging("${device.displayName} - Parsed: ${cmd}")

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
    def encapsulatedCommand = cmd.encapsulatedCommand(cmdVersions())
    if (encapsulatedCommand) {
        logging("${device.displayName} - Parsed SecurityMessageEncapsulation into: ${encapsulatedCommand}")
    } else {
        log.warn "Unable to extract secure cmd from $cmd"

def zwaveEvent(hubitat.zwave.commands.crc16encapv1.Crc16Encap cmd) {
    def version = cmdVersions()[cmd.commandClass as Integer]
    def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
    def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(
    if (encapsulatedCommand) {
        logging("${device.displayName} - Parsed Crc16Encap into: ${encapsulatedCommand}")
    } else {
        log.warn "Could not extract crc16 command from $cmd"

private logging(text, type = "debug") {
    if (settings.logging == "true") {
        log."$type" text

private secEncap(hubitat.zwave.Command cmd) {
    logging("${device.displayName} - encapsulating command using Secure Encapsulation, command: $cmd","info")

private crcEncap(hubitat.zwave.Command cmd) {
    logging("${device.displayName} - encapsulating command using CRC16 Encapsulation, command: $cmd","info")

private encap(hubitat.zwave.Command cmd) {
    if ("s")) {
    } else if ("56")){
    } else {
        logging("${device.displayName} - no encapsulation supported for command: $cmd","info")

private encapSequence(cmds, Integer delay=250) {
    delayBetween(cmds.collect{ encap(it) }, delay)

private List intToParam(Long value, Integer size = 1) {
    def result = []
    size.times {
        result =, (value & 0xFF) as Short)
        value = (value >> 8)
    return result

private Map cmdVersions() {
    [0x5E: 2, 0x59: 1, 0x73: 1, 0x80: 1, 0x22: 1, 0x56: 1, 0x31: 5, 0x98: 1, 0x7A: 3, 0x5A: 1, 0x85: 2, 0x84: 2, 0x71: 2, 0x70: 2, 0x8E: 2, 0x9C: 1, 0x86: 1, 0x72: 2]

private parameterMap() {[
        [key: "zwaveNotifications", num: 2, size: 1, type: "enum", options: [
                0: "Both actions disabled",
                1: "Tampering (opened casing)",
                2: "Exceeding the temperature",
                3: "Both actions enabled"
         def: "0", title: "Z-Wave notifications",
         descr: "This parameter allows to set actions which result in sending notifications to the HUB"],
        [key: "highTempTreshold", num: 22, size: 1, type: "enum", options: [
                50: "120 °F / 50°C",
                55: "130 °F / 55°C",
                60: "140 °F / 60 °C",
                65: "150 °F / 65 °C",
                71: "160 °F / 71 °C",
                77: "170 °F / 77 °C",
                80: "176 °F / 80 °C"
         def: "55", title: "Threshold of exceeding the temperature",
         descr: "This parameter defines the temperature level, which exceeding will result in sending actions set in paramater 2."]

Thank you very much! Ill check it out and let you know.

Tagging @bcopeland, as Bryan is always looking for a challenging Z-Wave device to write a Hubitat driver for...:wink:

1 Like

Well thats great to know.. really stoked now!

Morning :coffee: :coffee:


1 Like

It's just CO?

Hi Brian, yes its just Carbon Monoxide with sensor temperature. I have loaded it without any issues or errors.. just set up now to collect data.... see what happens when we put a pan on the gas hob..

Next I want to hook up analogue air velocity sensor to monitor flow through a duct.. looking around for a Z-wave module to do that. I would guess I might need some help with that too!

Good to meet you, bobbles and ogiewon !


I'd be happy to take a stab at it.. After proclaiming my boredom ... I actually have a backlog of devices now.. Not sure how that happened... I want to get finished with the devices that vendors have sent me first.. Then I have a few devices that people have let me know need devices..

1 Like

Thank you so much for the heads up.


Hi @bcopeland just in case you had not noticed I have posted the SmartThings driver above.
It may help you.

I have ordered a can of CO to check it out properly.. I don't want to poison the sensor. It will arrive early next week. I'll let you know the outcome for sure.

Thanks everyone who responded.


Hey @projects, any news on this?
I have the same co detector (Fibaro FGCD-001 ZW5 v3.2)
I currently use the generic z-wave CO/Smoke sensor driver, which looks like it works but has limited functionality and a smoke sensor parameter that isn't really there.
Does the driver from @bobbles work for you?
PS. you can test the meter by holding the B button , when the light goes on hold for 3 more seconds and release, should show in the Hub
PPS. the title states co2 which is a different thing, this is a CO [Carbon Monoxide] like you stated in the body text :smiley:

Hello Mr. Loma, Yes the driver Bobbles sent me works well. Yes the effect of the Test button does work. My CO gas in a can should arrive tomorrow. I'll make a posting on the result.


1 Like

thanks for the reply , I'll give it a shot.:+1:

Hello Mr. Loma, Bobbles and Brian,

CO test gas arrived today as expected. (I ordered it from Sleepsafe in the UK) Yes, Hubitat Elevation C5 triggered "detected" and reported the ppm value in the event log file. It took about 2-3 mins to detect the gas. BUT after gas had gone the PPM level remained in register at the detected level, (no good for graphing) It does not clear down, It needs a reset. But the driver certainly works. Not sure how the preferences should be set in the tiles for best results though!

Thanks again folks for your help.

Yeah mine seems to be working too.
I had to reconnect it, I think it was configured with the wrong driver.
It does have an entry for a ZigBee id which is a bit strange since it's a zwave device

For some reason it's stuck in unknown state on the dashboard tile. Does anyone know how to sync the state?
The temperature and battery seem to get synched every update tick