Heiman Smart Smoke Sensor HS1SA Zigbee

Did the configure within 2 seconds of selftest will see what happens.

No periodic all clear messages either, neither in log or events.

Will try repair on second sensor as it isn't logging battery events.

Update: 9/5 Above actions made no difference still no battery or all clear events

I have the same problem. No battery info, no any other info. Only test smoke when I press the test button.

Are you getting any catch-all (that is, unparsed) messages in the log ????

Hey @simon

I just received 5 of these direct from Heiman that I 'd forgotten about (they took so long to arrive!!)

Installed your driver, put one into pair mode and it showed up instantly :slight_smile:
It reports battery every 4 hours 8 minutes (and a few seconds)

I have a driver update suggestion...

Event "Test Button Pressed"
The test button sends the "Smoke Detected" followed by "All Clear" a second or so later.
On receiving the Smoke message, start a delay (maybe 2 - 3 seconds)
If "All Clear" received, set event "Test Button Pressed" True
If delay times out without All Clear message, set Smoke event True.

I can do this in RM, obviously, but it won't work in HSM. Needs to be in the driver to prevent HSM going into full panic mode when you press the test button....

Karyn

Now that's a very good idea !!!!

I'll have a look at that....

New version..........

Based on @karyn.andrews suggestion I have added logic to detect a test event as opposed to a real smoke event. You have an input option to enable or disable this functionality. The driver now keeps a record of when the sensor was last tested... I think that's a very useful enhancement. Thanks Karyn.

I do need to give this a test with some real smoke.... I'll get to that shortly.

/**
 * Heiman 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: 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: "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"
          
        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,0001,0500,0502,0B05", outClusters: "0019", manufacturer: "HEIMAN", model: "SmokeSensor-N-3.0", deviceJoinName: "HEIMAN Smoke Detector 3.0" //HEIMAN Smoke Sensor (HS1SA-E)
    }
}

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") 
                // log.debug "${device.displayName} reports status is all clear"
			}else if ((descMap?.cluster == "0500" && descMap.attrInt == 0x0002) && ((descMap.value == '0031') || (descMap.value == '0021'))){  //Zone Status Smoke
                SmokeOrClear("detected") 
                // log.debug "${device.displayName} reports status is smoke"                
			}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 == 0x0021)) {  //Battery Cells
                def batteryCells = ConvertHexValue(descMap.value)
                handleCellsEvent(batteryCells)                
            }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}"
	}
	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",
        ] +  zigbee.enrollResponse(1200) + zigbee.configureReporting(0x0500, 0x0002, 0x19, 60, 3600, 0x01) + "delay 200" + 
        zigbee.configureReporting(0x0001, 0x0020, 0x20, 600, 7200, 0x01) + refresh()
    
    return cmds 
}

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")
    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) {
}

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"
	    	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
		    return [
			    name			: 'smoke',
			    value			: 'detected',
                isStateChange: true,
			    descriptionText : descriptionText
            ]
        }                 
   } else {
		def descriptionText = "${device.displayName} all clear"
		log.debug descriptionText
		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")
	}
}
2 Likes

Wow, that was quick!!

I'll upgrade it now but I can't test until tomorrow as my husband has just gone to bed.. He won't be a happy bunny if he's woken up by the smoke alarm :smiley:

Thank you

No, I don't get any raw messages in the log. Only when I press the button I get smoke and clear. Nothing else.

@simon Another Heiman Zigbee smoke detector model to add to the driver:
fingerprint profileID: "0104", deviceID: "0402", inClusters: "0000,0003,0500,0001,0009,0502", outClusters:"0019", model:"SMOK_V16", manufacturer:"Heiman", deviceJoinName: "HEIMAN Smoke Detector M" // HEIMAN Smoke Sensor (H1S1A-M)

Test button triggers testing event, not tried with actual smoke yet

Thanks - will add it.

I tried it today with real smoke and the detector beeped but the sensor on Hubitat did not work.

This is my device

INCOMPLETE - DO NOT SUBMIT THIS - TRY AGAIN: fingerprint model:"SmokeSensor-N-3.0", manufacturer:"HEIMAN", profileId:"0104", endpointId:"01", inClusters:"0000,0003,0001,0500,0502,0B05", outClusters:"0019", application:"14"

You should try re-pairing because Iā€™m pretty sure that this is trying to tell you that the device is not paired correctly.

1 Like

M
Can you double check your driver please. I do not recognize the INCOMPLETE... message.

The incomplete message was using the zigbee generic tool box, to show the information about the sensor.
I'm using your driver posted on message #51
I will try to unpair and pair it again

I'd like to update my status about my HS1SA-E model.
My wife burned some cheese in the oven and the smoke sensor instantly beeped. But on Hubitat I have no info about that.

But I reset that sensor and pair it again and it works. I get all events in the log. So after you change the driver from the code you need to pair the device once again.

Just reporting that this driver also works very nicely with the Heiman Smart Carbon Monoxide sensor, model : HS1CA-E
Thanks. :pray:

1 Like

If you can give me the Fingerprint I will add it to the driver so that it will be discovered correctly.

Many Thanks

1 Like

Here you go:

Heiman Smart Carbon Monoxide Sensor
Model:HS1CA-E
Zigbee HA1.2

ID: 6AC1
Manufacturer: HEIMAN
Product Name:
Model Number: COSensor-EM
deviceTypeId: 103
manufacturer : HEIMAN
idAsInt : 1
inClusters : 0000,0001,0003,0500
endpointId : 01
profileId : 0104
application : 11
outClusters :
initialized : true
model : COSensor-EM
stage : 4

Thank you

Hi.
This device handler work for me, then there is smoke or the test button is pressed I'll get an event.
But there is no status updates between I'm manuel testing or smoke is detected.

I'm have a diffrent version names Heiman SmokeSensor-EF-3.0.
What can I do to her status updates og battery level reported frequently.

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

Christian