Philio PST02ABC driver -- yet more parameter handling

I took the excellent Philio PST02ABC driver developed by @dennypage and extended by @kunpet, as described in this thread, and used cursor.com to extend it to provide control of 2 additional parameters:

  • Parameter 3: PIR sensitivity, Range 0-99, default 80. 0 = disabled, 99 = highest sensitivity.
  • Parameter 22: Illumination differential report. Range 0-99, default 0. 0 = disable, 99 = every 99 minutes. The default setting of this parameter was 0, resulting in no illuminance reporting.

Updated 12/5/2025, V100.4, added control for parameter 4: Illumination threshold. 1 = darkest, 99 = lightest, 100 = disable

Update 12/5/2025 V100.5, Previously, parameter 5 was entered as a decimal number representing a bit mask. To make this easier for the user, I have changed this to a set of 6 toggles. Each toggle explains what it controls so the user can select what they want. The appropriate value for parameter 5 is then calculated automatically. The calculated value can be seen in the state variables on the "commands" page and in the logs. Screenshot of the toggles:

The code is below -- posting it in case it is useful for anyone else.

Marc

//
// Copyright (c) 2020-2022, Denny Page
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Version 1.0.0    Initial release
// Version 1.1.0    Report version information for protocol, hardware and firmware.
//                  Unhandled events logged as warnings.
// Version 1.2.0    Add support for setting the wakeup interval.
// Version 1.3.0    Move to Wakeup interval in minutes and improve validity checks.
// Version 1.4.0    Use zwaveSecureEncap method introduced in Hubitat 2.2.3.
// Version 1.5.0    Normalize logging
// Version 1.5.1    Fix low battery alert
// Version 1.5.2    Low battery value cannot be 0
// Version 1.5.3    Fix battery value again
// Version 2.0.0    Support flood sensor (PAT02-A & PAT03-C)
// Version 2.0.1    Poll flood sensor on refresh
// Version 2.0.2    Support older firmware that may send SensorBinaryReport rather than NotificationReport for flood sensor
// Version 2.0.3    Older firmware may also send SensorBinaryReport for tamper
// Version 2.0.4    Notify if parameter 7 is not factory default
/*
***	 Philio PAT02 driver adapted to Philio PST02 A/B/C by kunPet
***	 Parameter 5/6/7 added as preference
***	 works with firmware 1.16, 1.20, 1.24
*/
/*
*** Philio PST02 A/B/C driver updated by Marc Aronson using cursor.com
*** v100.1
*** Parameters 3 (PIR sensitivity) & 22 (Illumination differential report) added as preferences
*** Tested with firmware version 1.20
*** v100.2
***		Parameter 4 (LightThreshold)
*** v100.3
***		Used gemini LLM to review code. It found & fixed 2 potential issues.
***		- Parameter values set to 0 could be reset to default due to missing check for null.
***		- Variable "value" used w/ being defined inside a method, making it a global.
***		Also removed some unreachable break statements. 
*** v100.4
*** Replaced Parameter 5 raw input with 6 bitmask toggles.
*** v100.5
*** Updated Parameter 5 toggles logic (On=0, Off=1) and fixed signed byte handling.
*/

//	devID = getDataValue "deviceId"
//	If (devID.toInteger() == 14) {
//		value = para5 ? para5.toInteger() : 60
//		device.updateSetting("para5", 60)
//	}

metadata
{
    definition (
        name: "Philio PST02 v100.5", namespace: "kunPet", author: "Marc Aronson / kunPet / Denny Page"
    )
    {
        capability "TemperatureMeasurement"
        capability "Sensor"
        capability "Refresh"
        capability "Configuration"
        capability "Battery"
        capability "TamperAlert"
        capability "Contact Sensor"					//PST-AC only
        capability "Motion Sensor"					//PST-AB only
        capability "Illuminance Measurement"		//PST only

        command "clearTamper"

        fingerprint  mfr:"013C", prod:"0002", deviceId:"000E", inClusters:"0x5E,0x72,0x86,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20"	//PST-C , dec=14
        fingerprint  mfr:"013C", prod:"0002", deviceId:"000C", inClusters:"0x5E,0x72,0x86,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20"	//PST-A , dec=12
        fingerprint  mfr:"013C", prod:"0002", deviceId:"000D", inClusters:"0x5E,0x72,0x86,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20"	//PST-B , dec=13

        // 0x30 COMMAND_CLASS_SENSOR_BINARY_V2 (removed in later firmware)
        // 0x31 COMMAND_CLASS_SENSOR_MULTILEVEL_V5 (later firmware uses V11)
        // 0x59 COMMAND_CLASS_ASSOCIATION_GRP_INFO
        // 0x5A COMMAND_CLASS_DEVICE_RESET_LOCALLY
        // 0x5E COMMAND_CLASS_ZWAVEPLUS_INFO_V2
        // 0x70 COMMAND_CLASS_CONFIGURATION
        // 0x71 COMMAND_CLASS_NOTIFICATION_V4 (later firmware uses V8)
        // 0x72 COMMAND_CLASS_MANUFACTURER_SPECIFIC_V2
        // 0x73 COMMAND_CLASS_POWERLEVEL
        // 0x7A COMMAND_CLASS_FIRMWARE_UPDATE_MD_V2
        // 0x80 COMMAND_CLASS_BATTERY
        // 0x84 COMMAND_CLASS_WAKE_UP_V2
        // 0x85 COMMAND_CLASS_ASSOCIATION_V2
        // 0x86 COMMAND_CLASS_VERSION_V2 (later firmware uses V3)
        // 0x8F COMMAND_CLASS_MULTI_CMD
        // 0x98 COMMAND_CLASS_SECURITY
        // 0x9F COMMAND_CLASS_SECURITY_2 (only in later firmware)
    }
}

