Driver request: Ecolink Firefighter (zigbee)

Consider returning it for the z-wave version:

https://www.amazon.com/Ecolink-Wireless-Detector-existing-FF-ZWAVE5-ECO/dp/B071Z8NM8N/

I know someone who spent time trying to get the zigbee version to work with zigbee2mqtt and then gave up.

1 Like

@mike.maxwell I factory reset it and I paired with smartthings and it works without adding a specific device handler for it. The sound is detected in the smartthings app. Do you know why?

I was not able to trigger the alarm using a real smoke detector. This isn't a software issue on the habitat end. So we're not going to put out a comparability acknowledgment for a device of this type that we can't verify.

Could I try your driver to see if I can get the event? I tried with a real smoke detector on my end. Maybe you got a defective device?

Maybe, but if that’s the explanation, then I got more than one defective device, since I tried Mike’s driver with a couple different ones I had purchased. So I think that’s unlikely.

1 Like

1 Like

I just tested it again with smartthings and it seems to work fine:

When did you purchase it? Maybe Ecolink updated the device's firmware recently.

I bought it this week. Maybe smartthings updated the firmware when I connected to it?

No. Maybe the manufacturer is now selling units with an updated firmware.

What could I do to help supporting the device on hubitat?

Maybe post firmware versions for someone who has a working one vs a non-working one?

Does Smartthings still publish their drivers? If so you could try to add the driver to Hubitat. There isn't a guarantee that it will work without doing some edits, sometimes you get lucky and the drivers just work.

1 Like

I have this for now. I will try to play with the driver that was post here in the thread

I got this error when trying the driver in this thread: groovy.lang.MissingPropertyException: No such property: ATTRIBUTE_IAS_ZONE_STATUS for class: com.hubitat.zigbee.Zigbee on line 156 (method configure) Anyone knows how to get around it, maybe @mike.maxwell ? Mike could you please check my previous post showing that the device works on smartthings. Any idea why it would work on smartthings but not hubitat?

Another thing I'm not getting any information in the parser (sound detection/battery status or temperature), is it normal or it's because I have the problem above? Do I need to register to something to get the events? This is my first experience with zigbee so I have no idea what I'm doing.

So I got everything working with no apparent error. I will publish the driver over the weekend :slightly_smiling_face:

5 Likes

Cool, happy to try it out.

Here we go... I just adapted the code from smartthings to work on hubitat (no credit for me :stuck_out_tongue:). Please let me know if it works for you too :slight_smile:


 /**
 *  Zigbee Sound Sensor
 *
 *  Copyright 2018 Samsung SRPOL
 *
 *  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.
 *
 */

import hubitat.zigbee.clusters.iaszone.ZoneStatus
import hubitat.zigbee.zcl.DataType

metadata {
	definition(name: "ZigBee Sound Sensor", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "x.com.st.d.siren") {
		capability "Battery"
		capability "Configuration"
		capability "Health Check"
		capability "Refresh"
		capability "Sensor"
		capability "Sound Sensor"
		capability "Temperature Measurement"

		fingerprint profileId: "0104", inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "Ecolink", model: "FFZB1-SM-ECO", deviceJoinName: "Ecolink Sound Sensor" //Ecolink Firefighter
	}
}

private getPOLL_CONTROL_CLUSTER() { 0x0020 }
private getFAST_POLL_TIMEOUT_ATTR() { 0x0003 }
private getCHECK_IN_INTERVAL_ATTR() { 0x0000 }
private getBATTERY_VOLTAGE_VALUE() { 0x0020 }
private getTEMPERATURE_MEASURE_VALUE() { 0x0000 }
private getSET_LONG_POLL_INTERVAL_CMD() { 0x02 }
private getSET_SHORT_POLL_INTERVAL_CMD() { 0x03 }
private getCHECK_IN_INTERVAL_CMD() { 0x00 }
private getATTRIBUTE_IAS_ZONE_STATUS() { 0x0002 }

def installed() {
	sendEvent(name: "sound", value: "not detected", displayed: false)
	response(refresh())
}

def parse(String description) {
	def map = zigbee.getEvent(description)
	if(!map) {
		if(isZoneMessage(description)) {
			map = parseIasMessage(description)
		} else {
			map = parseAttrMessage(description)
		}
	} else if (map.name == "temperature") {
		if (tempOffset) {
			map.value = new BigDecimal((map.value as float) + (tempOffset as float)).setScale(1, BigDecimal.ROUND_HALF_UP)
		}
		map.descriptionText = temperatureScale == 'C' ? "${device.displayName} was ${map.value}°C" : "${device.displayName} was ${map.value}°F"
		map.translatable = true
	}

	def result = map ? createEvent(map) : [:]

	if (description?.startsWith('enroll request')) {
		def cmds = zigbee.enrollResponse()
		log.debug "enroll response: ${cmds}"
		result = cmds?.collect { new hubitat.device.HubAction(it)}
	}
	return result
}

