Skip to content

Commit

Permalink
No commit message
Browse files Browse the repository at this point in the history
  • Loading branch information
krlaframboise committed Nov 22, 2021
1 parent 83ac450 commit 5d715ff
Show file tree
Hide file tree
Showing 2 changed files with 429 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,390 @@
/*
* Zooz Temperature | Humidity XS Sensor ZSE44 v1.0
*
* Changelog:
*
* 1.0 (11/21/2021)
* - Initial Release
*
* Copyright 2021 Zooz
*
* 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 groovy.transform.Field

@Field static Map commandClassVersions = [
0x31: 5, // SensorMultilevel
0x55: 1, // Transport Service v2
0x59: 1, // AssociationGrpInfo v3
0x5A: 1, // DeviceResetLocally
0x5E: 2, // ZwaveplusInfo v2
0x6C: 1, // Supervision
0x70: 2, // Configuration v4
0x71: 3, // Notification v4
0x72: 2, // ManufacturerSpecific
0x73: 1, // Powerlevel
0x7A: 2, // FirmwareUpdateMd v5
0x80: 1, // Battery
0x84: 2, // WakeUp
0x85: 2, // Association v3
0x86: 1, // Version v2
0x87: 1, // Indicator v3
0x8E: 2, // Multi Channel Association v4
0x9F: 1 // Security 2
]

@Field static Map configParams = [
lowBatteryReports: [num:2, title:"Low Battery Reports", size:1, defaultVal:10, options:[10:"10% [DEFAULT]", 20:"20%", 30:"30%", 40:"40%", 50:"50%"]],
tempReportingThreshold: [num:3, title:"Temperature Reporting Threshold", size:1, defaultVal:10, range:"10..100", desc:"10..100 (10 = 1°)"],
tempReportingInterval: [num:16, title:"Temperature Reporting Interval", size:2, defaultVal:240, range:"0..480", desc:"0(disabled), 1..480(minutes)"],
tempUnit: [num:13, title:"Temperature Unit", size:1, defaultVal:1, options:[0:"Celsius", 1:"Fahrenheit [DEFAULT]"]],
tempOffset: [num:14, title:"Temperature Offset", size:1, defaultVal:0, range:"-100..100", desc:"-100..100 (10 = 1°)"],
highTempThreshold: [num:5, title:"Heat Alert Temperature", size:1, defaultVal:120, range:"50..120", desc:"50..120(°)"],
lowTempThreshold: [num:7, title:"Freeze Alert Temperature", size:1, defaultVal:10, range:"10..100", desc:"10..100(°)"],
humidityReportingThreshold: [num:4, title:"Humidity Reporting Threshold", size:1, defaultVal:5, range:"5..50", desc:"5..50(%)"],
humidityReportingInterval: [num:17, title:"Humidity Reporting Interval", size:2, defaultVal:240, range:"0..480", desc:"0(disabled), 1..480(minutes)"],
humidityOffset: [num:15, title:"Humidity Offset", size:1, defaultVal:0, range:"-20..20", desc:"-20..20(%)"],
highHumidityThreshold: [num:9, title:"High Humidity Alert Level", size:1, defaultVal:0, range:"0..100", desc:"0(disabled), 1..100(%)"],
lowHumidityThreshold: [num:11, title:"Low Humidity Alert Level", size:1, defaultVal:0, range:"0..100", desc:"0(disabled), 1..100(%)"]
]

@Field static Map temperatureSensor = [sensorType:1, scale:1]
@Field static Map humiditySensor = [sensorType: 5, scale:0]
@Field static Map temperatureAlarm = [name:"temperatureAlarm", notificationType:4, normalEvent:0, highEvent:2, lowEvent:6]
@Field static Map humidityAlarm = [name:"humidityAlarm", notificationType:16, normalEvent:0, highEvent:2, lowEvent:6]
@Field static int wakeUpInterval = 43200

metadata {
definition (
name: "Zooz Temperature | Humidity XS Sensor ZSE44",
namespace: "Zooz",
author: "Kevin LaFramboise (@krlaframboise)",
ocfDeviceType:"oic.d.thermostat",
vid: "feb1a07f-d442-3ee4-9f44-14faf86b3a98",
mnmn: "SmartThingsCommunity"
) {
capability "Sensor"
capability "Temperature Measurement"
capability "Relative Humidity Measurement"
capability "Battery"
capability "Refresh"
capability "Health Check"
capability "Configuration"
capability "platemusic11009.temperatureAlarm"
capability "platemusic11009.humidityAlarm"
capability "platemusic11009.firmware"
capability "platemusic11009.syncStatus"

// zw:Ss2a type:0701 mfr:027A prod:0004 model:E004 ver:1.10 zwv:7.13 lib:03 cc:5E,55,9F,6C sec:86,85,8E,59,31,72,5A,87,73,80,71,70,84,7A
fingerprint mfr:"027A", prod:"0004", model:"E004", deviceJoinName: "Zooz Temperature | Humidity XS Sensor ZSE44"
}

preferences {
configParams.each { name, param ->
if (param.options) {
input name, "enum",
title: param.title,
description: "Default: ${param.options[param.defaultVal]}",
required: false,
displayDuringSetup: false,
defaultValue: param.defaultVal,
options: param.options
} else if (param.range) {
input name, "number",
title: param.title,
description: "${param.desc} - Default: ${param.defaultVal}",
required: false,
displayDuringSetup: false,
defaultValue: param.defaultVal,
range: param.range
}
}

input "debugLogging", "enum",
title: "Logging:",
description: "Default: Enabled",
required: false,
defaultValue: "1",
options: ["0":"Disabled", "1":"Enabled"]
}
}

def installed() {
logDebug "installed()..."
state.pendingRefresh = true
initialize()
}

def updated() {
logDebug "updated()..."
initialize()

if (pendingChanges) {
logForceWakeupMessage("The setting changes will be sent to the device the next time it wakes up.")
}
}

void initialize() {
state.debugLoggingEnabled = (safeToInt(settings?.debugLogging, 1) != 0)

refreshSyncStatus()

if (!device.currentValue("checkInterval")) {
sendEvent([name: "checkInterval", value: ((wakeUpInterval * 2) + (5 * 60)), displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID]])
}

if (!device.currentValue("temperatureAlarm")) {
sendEvent(name:"temperatureAlarm", value:"normal")
}

if (!device.currentValue("humidityAlarm")) {
sendEvent(name:"humidityAlarm", value:"normal")
}
}

def refresh() {
logDebug "refresh()..."
refreshSyncStatus()
state.pendingRefresh = true
logForceWakeupMessage("The device will be refreshed the next time it wakes up.")
}

void logForceWakeupMessage(String msg) {
log.warn "${msg} To force the device to wake up immediately press the action button 4x quickly."
}

def configure() {
logDebug "configure()..."
sendHubCommand(getRefreshCmds(), 250)
}

List<String> getRefreshCmds() {
List<String> cmds = []

if (state.wakeUpInterval == null) {
cmds << secureCmd(zwave.wakeUpV2.wakeUpIntervalGet())
}

if (state.pendingRefresh || !device.currentValue("battery")) {
cmds << secureCmd(zwave.batteryV1.batteryGet())
}

if (state.pendingRefresh || (device.currentValue("temperature") == null)) {
cmds << secureCmd(zwave.sensorMultilevelV5.sensorMultilevelGet(scale: temperatureSensor.scale, sensorType: temperatureSensor.sensorType))
}

if (state.pendingRefresh || (device.currentValue("humidity") == null)) {
cmds << secureCmd(zwave.sensorMultilevelV5.sensorMultilevelGet(scale: humiditySensor.scale, sensorType: humiditySensor.sensorType))
}

if (state.pendingRefresh || !device.currentValue("firmwareVersion")) {
cmds << secureCmd(zwave.versionV1.versionGet())
}

state.pendingRefresh = false
return cmds
}

List<String> getConfigureCmds() {
List<String> cmds = []

int changes = pendingChanges
if (changes) {
log.warn "Syncing ${changes} Change(s)"
}

if (state.wakeUpInterval != wakeUpInterval) {
cmds << secureCmd(zwave.wakeUpV2.wakeUpIntervalSet(seconds: wakeUpInterval, nodeid:zwaveHubNodeId))
cmds << secureCmd(zwave.wakeUpV2.wakeUpIntervalGet())
}

configParams.each { name, param ->
Integer storedVal = getStoredVal(name)
Integer settingVal = getSettingVal(name)
if (storedVal != settingVal) {
logDebug "Changing ${param.title}(#${param.num}) from ${storedVal} to ${settingVal}"
cmds << secureCmd(zwave.configurationV1.configurationSet(parameterNumber: param.num, size: param.size, scaledConfigurationValue: settingVal))
cmds << secureCmd(zwave.configurationV1.configurationGet(parameterNumber: param.num))
}
}
return cmds
}

def ping() {
logDebug "ping()"
}

String secureCmd(cmd) {
if (zwaveInfo?.zw?.contains("s")) {
return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
} else {
return cmd.format()
}
}

def parse(String description) {
def cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
zwaveEvent(cmd)
} else {
log.warn "Unable to parse: $description"
}
}

void zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCmd = cmd.encapsulatedCommand(commandClassVersions)
if (encapsulatedCmd) {
zwaveEvent(encapsulatedCmd)
} else {
log.warn "Unable to extract encapsulated cmd from $cmd"
}
}

void zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
logDebug "Device Woke Up..."
List<String> cmds = []

cmds += getRefreshCmds()
cmds += getConfigureCmds()

if (!cmds) {
cmds << secureCmd(zwave.batteryV1.batteryGet())
}

cmds << secureCmd(zwave.wakeUpV2.wakeUpNoMoreInformation())
sendHubCommand(cmds, 250)
}

void zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpIntervalReport cmd) {
logDebug "Wake Up Interval = ${cmd.seconds} seconds"
state.wakeUpInterval = cmd.seconds
refreshSyncStatus()
}

void zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
Integer val = (cmd.batteryLevel == 0xFF ? 1 : cmd.batteryLevel)
if (val > 100) {
val = 100
}
logDebug "Battery is ${val}%"
sendEvent(name:"battery", value:val, unit:"%", isStateChange: true)
}

void zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
logDebug "${cmd}"
switch (cmd.notificationType) {
case temperatureAlarm.notificationType:
sendAlarmEvent(temperatureAlarm, cmd)
break
case humidityAlarm.notificationType:
sendAlarmEvent(humidityAlarm, cmd)
break
default:
logDebug "${cmd}"
}
}

void sendAlarmEvent(Map alarm, cmd) {
String value
if ((cmd.event == alarm.highEvent) || (cmd.event == alarm.lowEvent)) {
value = (cmd.event == alarm.highEvent) ? "high" : "low"
} else if (cmd.event == alarm.normalEvent) {
value = "normal"
}

if (value) {
logDebug "${alarm.name} is ${value}"
sendEvent(name: alarm.name, value: value)
}
}

void zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
switch (cmd.sensorType) {
case temperatureSensor.sensorType:
def temp = convertTemperatureIfNeeded(cmd.scaledSensorValue, (cmd.scale ? "F" : "C"), cmd.precision)
logDebug "temperature is ${temp}°${temperatureScale}"
sendEvent(name: "temperature", value: temp, unit: temperatureScale)
break
case humiditySensor.sensorType:
logDebug "humidity is ${cmd.scaledSensorValue}%"
sendEvent(name: "humidity", value: cmd.scaledSensorValue, unit: "%")
break
default:
logDebug "Unhandled: ${cmd}"
}
}

void zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
logDebug "${cmd}"
sendEvent(name: "firmwareVersion", value: (cmd.applicationVersion + (cmd.applicationSubVersion / 100)))
}

void zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
runIn(4, refreshSyncStatus)
String name = configParams.find { name, param -> param.num == cmd.parameterNumber }?.key
if (name) {
int val = cmd.scaledConfigurationValue
state[name] = val
logDebug "${configParams[name]?.title}(#${configParams[name]?.num}) = ${val}"
} else {
logDebug "Parameter #${cmd.parameterNumber} = ${cmd.scaledConfigurationValue}"
}
}

void zwaveEvent(physicalgraph.zwave.Command cmd) {
logDebug "Unhandled zwaveEvent: ${cmd}"
}

void refreshSyncStatus() {
int changes = pendingChanges
sendEvent(name: "syncStatus", value: (changes ? "${changes} Pending Changes" : "Synced"), displayed: false)
}

Integer getPendingChanges() {
int configChanges = safeToInt(configParams.count { name, param ->
(getSettingVal(name) != getStoredVal(name))
}, 0)
int pendingWakeUpInterval = (state.wakeUpInterval != wakeUpInterval ? 1 : 0)
return (configChanges + pendingWakeUpInterval)
}

Integer getSettingVal(String name) {
Integer value = safeToInt(settings[name], null)
if ((value == null) && (getStoredVal(name) != null)) {
return configParams[name].defaultVal
} else {
return value
}
}

Integer getStoredVal(String name) {
return safeToInt(state[name], null)
}

Integer safeToInt(val, Integer defaultVal=0) {
if ("${val}"?.isInteger()) {
return "${val}".toInteger()
} else if ("${val}".isDouble()) {
return "${val}".toDouble()?.round()
} else {
return defaultVal
}
}

void logDebug(String msg) {
if (state.debugLoggingEnabled != false) {
log.debug "$msg"
}
}
Loading

0 comments on commit 5d715ff

Please sign in to comment.