preferences
{
    // Device values not configurable by this driver but logged when configure: Parameter 4, 9, 12
    // !! button "configure" sets Defaults specified under deviceSync() - preferenence values are set after "set preference"
    
    // # # # # # # #
    // Parameter 3: PIR sensitivity, Range 0-99, default 80. 0 = disabled, 99 = highest sensitivity.
    input name: "pirSensitivity", title: "Para 3 PIR Sensitivity", description: "0 = disabled, 99 = highest sensitivity", type: "number", defaultValue: "80", range: "0..99"
    // Parameter 4: Illumination threshold. 1 = darkest, 99 = lightest, 100 = disable
    input name: "illuminationThreshold", title: "Para 4 Illumination Threshold", description: "1 = darkest, 99 = lightest, 100 = disabled", type: "number", defaultValue: "99", range: "1..100"


    // # # # # # # #
    // Parameter 5 broken out into bitmask toggles
    // Bits 0 and 6 are reserved (0)
    
    // bit 1: On = Normal mode; Off = Test mode. Default this to off (false).
    input name: "p5_normalMode", type: "bool", title: "Para 5 (Bit 1): Operation Mode", description: "On = Normal Mode, Off = Test Mode(D)", defaultValue: false
    
    // bit 2: On = Enable contact sensor reporting. Default this to on (true).
    input name: "p5_contactReport", type: "bool", title: "Para 5 (Bit 2): Contact Sensor", description: "On = Enable Reporting(D)", defaultValue: true
    
    // bit 3: On = report temperature in Celsius. Default this to Off (False).
    input name: "p5_celsius", type: "bool", title: "Para 5 (Bit 3): Temp Scale", description: "On = Celsius, Off = Fahrenheit(D)", defaultValue: false
    
    // Bit 4: On = report illumination after trigger. Default this to on (true).
    input name: "p5_illumReport", type: "bool", title: "Para 5 (Bit 4): Illumination Report", description: "On = Report after trigger(D)", defaultValue: true
    
    // Bit 5: On = report temperature after trigger. Default this to on (true).
    input name: "p5_tempReport", type: "bool", title: "Para 5 (Bit 5): Temperature Report", description: "On = Report after trigger(D)", defaultValue: true
    
    // Bit 7: On = Enable the back key release into test mode. Default this to on (true).
    input name: "p5_backKey", type: "bool", title: "Para 5 (Bit 7): Back Key", description: "On = Releasing key on back => test mode(D)", defaultValue: true

    // Parameter 6 (= 6), Range 0-127, hexa-String			(value 4 not working)
    // Parameter 7 (= 86), Range 0-127, hexa-String			(for PST02B =22  ,  for PST02A/C: NotificationReport doesn't get motion-inactive!)
    input name: "para6", title: "Para 6 MultSensor Fct Switch", description: "bitControl def.6", type: "number", defaultValue: "6", range: "0..127"
    input name: "para7", title: "Para 7 Customer Function", description: "bitControl def.86", type: "number", defaultValue: "86", range: "0..127"

     // PIR Redetect interval: Parameter 8, Range 0-127, default 3 changed to 12, units of Ticks. 0 disables auto reporting.
    input name: "pirInterval", title: "PIR Redetect Ticks", description: "8s per Tick", type: "number", defaultValue: "12", range: "0..127"

    // Auto Report Battery interval: Parameter 10, Range 0-127, default 12, units of Ticks. 0 disables auto reporting.
    input name: "batteryInterval", title: "Battery Auto Report Ticks", description: "0 disables auto reporting", type: "number", defaultValue: "12", range: "0..127"

    // Auto Report Door interval: Parameter 11, Range 0-127, default 12, units of Ticks. 0 disables auto reporting.
    input name: "doorInterval", title: "Door Auto Report Ticks", description: "0 disables auto reporting", type: "number", defaultValue: "12", range: "0..127"

    // Auto Report Temperature interval: Parameter 13, Range 0-127, default 12 changed to 2, units of Ticks. 0 disables auto reporting.
    input name: "temperatureInterval", title: "Temperature Auto Report Ticks", description: "0 disables auto reporting", type: "number", defaultValue: "2", range: "0..127"

//		    // Auto Report Humidity interval: Parameter 14, Range 0-127, default 12, units of Ticks. 0 disables auto reporting.
//		    input name: "humidityInterval", title: "Humidity Auto Report Ticks", description: "0 disables auto reporting", type: "number", defaultValue: "12", range: "0..127"

//		    // Auto Report Water interval: Parameter 15, Range 0-127, default 12, units of Ticks. 0 disables auto reporting.
//		    input name: "waterInterval", title: "Water Auto Report Ticks", description: "0 disables auto reporting", type: "number", defaultValue: "12", range: "0..127"

    // Auto Report Tick interval: Parameter 20, Range 0-255, default 30, units of minutes. 0 disables all auto reporting.
    input name: "tickInterval", title: "Auto Report Tick minutes", description: "0 disables ALL auto reporting", type: "number", defaultValue: "30", range: "0..255"
 
    // Temperature differential report: Parameter 21, Range 0-127, default 1 changed to 3, units of degrees Fahrenheit  !! Feuer-TempÜberwachung !!!!!
    input name: "temperatureDifferential", title: "Temperature differential report", description: "0 disables differential reporting", type: "number", defaultValue: "3", range: "0..127"

    // Illumination differential report: Parameter 22, Range 0-99, default 0. 0 = disable, 99 = every 99 minutes.
    input name: "illuminationDifferential", title: "Para 22 Illumination differential report", description: "0 = disable, 99 = every 99 minutes", type: "number", defaultValue: "0", range: "0..99"

//		    // Humidity differential report: Parameter 23, Range 0-60, default 5, units of percent RH%
//		    input name: "humidityDifferential", title: "Humidity differential report", description: "0 disables differential reporting", type: "number", defaultValue: "5", range: "0..60"

    // Wakeup Interval: Number of minutes between wakeups, default 1440 changed to 180
    input name: "wakeUpInterval", title: "Wakeup interval minutes", type: "number", defaultValue: "180", range: "30..7200"

    // Temperature offset: Adjustment amount for temperature measurement
    input name: "temperatureOffset", title: "Temperature offset degrees", type: "decimal", defaultValue: "0"

 //		   // Humidity offset: Adjustment amount for humidity measurement
 //		   input name: "humidityOffset", title: "Humidity offset percent", type: "decimal", defaultValue: "0"

    input name: "logEnable", title: "Enable debug logging", type: "bool", defaultValue: true

    input name: "txtEnable", title: "Enable descriptionText logging", type: "bool", defaultValue: true
}

