Heiman Smart Smoke Sensor HS1SA Zigbee

Hi, does anybody know if there is a driver for the Heiman smart smoke sensor (Zigbee version). I have no Z-Wave devices and a good Zigbee mesh so I would like to get a few of these if they compatible with HE.

http://www.heimantech.com/product/?type=detail&id=3

Thanks

Search is your friend.

Hi at9

I did find that but I thought it was about the gas detector, did not see it was for smoke as well.

I've played around with using the Zigbee Smoke Detector driver by veeceeoh.
My Heiman sensor is the HS1SA-E

It does detect the alarm going off and when I press the test button I get a message logged saying that smoke was detected and that's then followed by an All Clear message - so that's good.

However, there are no periodic All Clear messages and no battery level messages. My issue with that is that the device has no activity with the hub so the Last Activity date is whenever the device was last manually tested.

I have played around with the driver (as much as I can) and I can't find a way to trigger these messages - the device does not seem to be sending them of it's own accord as otherwise I would have had a parse message logged.

I also have the Heiman Gas Sensor, it sends periodic All Clear messages.
It's mains powered so battery is not a consideration.

Hope that helps.

1 Like

Yes , I think it does need to have some periodic activity with the hub, just so you know it's "working". battery level or an all clear would be good. Otherwise it leaves you guessing if it is actually going to work one day when you not at home maybe.

I've sent an email to Heiman asking for details. They responded straight away asking where I purchased my units (Amazon). Lets see if they respond again now with anything useful.

1 Like

Heiman didn't help, they referred me to the Amazon reseller I purchased from - he had no data. So I got a zniffer and got zniffing. I'm pleased to announce that I now have a basic working driver.

It sends a status report every hour and a battery report every 2 hours. If you press the test button on the device it will send a smoke alert followed by an all clear alert.

I've tried to get it to take a new configuration without re-pairing, I've tried to get it to do a test function from the driver - nope. Once it has it's configuration at pairing time it seems to go to sleep and wake up to send reports.

/**
 * Heiman (and maybe frient) Zigbee Smoke Detector
 *
 *  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.
 *
 *   Original DTH on SmartThings by cuboy29
 *       Converted to Hubitat and extensivly modified by Scruffy-SJB
 *
 *   Code snippets copied from veeceeoh, Daniel Terryn and Marcus Liljergren - with thanks.
 */

// Ver: 2.0 - Parsed another unhandled battery catch-all
// Ver: 1.9 - Added fingerprint for frient Intelligent Smoke Alarm
// Ver: 1.8 - Added fingerprint for model SmokeSensor-EF-3.0  Enhanced status message processing.  New Current State - Date/Time last status recorded.
// Ver: 1.7 - Added fingerprint for model HS1CA-M HEIMAN Smart Carbon Monoxide Sensor
// Ver: 1.6 - Added fingerprint for model HS1SA-M
// Ver: 1.5 - Added ability to detect a device test and to trigger a last tested event
// Ver: 1.4 - Parsed another unhandled catch-all
// Ver: 1.3 - Updated to support Zigbee 3.0 version HS1SA-E

import hubitat.zigbee.clusters.iaszone.ZoneStatus
 