private Map parseIasMessage(String description) {
	ZoneStatus zs = zigbee.parseZoneStatus(description)
	def result = [:]
	if(zs.isAlarm1Set() || zs.isAlarm2Set()) {
		result = getSoundDetectionResult("detected")
	} else if(!zs.isTamperSet()) {
		result = getSoundDetectionResult("not detected")
	} else {
		result = [displayed: true, descriptionText: "${device.displayName}'s case is opened"]
        def cmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, BATTERY_VOLTAGE_VALUE)
		sendHubCommand(new hubitat.device.HubMultiAction(cmds, hubitat.device.Protocol.ZIGBEE))
	}

	return result
}

private Map parseAttrMessage(description) {
	def descMap = zigbee.parseDescriptionAsMap(description)
	def map = [:]
	if(descMap?.clusterInt == zigbee.POWER_CONFIGURATION_CLUSTER && descMap.commandInt != 0x07 && descMap?.value) {
		map = getBatteryPercentageResult(Integer.parseInt(descMap.value, 16))
	} else if(descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) {
		if (descMap.data[0] == "00") {
			sendCheckIntervalEvent()
		} else {
			log.warn "TEMP REPORTING CONFIG FAILED - error code: ${descMap.data[0]}"
		}
	} else if(descMap.clusterInt == POLL_CONTROL_CLUSTER && descMap.commandInt == CHECK_IN_INTERVAL_CMD) {
		sendCheckIntervalEvent()
	}

	return map
}

private Map getBatteryPercentageResult(rawValue) {
	def result = [:]
	def volts = rawValue / 10
	if (!(rawValue == 0 || rawValue == 255)) {
		def minVolts = 2.2
		def maxVolts = 3.0
		def pct = (volts - minVolts) / (maxVolts - minVolts)
		def roundedPct = Math.round(pct * 100)
		if (roundedPct <= 0) {
			roundedPct = 1
		}
		result.value = Math.min(100, roundedPct)
	}
	result.name = 'battery'
	result.translatable = true
	result.descriptionText = "${device.displayName} battery was ${result.value}%"
	return result
}

private Map getSoundDetectionResult(value) {
	def text = "Sound was ${value}"
	def result = [name: "sound", value: value, descriptionText: text, displayed: true]
	return result
}

private sendCheckIntervalEvent() {
	sendEvent(name: "checkInterval", value: 60 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID, offlinePingable: "1"])
}

def ping() {
	refresh()
}

def refresh() {
	return zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, BATTERY_VOLTAGE_VALUE) +
			zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, TEMPERATURE_MEASURE_VALUE) +
            zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, getATTRIBUTE_IAS_ZONE_STATUS())
			//zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS)
}

def configure() {
	sendCheckIntervalEvent()

	//send zone enroll response, configure short and long poll, fast poll timeout and check in interval
	def enrollCmds = (zigbee.command(POLL_CONTROL_CLUSTER, SET_LONG_POLL_INTERVAL_CMD, "B0040000") + zigbee.command(POLL_CONTROL_CLUSTER, SET_SHORT_POLL_INTERVAL_CMD, "0200") +
			zigbee.writeAttribute(POLL_CONTROL_CLUSTER, FAST_POLL_TIMEOUT_ATTR, DataType.UINT16, 0x0028) + zigbee.writeAttribute(POLL_CONTROL_CLUSTER, CHECK_IN_INTERVAL_ATTR, DataType.UINT32, 0x00001950))

	//send enroll commands, configures battery reporting to happen every 30 minutes, create binding for check in attribute so check ins will occur
    return zigbee.enrollResponse() + zigbee.configureReporting(zigbee.IAS_ZONE_CLUSTER, getATTRIBUTE_IAS_ZONE_STATUS(), DataType.BITMAP16, 30, 60 * 30, null) + zigbee.batteryConfig() + zigbee.temperatureConfig(60 * 30, 60 * 30 + 1) + zigbee.configureReporting(POLL_CONTROL_CLUSTER, CHECK_IN_INTERVAL_ATTR, DataType.UINT32, 0, 3600, null) + refresh() + enrollCmds
	//return zigbee.enrollResponse() + zigbee.configureReporting(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS, DataType.BITMAP16, 30, 60 * 30, null) + zigbee.batteryConfig(60 * 30, 60 * 30 + 1) + zigbee.temperatureConfig(60 * 30, 60 * 30 + 1) + zigbee.configureReporting(POLL_CONTROL_CLUSTER, CHECK_IN_INTERVAL_ATTR, DataType.UINT32, 0, 3600, null) + refresh() + enrollCmds
}

private boolean isZoneMessage(description) {
	return (description?.startsWith('zone status') || description?.startsWith('zone report'))
}
1 Like

Sorry took me a while to get around to trying this driver. I’m seeing the same behavior as before, ie it doesn’t seem to do anything in response to smoke or CO alarm tones :confused:.

1 Like

Weird... for me it works fine. I guess they updated something in the device.