def deviceSync()
{
    def resync = state.pendingResync
    def refresh = state.pendingRefresh
    Integer value

    state.pendingResync = false
    state.pendingRefresh = false

    if (logEnable) log.debug "deviceSync: pendingResync ${resync}, pendingRefresh ${refresh}"

    def cmds = []
    if (resync)
    {
        cmds.add(zwaveSecureEncap(zwave.versionV2.versionGet()))
    }
    
    // Para 4 – Illumination threshold
    value = illuminationThreshold != null ? illuminationThreshold.toInteger() : 99
    if (resync || state.illuminationThreshold != value)
    {
        log.warn "Updating device illuminationThreshold (para4): ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(
            scaledConfigurationValue: value, 
            parameterNumber: 4, 
            size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 4)))
    }

    // Para 5 - Operation Mode (Calculated from bitmask preferences)
    // Logic: If toggle is ON, bit is 0. If toggle is OFF, bit is 1.
    // Bits 0 and 6 are always 0.
    
    def b1 = p5_normalMode != null ? p5_normalMode : false // Default Off
    def b2 = p5_contactReport != null ? p5_contactReport : true // Default On
    def b3 = p5_celsius != null ? p5_celsius : true // Default On
    def b4 = p5_illumReport != null ? p5_illumReport : true // Default On
    def b5 = p5_tempReport != null ? p5_tempReport : true // Default On
    def b7 = p5_backKey != null ? p5_backKey : true // Default On

    Integer p5Val = 0
    if (!b1) p5Val += 2     // Bit 1 (2^1)
    if (!b2) p5Val += 4     // Bit 2 (2^2)
    if (!b3) p5Val += 8     // Bit 3 (2^3)
    if (!b4) p5Val += 16    // Bit 4 (2^4)
    if (!b5) p5Val += 32    // Bit 5 (2^5)
    if (!b7) p5Val += 128   // Bit 7 (2^7)
    // Bit 0 and 6 are fixed at 0, so no addition needed.

    value = p5Val
    
    // Fix for signed byte issue. 
    // To set values > 127 in a 1-byte signed field, we must subtract 256.
    def sendValue = value
    if (sendValue > 127) sendValue -= 256

    if (resync || state.para5 != value)
    {
        log.warn "Updating device para5 (Operation Mode): ${value} (Calculated)"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: sendValue, parameterNumber: 5, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 5)))
    }

    value = para6 != null ? para6.toInteger() : 6
    if (resync || state.para6 != value)
    {
        log.warn "Updating device para6: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 6, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 6)))
    }

    value = para7 != null ? para7.toInteger() : 86
    if (resync || state.para7 != value)
    {
        log.warn "Updating device para7: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 7, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 7)))
    }

    value = pirSensitivity != null ? pirSensitivity.toInteger() : 80	// Para 3
    if (resync || state.pirSensitivity != value)
    {
        log.warn "Updating device pirSensitivity: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 3, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 3)))
    }

    value = pirInterval != null ? pirInterval.toInteger() : 12	// Para 8
    if (resync || state.pirInterval != value)
    {
        log.warn "Updating device pirInterval: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 8, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 8)))
    }

    value = batteryInterval != null ? batteryInterval.toInteger() : 12	//Para 10
    if (resync || state.batteryInterval != value)
    {
        log.warn "Updating device batteryInterval: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 10, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 10)))
    }

    value = doorInterval != null ? doorInterval.toInteger() : 12	//Para 11
    if (resync || state.doorInterval != value)
    {
        log.warn "Updating device doorInterval: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 11, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 11)))
    }

    value = temperatureInterval != null ? temperatureInterval.toInteger() : 2	//Para 13
    if (resync || state.temperatureInterval != value)
    {
        log.warn "Updating device temperatureInterval: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 13, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 13)))
    }