metadata {
	definition (name: "Heiman Zigbee Smoke Detector", namespace: "scruffy-sjb", author: "scruffy-sjb and cuboy29") {
		
        capability "Configuration"
        capability "Smoke Detector"
        capability "SmokeDetector"
        capability "Sensor"
        capability "Refresh"
        capability "Battery"
        
		command "resetToClear"
        command "resetBatteryReplacedDate"
        
        attribute "smoke", "string"
        attribute "batteryLastReplaced", "string"
        attribute "sensorLastTested", "string"
        attribute "lastStatus", "string"
          
        fingerprint profileID: "0104", deviceID: "0402", inClusters: "0000,0001,0003,0500,0502", outClusters: "0019", manufacturer: "HEIMAN", model: "SmokeSensor-EM", deviceJoinName: "HEIMAN Smoke Detector" //HEIMAN Smoke Sensor (HS1SA-E)
        fingerprint profileID: "0104", deviceID: "0402", inClusters: "0000,0003,0500,0001,0009,0502", outClusters: "0019", manufacturer: "HEIMAN", model: "SMOK_V16", deviceJoinName: "HEIMAN Smoke Detector M" //HEIMAN Smoke Sensor (HS1SA-M)
        fingerprint profileID: "0104", deviceID: "0402", inClusters: "0000,0003,0001,0500,0502,0B05", outClusters: "0019", manufacturer: "HEIMAN", model: "SmokeSensor-N-3.0", deviceJoinName: "HEIMAN Smoke Detector 3.0" //HEIMAN Smoke Sensor (HS1SA-E)
        fingerprint profileID: "0104", deviceID: "0402", inClusters: "0000,0001,0003,0500", manufacturer: "HEIMAN", model: "COSensor-EM", deviceJoinName: "HEIMAN CO Sensor" //HEIMAN Smart Carbon Monoxide Sensor (HS1CA-E)
        fingerprint profileID: "0104", deviceID: "0402", inClusters: "0000,0001,0003,0500,0502,0B05", outClusters: "0019", manufacturer: "HEIMAN", model: "SmokeSensor-EF-3.0", deviceJoinName: "HEIMAN Smoke Detector" //HEIMAN Smoke Sensor 
        fingerprint profileId: "0104", deviceId: "0402", inClusters: "0000,0001,0003,000F,0020,0500,0502", outClusters: "000A,0019", manufacturer: "frient A/S", model :"SMSZB-120", deviceJoinName: "frient Smoke Detector" // frient Intelligent Smoke Alarm
    }
}

def SensorTestOptions = [:]
	SensorTestOptions << ["1" : "Yes"] // 0x01
	SensorTestOptions << ["0" : "No"]  // 0x00

preferences {
	input "SensorTest", "enum", title: "Enable Sensor Testing", options: SensorTestOptions, description: "Default: Yes", required: false, displayDuringSetup: true
}        

def parse(String description) {
    def descMap = [:]
    
	if (description?.startsWith('zone status')) {
			descMap = parseIasMessage(description)
    }else if (description?.startsWith('enroll request')) {
		    List cmds = zigbee.enrollResponse()
		    descMap = cmds?.collect { new hubitat.device.HubAction(it) }
	}else if (description?.startsWith('catchall')) {
            descMap = parseCatchAllMessage(description)
    }else if (description?.startsWith('read attr'))  {  
            descMap = zigbee.parseDescriptionAsMap(description)
            if ((descMap?.cluster == "0500" && descMap.attrInt == 0x0001) && (descMap.value == '0028')){  //Zone Type
                log.debug "Zone Type is Fire Sensor"
			}else if ((descMap?.cluster == "0500" && descMap.attrInt == 0x0000) && (descMap.value == '01')){  //Zone State
                log.debug "Zone State is enrolled"
			}else if ((descMap?.cluster == "0500" && descMap.attrInt == 0x0002) && ((descMap.value == '20') || (descMap.value == '0020'))){  //Zone Status Clear
                SmokeOrClear("clear")    
			}else if ((descMap?.cluster == "0500" && descMap.attrInt == 0x0002) && ((descMap.value == '30') || (descMap.value == '0030'))){  //Zone Status Clear
                SmokeOrClear("clear") 
			}else if ((descMap?.cluster == "0500" && descMap.attrInt == 0x0002) && ((descMap.value == '0031') || (descMap.value == '0021'))){  //Zone Status Smoke
                SmokeOrClear("detected")              
			}else if ((descMap?.cluster == "0502" && descMap.attrInt == 0x0000)){  //Alarm Max Duration
                def int alarmMinutes = Integer.parseInt(descMap.value,16) 
                log.debug "Max Alarm Duration is ${alarmMinutes} seconds"              
			}else if ((descMap?.cluster == "0000" && descMap.attrInt == 0x0007) && ((descMap.value == '03') )){  //PowerSource
                log.debug "${device.displayName} is Battery Powered"    
			}else if ((descMap?.cluster == "0001" && descMap.attrInt == 0x0020)) {  //Battery Voltage
                def batteryVoltage = ConvertHexValue(descMap.value)
                handleBatteryEvent(batteryVoltage)
			}else if ((descMap?.cluster == "0001" && descMap.attrInt == 0x0033)) {  //Battery Cells
                def batteryCells = ConvertHexValue(descMap.value)
                handleCellsEvent(batteryCells)   
            }else if ((descMap?.cluster == "0001" && descMap.attrInt == 0x0021)) {  //Battery Percentage Remaining
                                                                                    //  -- Ignore Attribute                                                             
            }else if (descMap?.cluster == "0000" && descMap.attrInt == 0x0004){  //Manufacture
                sendEvent(name: "manufacture", value: descMap.value)
                log.debug "Manufacturer is ${descMap.value}"
            }else if (descMap?.cluster == "0000" && descMap.attrInt == 0x0005){  //Model 
                sendEvent(name: "model", value: descMap.value)
                log.debug "Model is ${descMap.value}"
            }else {log.debug "Unknown --> Cluster-> ${descMap?.cluster}  AttrInt-> ${descMap.attrInt}  Value-> ${descMap.value}"
            }
       // log.debug "Cluster-> ${descMap?.cluster}  AttrInt-> ${descMap.attrInt}  Value-> ${descMap.value}"
    }else { 
        log.debug "Unparsed -> $description" 
        descMap = zigbee.parseDescriptionAsMap(description)
    }
    // log.debug "$descMap"
	return descMap   
}    

