/*
*
*/
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
import physicalgraph.zigbee.zcl.DataType
metadata {
definition (name: “SMSZB-120 third test”, namespace: “NorxNor”, author: “Norx”, ocfDeviceType: “x.com.st.d.sensor.smoke”, vid: “generic-smoke”, genericHandler: “Zigbee”) {
capability “Smoke Detector”
capability “Sensor”
capability “Battery”
capability “Alarm”
capability “Switch”
capability “Configuration”
capability “Temperature Measurement”
capability “Refresh”
capability “Health Check”
command "turnOnTestAlarm"
command "turnOffTestAlarm"
fingerprint profileId: "0104", deviceId: "0402", inClusters: "0000,0001,0003,000F,0020,0500,0502", outClusters: "0019", manufacturer: "Develco Products A/S", model: "SMSZB-120", deviceJoinName: "SmokeAlarm"
}
tiles {
multiAttributeTile(name:"smoke", type: "lighting", width: 6, height: 4) {
tileAttribute ("device.smoke", key: "PRIMARY_CONTROL") {
attributeState("clear", label: "clear", icon: "st.alarm.smoke.clear", backgroundColor: "#ffffff")
attributeState("detected", label: "Smoke!", icon: "st.alarm.smoke.smoke", backgroundColor: "#e86d13")
}
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label: '${currentValue}% battery', unit: ""
}
valueTile("temperature", "device.temperature", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "temperature", label:'${currentValue}°C', unit:"C",
backgroundColors: [
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 28, color: "#f1d801"],
[value: 35, color: "#d04e00"],
[value: 37, color: "#bc2323"]
]
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", action: "refresh.refresh", icon: "st.secondary.refresh"
}
standardTile("batteryStatus", "device.batteryStatus", decoration: "flat", inactiveLabel: false,width: 2, height: 2 ) {
state "batteryOK", label:'Battery OK' , backgroundColor: "#44b621"
state "batteryLOW", label:'Battery <30 days left' , backgroundColor: "#f1d801"
}
standardTile("testStatus", "device.testStatus", decoration: "flat", inactiveLabel: false,width: 2, height: 2 ) {
state "false", label:'Testbutton ${currentValue}', action:'turnOnTestAlarm', backgroundColor: "#44b621"
state "true", label:'Testbutton ${currentValue}', action:'turnOffTestAlarm',backgroundColor: "#f1d801"
}
standardTile("alarm", "device.alarm", width: 2, height: 2) {
state "off", label:'off', action:'alarm.siren', icon:"st.secondary.siren", backgroundColor:"#ffffff"
state "siren", label:'siren', action:'alarm.off', icon:"st.secondary.siren", backgroundColor:"#e86d13"
}
main "smoke"
details(["smoke", "battery", "temperature","batteryStatus","alarm","testStatus","refresh"])
}
// input name: "text", type: "text", title: "Text", description: "Enter Text", required: true
preferences {
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
}
}
private getDEFAULT_MAX_DURATION() { 0x00B4 }
private getDEFAULT_DURATION() { 0xFFFE }
private getIAS_WD_CLUSTER() { 0x0502 }
private getATTRIBUTE_IAS_WD_MAXDURATION() { 0x0000 }
private getATTRIBUTE_IAS_ZONE_STATUS() { 0x0002 }
private getCOMMAND_IAS_WD_START_WARNING() { 0x00 }
private getCOMMAND_DEFAULT_RESPONSE() { 0x0B }
def turnOffAlarmTile(){
sendEvent(name: “alarm”, value: “off”)
sendEvent(name: “switch”, value: “off”)
}
def turnOnAlarmTile(){
sendEvent(name: “alarm”, value: “siren”)
sendEvent(name: “switch”, value: “on”)
}
def turnOnAlarmTestTile(){
sendEvent(name: “alarm”, value: “siren”)
sendEvent(name: “switch”, value: “on”)
sendEvent(name: “smoke”, value: “detected”)
}
def installed(){
log.debug “installed”
state.maxDuration = DEFAULT_MAX_DURATION
turnOffAlarmTile()
response(refresh())
}
def parse(String description) {
log.debug “description(): $description”
def list = []
def map = [:]
def maps = zigbee.getEvent(description)
if (!maps) {
if (description?.startsWith('zone status 0x0030')) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
map = getDetectedZoneStatus2Map("smoke", zs.isAlarm1Set(), "detected", "clear", "smoke")
list.add(map ? createEvent(map) : [:] )
map = getDetectedZoneStatus2Map("batteryStatus", zs.isBatterySet(), "batteryLOW", "batteryOK", "battery condition")
list.add(map ? createEvent(map) : [:] )
map = getDetectedZoneStatus2Map("testStatus", zs.isTestSet(), "true", "false", "test status")
list.add(map ? createEvent(map) : [:] )
log.trace "zone status 0x0030 : $list"
/* def status = [
status: state.armed ? (state.stay ? "armed stay" : "armed away") : "disarmed",
alarms: state.alarms
] */
}
else if (description?.startsWith('zone status 0x031')) {
log.warn "zone status 0x031 : Smoke Alarm Detected..."
//ZoneStatus zs = zigbee.parseZoneStatus(description)
map = getDetectedZoneStatus2Map("smoke", true, "detected", "detected", "smoke")
list.add(map ? createEvent(map) : [:] )
log.trace "zone status 0x0031 : $list"
}
else if (description?.startsWith('zone status 0x0130')) {
log.warn "zone status 0x0130 : not able to parse at the moment. So read again..."
ZoneStatus zs = zigbee.parseZoneStatus(description)
map = getDetectedZoneStatus2Map("testStatus", zs.isTestSet(), "true", "false", "test status")
list.add(map ? createEvent(map) : [:] )
// def rStatuscmd = []
// rStatuscmd = zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS,[destEndpoint: 0x23])
// list.add(rStatuscmd?.collect { new physicalgraph.device.HubAction(it) } )
log.trace "zone status 0x0030 : $list"
}
else {
list = parseAttrMessage(description)
//log.trace "parseAttrMessage maps $maps"
}
}
else if (description?.startsWith('enroll request')) {
List cmds = zigbee.enrollResponse()
log.debug "enroll response: ${cmds}"
list.add(cmds?.collect { new physicalgraph.device.HubAction(it) } )
}
else {
list.add(maps ? createEvent(maps) : [:])
}
//log.debug "Parse returned: $maps"
log.debug "Parse Results : $list"
return list
}
def parseAttrMessage(String description){
def descMap = zigbee.parseDescriptionAsMap(description)
def map = [:]
def list = []
if (descMap?.clusterInt == zigbee.POWER_CONFIGURATION_CLUSTER && descMap.commandInt != 0x07 && descMap.value) {
map = [getBatteryPercentageResult(Integer.parseInt(descMap.value, 16))]
list.add(map ? createEvent(map) : [:] )
}
else if (descMap?.clusterInt == zigbee.IAS_ZONE_CLUSTER && descMap.attrInt == zigbee.ATTRIBUTE_IAS_ZONE_STATUS) {
def zs = new ZoneStatus(zigbee.convertToInt(descMap.value, 16))
map = getDetectedZoneStatus2Map("smoke", zs.isAlarm1Set(), "detected", "clear", "smoke")
list.add(map ? createEvent(map) : [:] )
map = getDetectedZoneStatus2Map("batteryStatus", zs.isBatterySet(), "batteryLOW", "batteryOK", "battery condition")
list.add(map ? createEvent(map) : [:] )
map = getDetectedZoneStatus2Map("testStatus", zs.isTestSet(), "true", "false", "test status")
list.add(map ? createEvent(map) : [:] )
}
else if (descMap?.clusterInt == IAS_WD_CLUSTER) {
def data = descMap.data
Integer parsedAttribute = descMap.attrInt
Integer command = Integer.parseInt(descMap.command, 16)
if (parsedAttribute == ATTRIBUTE_IAS_WD_MAXDURATION && descMap?.value) {
state.maxDuration = Integer.parseInt(descMap.value, 16)
} else if (command == COMMAND_DEFAULT_RESPONSE) {
Boolean isSuccess = Integer.parseInt(data[-1], 16) == 0
Integer receivedCommand = Integer.parseInt(data[-2], 16)
if (receivedCommand == COMMAND_IAS_WD_START_WARNING && isSuccess){
if(state.alarmOn){
turnOnAlarmTile()
runIn(state.lastDuration, turnOffAlarmTile)
} else {
turnOffAlarmTile()
}
}
}
}
else if (description?.startsWith('catchall')) {
if (Integer.parseInt(descMap.command, 16) == 0x01) {
log.trace "read Attribute Reponse: $descMap"
}
else {log.error "not defined parse : $descMap" }
}
//log.trace "retur from parseattr : $list"
return list;
}
private Map getBatteryPercentageResult(rawValue) {
if (rawValue > 32) {rawValue =32} //Sometimes Value is to high for Normal Voltage, and we don’t want battery to be 105%
log.debug “Battery Percentage rawValue = ${rawValue} → ${(rawValue / 32) * 100}%”
def result = [:]
if (0 <= rawValue && rawValue <= 35) {
result.name = 'battery'
result.translatable = true
result.value = Math.round((rawValue / 32) * 100)
result.descriptionText = "${device.displayName} battery was ${result.value}%"
}
return result
}
private Map getDetectedZoneStatus2Map(String _name, Boolean statusbit, String descbit1set, String descbit0set, String descriptionShort,Boolean _translatable = true) {
def detect = statusbit ? descbit1set : descbit0set
String _descriptionText = “${device.displayName} ${descriptionShort} ${detect}”
return [name: _name,
value: detect,
descriptionText:_descriptionText,
translatable:_translatable]
}
def refresh() {
log.debug “Refreshing Values”
def refreshCmds =
refreshCmds += zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020,[destEndpoint: 0x23]) +
zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0029,[destEndpoint: 0x26]) +
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS,[destEndpoint: 0x23])
return refreshCmds
}
/**
- PING is used by Device-Watch in attempt to reach the Device
- */
def ping() {
log.debug "ping "
zigbee.readAttribute(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS,[destEndpoint: 0x23])
}
def configure() {
log.debug “configure”
sendEvent(name: “checkInterval”, value:20 * 60 + 2 * 60, displayed: false, data: [protocol: “zigbee”, hubHardwareId: device.hub.hardwareID, offlinePingable: “1”])
Integer minPowerReportTime = 60*60*12 // 12H
Integer maxPowerReportTime = 60*60*12
Integer reportPowertableChange = 0x01
Integer minTempReportTime = 60*5 // 5 min
Integer maxTempReportTime = 600 // 10 min
Integer reportTemptableChange = 0x10
Integer minZSReportTime = 0
Integer maxZSReportTime = 60*5 //5 min
Integer reportZStableChange = null
return refresh() + zigbee.enrollResponse() + zigbee.configureReporting(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0021, DataType.UINT8, minPowerReportTime, maxPowerReportTime, reportPowertableChange) +
zigbee.configureReporting(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0029, DataType.INT16, minTempReportTime, maxTempReportTime, reportTemptableChange,[destEndpoint: 0x26]) +
zigbee.writeAttribute(IAS_WD_CLUSTER, ATTRIBUTE_IAS_WD_MAXDURATION, DataType.UINT16, DEFAULT_DURATION) +
zigbee.configureReporting(zigbee.IAS_ZONE_CLUSTER, zigbee.ATTRIBUTE_IAS_ZONE_STATUS, DataType.UINT16, minZSReportTime, maxZSReportTime, reportZStableChange,[destEndpoint: 0x23])
}
//A testbutton is pressed on one of the devices.
def alarm() {
log.debug “testStatus()”
}
def turnOffTestAlarm() {
log.trace “started : turnOffTestAlarm”
def smokeTestAlarmStatus = device.currentState('testState')?.value
log.trace "Current SmokeTestAlarmStatus $smokeTestAlarmStatus"
def smokeDetectorStatus = device.currentState('smoke')?.value
log.trace "Current smokeDetectorStatus $smokeDetectorStatus"
if (smokeTestAlarmStatus == "false" && smokeDetectorStatus =="clear") {turnOffAlarmTile()}
else if (smokeDetectorStatus =="detected") { log.warn "smoke detector already in detected.."}
}
def turnOnTestAlarm() {
log.trace “started : turnOnTestAlarm”
def smokeTestAlarmStatus = device.currentState('testState')?.value
log.trace "Current SmokeTestAlarmStatus $smokeTestAlarmStatus"
def smokeDetectorStatus = device.currentState('smoke')?.value
log.trace "Current smokeDetectorStatus $smokeDetectorStatus"
if (smokeTestAlarmStatus == "true" && smokeDetectorStatus =="clear") {turnOnAlarmTestTile()}
else if (smokeDetectorStatus =="detected") { log.warn "smoke detector already in detected.."}
}
def siren() {
log.trace “siren()”
on()
}
def on() {
log.trace “starting on()”
state.alarmOn = true
def warningDuration = state.maxDuration ? state.maxDuration : DEFAULT_MAX_DURATION
state.lastDuration = warningDuration
// start warning, burglar mode, no strobe, siren very high
zigbee.command(IAS_WD_CLUSTER, COMMAND_IAS_WD_START_WARNING, "13", DataType.pack(warningDuration, DataType.UINT16), "00", "00")
}
def off() {
log.debug “starting off()”
state.alarmOn = false
zigbee.command(IAS_WD_CLUSTER, COMMAND_IAS_WD_START_WARNING, "00", "0000", "00", "00")
}
def eventHandler(evt) {
log.info “event description text: ${evt.descriptionText}”
}