//    value = humidityInterval ? humidityInterval.toInteger() : 0	//Para 14
//	if (resync || state.humidityInterval != value)
//	{
//		log.warn "Updating device humidityInterval: ${value}"
//		cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 14, size: 1)))
//		cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 14)))
//	}
//
//	value = waterInterval ? waterInterval.toInteger() : 0	//Para 15
//    if (resync || state.waterInterval != value)
//    {
//        log.warn "Updating device waterInterval: ${value}"
//        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 15, size: 1)))
//        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 15)))
//	}

    value = tickInterval != null ? tickInterval.toInteger() : 30	//Para 20
    if (resync || state.tickInterval != value)
    {
        log.warn "Updating device tickInterval: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 20, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 20)))
    }

    value = temperatureDifferential != null ? temperatureDifferential.toInteger() : 3	//Para 21
    if (resync || state.temperatureDifferential != value)
    {
        log.warn "Updating device temperatureDifferential: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 21, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 21)))
    }

    value = illuminationDifferential != null ? illuminationDifferential.toInteger() : 0	//Para 22
    if (resync || state.illuminationDifferential != value)
    {
        log.warn "Updating device illuminationDifferential: ${value}"
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 22, size: 1)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 22)))
    }

//	value = humidityDifferential ? humidityDifferential.toInteger() : 0	//Para 23
//    if (resync || state.humidityDifferential != value)
//    {
//        log.warn "Updating device humidityDifferential: ${value}"
//        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: 23, size: 1)))
//        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 23)))
//    }

    value = wakeUpInterval != null ? wakeUpInterval.toInteger() : 180
    if (resync || state.wakeUpInterval != value)
    {
        log.warn "Updating device wakeUpInterval: ${value}"
        cmds.add(zwaveSecureEncap(zwave.wakeUpV2.wakeUpIntervalSet(seconds: value * 60, nodeid: zwaveHubNodeId)))
        cmds.add(zwaveSecureEncap(zwave.wakeUpV2.wakeUpIntervalGet()))
    }

    if (resync)
    {
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 4)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 9)))
        cmds.add(zwaveSecureEncap(zwave.configurationV1.configurationGet(parameterNumber: 12)))
    }

    if (refresh)
    {
        cmds.add(zwaveSecureEncap(zwave.batteryV1.batteryGet()))
        cmds.add(zwaveSecureEncap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1)))
        cmds.add(zwaveSecureEncap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5)))

//		cmds.add(zwaveSecureEncap(zwave.notificationV4.notificationGet(notificationType: 5, v1AlarmType: 0, event: 0)))
//		cmds.add(zwaveSecureEncap(zwave.notificationV4.notificationGet(notificationType: 5, v1AlarmType: 0, event: 2)))
//		cmds.add(zwaveSecureEncap(zwave.notificationV4.notificationGet(notificationType: 6, v1AlarmType: 0, event: 22)))
//		cmds.add(zwaveSecureEncap(zwave.notificationV4.notificationGet(notificationType: 6, v1AlarmType: 0, event: 23)))
//		cmds.add(zwaveSecureEncap(zwave.notificationV4.notificationGet(notificationType: 7, v1AlarmType: 0, event: 3)))
//		cmds.add(zwaveSecureEncap(zwave.notificationV4.notificationGet(notificationType: 7, v1AlarmType: 0, event: 8)))
//		cmds.add(zwaveSecureEncap(zwave.notificationV4.notificationGet(notificationType: 7, v1AlarmType: 0, event: 254)))

        cmds.add(zwaveSecureEncap(zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 6)))
        cmds.add(zwaveSecureEncap(zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 8)))
        cmds.add(zwaveSecureEncap(zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 10)))
        cmds.add(zwaveSecureEncap(zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 12)))
    }
        
    cmds.add(zwaveSecureEncap(zwave.wakeUpV2.wakeUpNoMoreInformation()))
    delayBetween(cmds, 250)
}

void logsOff()
{
    device.updateSetting("logEnable", [value:"false", type:"bool"])
    log.warn "debug logging disabled"
}

void installed()
{
    state.pendingResync = true
    state.pendingRefresh = true
    runIn(1, deviceSync)
//    runIn(1800, logsOff)
}