private parseCatchAllMessage(String description) {
    
    Map resultMap = [:]
    def descMap = zigbee.parse(description)  
    if (shouldProcessMessage(descMap)) {
        log.debug descMap.inspect()               
    }
    return resultMap
}

private boolean shouldProcessMessage(cluster) {
    // 0x0B is default response indicating message got through
    // 0x07 is bind message
    // 0x04 - No Idea !!!!!
    boolean ignoredMessage = cluster.profileId != 0x0104 || 
        cluster.command == 0x0B ||
        cluster.command == 0x07 ||
        cluster.command == 0x04 ||        
        (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
    return !ignoredMessage
}

def refresh() {
	log.debug "Refreshing..."
	def refreshCmds = []
    
    refreshCmds +=
	zigbee.readAttribute(0x0500, 0x0001) +	   // IAS ZoneType
    zigbee.readAttribute(0x0500, 0x0000) +	   // IAS ZoneState
    zigbee.readAttribute(0x0500, 0x0002) +	   // IAS ZoneStatus
    zigbee.readAttribute(0x0502, 0x0000) +	   // Alarm Max Duration
    zigbee.readAttribute(0x0000, 0x0007) +	   // Power Source
    zigbee.readAttribute(0x0001, 0x0020) +     // Battery Voltage      
    zigbee.readAttribute(0x0001, 0x0033) +     // Battery Cells         
    zigbee.readAttribute(0x0000, 0x0004) +	   // Manufacturer Name
    zigbee.readAttribute(0x0000, 0x0005) +	   // Model Indentification
    zigbee.enrollResponse()
    
	return refreshCmds
}

def configure() {
    log.debug "Configuring..."
    
	if (!device.currentState('batteryLastReplaced')?.value)
		resetBatteryReplacedDate(true)
    
    def cmds = [
        // Bindings
        "zdo bind 0x${device.deviceNetworkId} 1 1 0x0500 {${device.zigbeeId}} {}", "delay 200"
        ] 
        // cmds += zigbee.enrollResponse(1200) 
        cmds += zigbee.enrollResponse() 
        cmds += zigbee.configureReporting(0x0500, 0x0002, 0x19, 0, 3600, 0x00)
        cmds += zigbee.configureReporting(0x0001, 0x0020, 0x20, 600, 7200, 0x01) 
        return cmds + refresh()
}

def resetBatteryReplacedDate(paired) {
	def newlyPaired = paired ? " for newly paired sensor" : ""
	sendEvent(name: "batteryLastReplaced", value: new Date())
	log.debug "${device.displayName} Setting Battery Last Replaced to Current date${newlyPaired}"
}

def resetSensorTestedDate() {
    def newlyTested=""
    sendEvent(name: "sensorLastTested", value: new Date())
    log.debug "${device.displayName} Setting Sensor Last Tested to Current date${newlyTested}"
}

def resetToClear() {
	sendEvent(name:"smoke", value:"clear")
    sendEvent(name: "lastStatus", value: new Date(), displayed: True)
    log.debug "Resetting to Clear..."
	didWeGetClear = 0
}

/**
 * Code borrowed (mixed and matched) from both Daniel Terryn and veeceeoh
 *
 * Create battery event from reported battery voltage.
 *
 */

private handleBatteryEvent(rawVolts) {
    rawVolts = rawVolts / 10.0
	def minVolts = voltsmin ? voltsmin : 2.5
	def maxVolts = voltsmax ? voltsmax : 3.0
	def pct = (rawVolts - minVolts) / (maxVolts - minVolts)
	def roundedPct = Math.min(100, Math.round(pct * 100))
	log.debug "Battery level is ${roundedPct}% (${rawVolts} Volts)"
	
    sendEvent(name:"battery", value: roundedPct, unit: "%", isStateChange: true)
	return 
}

private handleCellsEvent(noCells) {
	log.debug "Battery reports that it has (${noCells} Cells)"
	return 
}

def ConvertHexValue(value) {
	if (value != null)
	{
		return Math.round(Integer.parseInt(value, 16))
	}
}

def SmokeOrClear(value) {
    if (value == "clear") {
        sendEvent(name:"smoke", value:"clear", isStateChange: true)
        log.info "${device.displayName} reports status is all clear"
    } else {
        sendEvent(name:"smoke", value:"detected", isStateChange: true)
        log.info "${device.displayName} reports smoke is detected"
    }
    
    sendEvent(name: "lastStatus", value: new Date(), displayed: True)
}

private Map parseIasMessage(String description) {
    // log.debug "Zone Status Received--> ${description}"
    ZoneStatus zs = zigbee.parseZoneStatus(description)    
    translateZoneStatus(zs)    
}

private Map translateZoneStatus(ZoneStatus zs) {
	// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion       
	return getSmokeResult(zs.isAlarm1Set() || zs.isAlarm2Set())
}

private Map getSmokeResult(value) {
	if (value) {
        if (SensorTest == "") {
            SensorTest = "1"    // Default is Yes
        }
        
        if (SensorTest == "1") {
    		def descriptionText = "${device.displayName} status is pending"
            sendEvent(name: "lastStatus", value: new Date(), displayed: True)
	    	log.debug descriptionText
		    runIn(3,EventOrTest)
		    return [
			    name			: 'smoke',
			    value			: 'pending',
                isStateChange: false,
			    descriptionText : descriptionText
            ]
        } else {
    		def descriptionText = "${device.displayName} testing disabled - smoke detected !!!"
	    	log.debug descriptionText
            sendEvent(name: "lastStatus", value: new Date(), displayed: True)
		    return [
			    name			: 'smoke',
			    value			: 'detected',
                isStateChange: true,
			    descriptionText : descriptionText
            ]
        }                 
   } else {
		def descriptionText = "${device.displayName} all clear"
		log.debug descriptionText
        sendEvent(name: "lastStatus", value: new Date(), displayed: True)
		return [
			name			: 'smoke',
			value			: 'clear',
            isStateChange: true,
			descriptionText : descriptionText
		]
	}
}		

def EventOrTest() {
    if (device.currentValue("smoke") == "clear") {
		log.debug "${device.displayName} was tested sucessfully"
        resetSensorTestedDate()
	} else {
		log.debug "${device.displayName} This was not a test - smoke detected !!!"
		sendEvent(name:"smoke", value:"detected")
        sendEvent(name: "lastStatus", value: new Date(), displayed: True)
	}
}
3 Likes

I've pasted a minor edit to the above code.... to remove a catchall that appeared earlier today....

Okay, thanks have updated. Don't have the device yet but looking to get soon.

I have a Heiman smoke detector


and I installed your driver.
The device is recognized as a Heiman SmokeSensor-N-3.0
I set a simple rule (if smoke detected then speak) and it works.
The dashboard tile change from Clear to Detected but (?) the Clear message replaces the Detected message right away after.
I made this test by clicking on the side button of the Heiman detector, NOT having a real fire/smoke...
So for me the test can be considered as good (will post something after a real smoke)
2 Likes

Sounds like all it working as expected. Very good.

When you press the test button on the device it sends the Smoke Detected signal immediately followed by the All Clear signal - that's what my units all do too. So that's expected behavior.

Many thanks.

1 Like

I'm about to get rid off my fibaro smoke sensor and buy this one.

But the question is: does this sensor produce false alarms? The fibaro sensor few days ago produced 4 false alarm in 2 hours...

I have mine a couple of months (even though I only got the driver done a week or so ago) - I've had 3 of them up on the basis that even if they are not talking properly to the system they would cause a racket if they detected smoke. No false alarms.

Hi @simon

Do you have any of the Heiman Z-Wave smoke detectors. I am using the generic z-wave smoke detector driver, but I am not seeing anything when I press the test button, I do get the battery level and the clear , but nothing on testing?

I have 3 Heiman smoke detectors and your code is not detecting the self test. It does is state clear but not smoke.

I had them paired with zigbee2mqtt and they do send periodic updates.

Mine does have a slightly different fingerprint then what is listed in your driver. I posted below.

endpointId: 01
application: 14
softwareBuild: 2000-0001
inClusters: 0000,0003,0001,0500,0502,0B05
outClusters: 0019
model: SmokeSensor-N-3.0
manufacturer: HEIMAN

    fingerprint profileId: "0104", deviceId: "0402", inClusters: "0000,0003,0001,0500,0502,0B05", outClusters: "0019", manufacturer: "HEIMAN", model: "SmokeSensor-N-3.0", deviceJoinName: "HEIMAN Smoke Sensor (HS3SA)"

ID: D138
Manufacturer: HEIMAN
Product Name:
Model Number: SmokeSensor-N-3.0
deviceTypeId: 113
manufacturer : HEIMAN
idAsInt : 1
inClusters : 0000,0003,0001,0500,0502,0B05
endpointId : 01
profileId : 0104
application : 14
outClusters : 0019
initialized : true
model : SmokeSensor-N-3.0
stage : 4

Nope. I only have the Zigbee ones. Sorry about that.

Your sensor seems to be a slightly different model from the one I have. HS1SA-E is my model and on self-test I get both Smoke and Clear. I had to do some zniffing to get the packets it sent so that I got them right. You should be getting a catch-all message into you log for any messages the driver gets that it does not know how to process - if I can see from that what's going on I should be able to do something about it.

Did you get a chance to see if you are getting a catch-all message into your logs?

All I get is the following when I press the self test button on the sensor

2020-08-06 07:19:28.399 pm [debug]Smoke Sensor 2 all clear

Started over in case I made an error, got the following during pairing process.

sys:12020-08-06 10:21:35.637 pm infoZigbee Discovery Stopped
dev:17772020-08-06 10:21:09.643 pm debugSmoke Sensor 2 reports status is all clear
dev:5172020-08-06 10:21:07.135 pm infoMotion Family Room temperature is 77.80°F
dev:17772020-08-06 10:21:06.664 pm debugZone State is enrolled
dev:17772020-08-06 10:21:05.928 pm debugZone Type is Fire Sensor
dev:17772020-08-06 10:21:02.458 pm debugUnknown --> Cluster-> 0001 AttrInt-> 33 Value-> 7E
sys:12020-08-06 10:20:53.734 pm infoCreated Zigbee Device Heiman Zigbee Smoke Detector
dev:17772020-08-06 10:20:53.704 pm debugRefreshing...
dev:17772020-08-06 10:20:53.695 pm debugSetting Battery Last Replaced to current date for newly paired sensor
dev:17772020-08-06 10:20:53.672 pm debugConfiguring...
sys:12020-08-06 10:20:46.501 pm infoInitializing Zigbee Device 00158D000292E776, 9BB8
sys:12020-08-06 10:20:35.634 pm infoZigbee Discovery Running