void updated()
{
    if (logEnable) log.debug "Updated preferences"

    Integer value

    // Validate numbers in preferences
    // NOTE: para5 validation removed as it is now constructed from checkboxes
    
    if (para6)
    {
        value = para6.toBigDecimal()
        if (value != para6)
        {
            log.warn "para6 must be an integer: ${para6} changed to ${value}"
            device.updateSetting("para6", value)
        }
    }
    if (para7)
    {
        value = para7.toBigDecimal()
        if (value != para7)
        {
            log.warn "para7 must be an integer: ${para7} changed to ${value}"
            device.updateSetting("para7", value)
        }
    }
    if (pirInterval)
    {
        value = pirInterval.toBigDecimal()
        if (value != pirInterval)
        {
            log.warn "Para 8 = pirInterval must be an integer: ${pirInterval} changed to ${value}"
            device.updateSetting("pirInterval", value)
        }
    }    
    if (batteryInterval)
    {
        value = batteryInterval.toBigDecimal()
        if (value != batteryInterval)
        {
            log.warn "Para 10 = batteryInterval must be an integer: ${batteryInterval} changed to ${value}"
            device.updateSetting("batteryInterval", value)
        }
    }
    if (doorInterval)
    {
        value = doorInterval.toBigDecimal()
        if (value != doorInterval)
        {
            log.warn "Para 11 = doorInterval must be an integer: ${doorInterval} changed to ${value}"
            device.updateSetting("doorInterval", value)
        }
    }    
    if (temperatureInterval)
    {
        value = temperatureInterval.toBigDecimal()
        if (value != temperatureInterval)
        {
            log.warn "Para 13 = temperatureInterval must be an integer: ${temperatureInterval} changed to ${value}"
            device.updateSetting("temperatureInterval", value)
        }
    }
//    if (humidityInterval)
//    {
//        value = humidityInterval.toBigDecimal()
//        if (value != humidityInterval)
//        {
//            log.warn "Para 14 = humidityInterval must be an integer: ${humidityInterval} changed to ${value}"
//            device.updateSetting("humidityInterval", value)
//        }
//    }
//    if (waterInterval)
//    {
//        value = waterInterval.toBigDecimal()
//        if (value != waterInterval)
//        {
//            log.warn "Para 15 = waterInterval must be an integer: ${waterInterval} changed to ${value}"
//            device.updateSetting("waterInterval", value)
//        }
//    }
    if (tickInterval)
    {
        value = tickInterval.toBigDecimal()
        if (value != tickInterval)
        {
            log.warn "Para 20 = tickInterval must be an integer: ${tickInterval} changed to ${value}"
            device.updateSetting("tickInterval", value)
        }
    }
    if (pirSensitivity)
    {
        value = pirSensitivity.toBigDecimal()
        if (value < 0) value = 0
        if (value > 99) value = 99
        if (value != pirSensitivity)
        {
            log.warn "Para 3 = pirSensitivity must be an integer between 0 and 99: ${pirSensitivity} changed to ${value}"
            device.updateSetting("pirSensitivity", value)
        }
    }
    if (temperatureDifferential)
    {
    value = temperatureDifferential.toBigDecimal()
        if (value != temperatureDifferential)
        {
            log.warn "Para 21 = temperatureDifferential must be an integer: ${temperatureDifferential} changed to ${value}"
            device.updateSetting("temperatureDifferential", value)
        }
    }
    if (illuminationDifferential)
    {
        value = illuminationDifferential.toBigDecimal()
        if (value < 0) value = 0
        if (value > 99) value = 99
        if (value != illuminationDifferential)
        {
            log.warn "Para 22 = illuminationDifferential must be an integer between 0 and 99: ${illuminationDifferential} changed to ${value}"
            device.updateSetting("illuminationDifferential", value)
        }
    }
//    if (humidityDifferential)
//    {
//        value = humidityDifferential.toBigDecimal()
//        if (value != humidityDifferential)
//        {
//            log.warn "Para 23 = humidityDifferential must be an integer: ${humidityDifferential} changed to ${value}"
//            device.updateSetting("humidityDifferential", value)
//        }
//    }
    if (wakeUpInterval)
    {
        value = wakeUpInterval.toBigDecimal()
        if (value < 30)
        {
            value = 30
        }
        else if (value > 7200)
        {
            value = 7200
        }
        else
        {
            Integer r = value % 30
            if (r)
            {
                value += 30 - r
            }
        }
        if (value != wakeUpInterval)
        {
            log.warn "wakeUpInterval must be an integer multiple of 30 between 30 and 7200: ${wakeUpInterval} changed to ${value}"
            device.updateSetting("wakeUpInterval", value)
        }
    }

    log.warn "debug logging is ${logEnable}"
    log.warn "description logging is ${txtEnable}"
    
    // Trigger sync to apply changes (including calculating Para 5)
    state.pendingResync = true
    runIn(2, deviceSync)
}

def configure()
{
    state.pendingResync = true
    log.warn "Configuration will resync when device wakes up"
}

def refresh()
{
    state.pendingRefresh = true
    log.warn "Data will refresh when device wakes up"
}

def clearTamper()
{
    def map = [:]
    map.name = "tamper"
    map.value = "clear"
    map.descriptionText = "${device.displayName}: tamper cleared"
    sendEvent(map)
    if (txtEnable) log.info "${device.displayName}: ${map.descriptionText}"
}

def parse(String description)
{
    hubitat.zwave.Command cmd = zwave.parse(description, commandClassVersions)
    if (cmd)
    {
        return zwaveEvent(cmd)
    }

    log.warn "Non Z-Wave parse event: ${description}"
    return null
}

def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
{
    def map = [:]

    if (logEnable) log.trace "SensorMultilevelReport: ${cmd.toString()}"

    switch (cmd.sensorType)
    {
        case 1: // temperature
            def value = cmd.scaledSensorValue
            def precision = cmd.precision
            def unit = cmd.scale == 1 ? "F" : "C"

            map.name = "temperature"
            map.value = convertTemperatureIfNeeded(value, unit, precision)
            map.unit = getTemperatureScale()
            //    if (logEnable) log.info "${device.displayName} temperature sensor value is ${value}°${unit} (${map.value}°${map.unit})"

            if (temperatureOffset)
            {
                map.value = (map.value.toBigDecimal() + temperatureOffset.toBigDecimal()).toString()
                if (logEnable) log.info "${device.displayName} adjusted temperature by ${temperatureOffset} to ${map.value}°${map.unit}"
            }
            map.descriptionText = "temperature is ${map.value}°${map.unit}"
            break

        case 5: // humidity
            value = cmd.scaledSensorValue

            map.name = "humidity"
            map.value = value
            map.unit = "%"
            //    if (logEnable) log.info "${device.displayName} humidity sensor value is ${map.value}${map.unit}"

            if (humidityOffset)
            {
                map.value = (map.value.toBigDecimal() + humidityOffset.toBigDecimal()).toString()
                if (logEnable) log.info "${device.displayName} adjusted humidity by ${humidityOffset} to ${map.value}${map.unit}"
            }
            map.descriptionText = "humidity is ${map.value}${map.unit}"
            break

        case 3:    // luminance
            value = cmd.scaledSensorValue

            map.name = "illuminance"
            map.value = value
            map.unit = "lux"
            //    if (logEnable) log.info "${device.displayName} luminance sensor value is ${map.value}${map.unit}"

            map.descriptionText = "luminance is ${map.value}${map.unit}"
            break            

        default:
            log.warn "Unknown SensorMultilevelReport-Type: ${cmd.toString()}"
            return null
    }

    sendEvent(map)
    if (txtEnable) log.info "${device.displayName}: ${map.descriptionText}"
}

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd)
{
    def map = [:]

    if (logEnable) log.trace "BatteryReport: ${cmd.toString()}"

    def batteryLevel = cmd.batteryLevel
    if (batteryLevel == 0xFF)
    {
        log.warn "${device.displayName} low battery"
        batteryLevel = 1
    }

    map.name = "battery"
    map.value = batteryLevel
    map.unit = "%"
    map.descriptionText = "battery is ${map.value}${map.unit}"
    sendEvent(map)
    if (txtEnable) log.info "${device.displayName}: ${map.descriptionText}"
}

def zwaveEvent(hubitat.zwave.commands.notificationv4.NotificationReport cmd)
{
    def map = [:]

    if (logEnable) log.trace "NotificationReport: ${cmd.toString()}"

    switch (cmd.notificationType)
    {
        case 5: //water alarm
            map.name = "water"
            map.value = cmd.event ? "wet" : "dry"
            map.descriptionText = "sensor is ${map.value}"
            //    if (logEnable) log.info "${device.displayName} water sensor value ${map.value}"
            break
        case 6: //access control, contact sensor
            map.name = "contact"
            def event = cmd.event.toInteger()
            if (event == 22) map.value = "open"
             if (event == 23) map.value = "closed"
            map.descriptionText = "contact is ${map.value}"
            //    if (logEnable) log.info "${device.displayName} door is ${map.value}"
            break
        case 7: // security
            def val = cmd.event.toInteger()
            if (val == 3) {
                map.name = "tamper"
                map.value = "detected"
                map.descriptionText = "tamper is ${map.value}"
                //    if (logEnable) log.info "${device.displayName} tamper is ${map.value}"
                break            
            } else if (val == 8) {
                map.name = "motion"
                map.value = "active"
                map.isStateChange = true    //Event auch ohne Änderung value ?
                map.descriptionText = "motion is ${map.value}"
                //    if (logEnable) log.info "${device.displayName} motion is ${map.value}"
                break
            } else if (val == 254) {
                map.name = "motion"
                map.value = "inactive"
                map.descriptionText = "motion is ${map.value}"
                //    if (logEnable) log.info "${device.displayName} motion is ${map.value}"
                break
            } else {
                log.warn "Unknown NotificationReport-Event: ${cmd.toString()}"
                return null
            }
        default:
            log.warn "Unknown NotificationReport-Type: ${cmd.toString()}"
            return null
    }

    sendEvent(map)
    if (txtEnable) log.info "${device.displayName}: ${map.descriptionText}"
}

def zwaveEvent(hubitat.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd)
{
    // NB: Older firmware versions may send SensorBinaryReport instead of NotificationReport

    def map = [:]

    if (logEnable) log.trace "SensorBinaryReport: ${cmd.toString()}"

    switch (cmd.sensorType)
    {
        case 6: // water
            map.name = "water"
            map.value = cmd.sensorValue ? "wet" : "dry"
            map.descriptionText = "sensor is ${map.value}"
            break
        case 8: // tamper
            map.name = "tamper"            
            if (cmd.sensorValue.toInteger() > 0 ) {
                map.value = "detected"
            } else {
                map.value = "cleared"
            }
            map.descriptionText = "tamper value ${map.value}"
            break
        case 10: // contact sensor
            map.name = "contact"
            if (cmd.sensorValue.toInteger() > 0 ) {
                map.value = "open"
            } else {
                map.value = "closed"
            }
            map.descriptionText = "contact is ${map.value}"
            break
        case 12: // motion sensor
            map.name = "motion"
            if (cmd.sensorValue.toInteger() > 0 ) {
                map.value = "active"
                map.isStateChange = true    //Event auch ohne Änderung value ?
            } else {
                map.value = "inactive"
            }
            map.descriptionText = "motion is ${map.value}"
            break
        default:
            log.warn "Unknown SensorBinaryReport: ${cmd.toString()}"
            return null
    }

    sendEvent(map)
    if (txtEnable) log.info "${device.displayName}: ${map.descriptionText}"
}

def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd)
{
    if (logEnable) log.trace "ConfigurationReport: ${cmd.toString()}"

    switch (cmd.parameterNumber)
    {
        case 5:    // Operation Mode
            state.para5 = cmd.configurationValue[0]
            // if (state.para5.toInteger() != para5) log.warn "values state=${state.para5.toInteger()} neq preference=${para5}"
            // Note: para5 check removed because para5 preference no longer exists directly
            break
        case 6:    // MultiSensor Fct Swictch
            state.para6 = cmd.configurationValue[0]
            if (state.para6.toInteger() != para6) log.warn "values state=${state.para6.toInteger()} neq preference=${para6}"
            break
        case 7:    // Customer Fct
            state.para7 = cmd.configurationValue[0]
            if (state.para7.toInteger() != para7) log.warn "values state=${state.para7.toInteger()} neq preference=${para7}"
            break
        case 3:    // PIR Sensitivity
            state.pirSensitivity = cmd.configurationValue[0]
            if (state.pirSensitivity.toInteger() != pirSensitivity) log.warn "values state=${state.pirSensitivity.toInteger()} neq preference=${pirSensitivity}"
            break
        case 8:    // PIR Redetect interval
            state.pirInterval = cmd.configurationValue[0]
            if (state.pirInterval.toInteger() != pirInterval) log.warn "values state=${state.pirInterval.toInteger()} neq preference=${pirInterval}"
            break
        case 10: // Auto Report Battery interval
            state.batteryInterval = cmd.configurationValue[0]
            break
        case 11: // Auto Report Door interval
            state.doorInterval = cmd.configurationValue[0]
            if (state.doorInterval.toInteger() != doorInterval) log.warn "values state=${state.doorInterval.toInteger()} neq preference=${doorInterval}"
            break
        case 13: // Auto Report Temperature interval
            state.temperatureInterval = cmd.configurationValue[0]
            break
//        case 14: // Auto Report Humidity interval
//            state.humidityInterval = cmd.configurationValue[0]
//            break
//        case 15: // Auto Report Water interval
//            state.waterInterval = cmd.configurationValue[0]
            break
        case 20: // Auto Report tick interval
            state.tickInterval = cmd.configurationValue[0]
            break
        case 21: // Temperature Differential Report
            state.temperatureDifferential = cmd.configurationValue[0]
            break
        case 22: // Illumination Differential Report
            state.illuminationDifferential = cmd.configurationValue[0]
            if (state.illuminationDifferential.toInteger() != illuminationDifferential) log.warn "values state=${state.illuminationDifferential.toInteger()} neq preference=${illuminationDifferential}"
            break
//        case 23: // Humidity Differential Report
//            state.humidityDifferential = cmd.configurationValue[0]
//             break
        case 4: case 9: case 12:
            break
        default:
            log.warn "Configuration Report with unspecified Parameter: ${cmd.toString()}"
    }
}

def zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpIntervalReport cmd)
{
    state.wakeUpInterval = cmd.seconds / 60
    if (logEnable) log.trace "wakup interval ${state.wakeUpInterval} minutes"
}

def zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpNotification cmd)
{
    log.debug "${device.displayName}: Received WakeUpNotification"
    runInMillis(200, deviceSync)
}

void zwaveEvent(hubitat.zwave.commands.versionv2.VersionReport cmd)
{
    if (logEnable) log.debug "VersionReport: ${cmd}"
    device.updateDataValue("firmwareVersion", "${cmd.firmware0Version}.${cmd.firmware0SubVersion}")
    device.updateDataValue("protocolVersion", "${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}")
    device.updateDataValue("hardwareVersion", "${cmd.hardwareVersion}")
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd)
{
    encapCmd = cmd.encapsulatedCommand()
    if (encapCmd)
    {
        return zwaveEvent(encapCmd)
    }

    log.warn "Unable to extract encapsulated cmd: ${cmd.toString()}"
    return null
}

def zwaveEvent(hubitat.zwave.Command cmd)
{
    log.warn "Unhandled cmd: ${cmd.toString()}"
    return null
}
3 Likes

excellent extension!

1 Like

I had missed that prior post because I wasn't tagged. Thank you for posting and tagging me. I love the Philio devices, and I'm glad you guys like the PAT02 driver!

FWIW, I was actually looking at a PST02 recently... I guess I really should get one now... :grin:

Thanks,
Denny

1 Like

@marcaronson408 - thanks so much for the modified driver. Quick question - I don't see a parameter for Illuminance Threshold? Is there another way to turn off illuminance reporting? I only need the contact sensor capability and I was hoping I could turn the others off.

Thanks for your help.

I suggest doing 2 things:

  • Set "Para 22 Illumination differential report" to 0.
  • Turn on bit 4 of "parameter 5: Operation mode".

If you aren't sure how to turn bit 4 on, send me your current value for parameter 5 and I will send you back the modified value that turns on bit 4.

This is a link to a google doc that documents all the various parameters.

Marc

Parameter 5: Operation Mode

Operation mode. Using bit to control.Caution: The value is unsigned byte, the range is from 0x00 ~ 0xFF.Bit0: Reserve.Bit1: 1 means test mode, 0 means normal mode.Bit2: Disable the door/window function.(1:Disable, 0:Enable)Bit3: Setting the temperature scale.0: Fahrenheit, 1:CelsiusBit4: Disable the illumination report after event triggered.(1:Disable, 0:Enable)Bit5: Disable the temperature report after event triggered.(1:Disable, 0:Enable)Bit6: Reserve.Bit7: Disable the back key release into test mode.(1:Disable, 0:Enable)
Size: 1 Byte, Default Value: 0

Setting Description
0 - 127 Setting Operation mode.
2 Bit1: 1 means test mode, 0 means normal mode.
4 Bit2: Disable the door/windowfunction.(1:Disable, 0:Enable)
8 Bit3: Setting the temperature scale. 0: Fahrenheit, 1:Celsius
10 Bit4: Disable the illumination report after event triggered.(1:Disable, 0:Enable)
20 Bit5: Disable the temperature report after event triggered.(1:Disable, 0:Enable)
40 Bit6: Reserve.
80 Bit7: Disable the back key release into test mode. (1:Disable, 0:Enable)
1 Bit0: Reserve.
1 Like

Marc - I have Parameter 22 turned off but not sure how to do the next part. Attached are my current settings. Does the value of "56" need to be changed? If so, can you tell me what to change it to? Thanks!

56 decimal = 111000 in binary. So you have bits 0,1 & 2 set to 0; 3, 4 & 5 set to 1, which means that you have already disabled "illumination report after event".

You also seem to have temperature reporting set to Celsius, not Fahrenheit. Are you seeing temperatures being reported in Celsius?

Marc

1 Like

In Event logs, the value is shown in fahrenheit. I think the only settings I changed were the ones relating to disable PIR reporting and temperature reporting intervals.

OK, let's try a simple experiment to see if I am mis-reading which value in bit 4 disables this function.

Try changing parameter 5 from 56 to 40, as this will change bit4 from 1 to 0.

Keep in mind that the device needs to wake up before it actually gets reconfigured. On mine, I had to remove the front cover and push the little button in the upper-left corner of the device -- circled in red in the photo below. You can confirm that the device got updated by looking on the device's "Commands" tab and seeing what value is showing for "Para 5".

If this doesn't solve it, we're going to have to use the basic z-wave driver to update parameter 4.

Marc

1 Like

@marcaronson408 - we are leaving to go out of the country in the next few hours. Will be back in about 10 days and will give this a try.

Also, in looking at the manual, bit 4 (of Parameter 5) if for the illumination report? Or am I misunderstanding what is there?

Yes, bit 4 is for the illumination report. The ambiguous part is the way it is worded: " Bit4: Disable the illumination report after event triggered.(1:Disable, 0:Enable)".

It probably means a value of 1 disables illumination reporting, and that is your current setting.

But there is another way of interpreting that statement that inverts the meaning, which is why I suggest trying the other value I provided earlier, as that will set bit4 to 0.

Or have I misunderstood what you are trying to accomplish?

Marc

1 Like

@rakeshg I experimented with a spare sensor I have and in my 5 minute test it did not report illumination if I set parameter 22 to 0.

Having said this, I updated the driver posted at the top of this tread to give you control over parameter 4. Try it out with parameter 22=0, parameter4=100.

If you still have the problem, please turn on debug logging and descriptive text logging and post a screenshot of a section of your log that shows where illumination is being reported. That might help me figure out what is going on.

Marc

1 Like

Thanks so much @marcaronson408. On a plane right now headed out of town for the next 10 days. Will update the driver when I return. Thanks so much for your help :pray:

Sounds good.

I just made another update so that you don't need to translate binary bit maps into decimal to set parameter 5. Instead, there are 6 documented toggles so you can choose what you want turned on and turned off. The driver then automatically calculates the correct value for parameter 5.

"(D)" indicates the default values.

Cheers,

Marc

1 Like