GoControl PIR (WAPIRZ-1) weird temp readings

For anyone still interested in my digging, the manufacturer of WAPIRZ-1 for GoControl/Nortek was Vision Automotive Electronics (Taiwan), which currently makes the Monoprice PIR sensor (FCC ID ZP3102US-5). I wonder if the current version has the same bug...?

Don't hold your breath. I had a few go arounds with my (then new) WD500Z-1 dimmer. Not only does it not do what it says (association) but they know it and didn't even change the instructions. I even talked to a product director with no success.

So go for it if it makes you feel better but don't expect any satisfaction from them.

2 Likes

@mike.maxwell is it possible to fork the Generic Z-Wave Motion/Temperature Sensor driver, to add a correction and make it specific to the GoControl / Monoprice PIR sensors? Where could I find the Groovy for the Generic driver? The fix would look kind of like this, for Fahrenheit readings:

if (TempValF < -20)
NewTempF = TempValF * 1.3 + 57.5

Update: Oops,I forgot to google it. SmartThings has a custom driver that addresses this issue (based on the notes). Now I just need to convince someone (maybe @krlaframboise) to help by porting it to Hubitat!

There's nothing odd in that driver. Anyone can convert it. 2 minutes - in theory. I've said that before and been quite wrong.

Well, I'm a Wink refugee, so the learning curve is steep for me. But I'll buy "beer" for someone who does it!

I didn't test it or change the header/revision text, but here you go. It saves without errors, but that doesn't mean it will work without errors. :wink:

Hard to test w/o the device.

/**
 *  GoControl Motion Sensor v1.3.6
 *    (Model: WAPIRZ-1)
 *
 *  Author: 
 *    Kevin LaFramboise (krlaframboise)
 *
 *  URL to documentation:
 *    https://community.smartthings.com/t/release-gocontrol-door-window-sensor-motion-sensor-and-siren-dth/50728?u=krlaframboise
 *
 *  Changelog:
 *
 *    1.3.6 (09/10/2017)
 *    	- Removed old style fingerprint to eliminate conflicts with other generic sensors. 
 *
 *    1.3.5 (09/01/2017)
 *    	- Added workaround for SmartThings breaking the convertTemperatureIfNeeded function for precision 0.
 *    	- Added +52Ā°F offset when the temperature is <= -21Ā°F to correct a firmware bug. 
 *
 *    1.3.2 (04/23/2017)
 *    	- SmartThings broke parse method response handling so switched to sendhubaction.
 *
 *    1.3.1 (04/20/2017)
 *      - Added fingerprint.
 *      - Added workaround for ST Health Check bug.
 *
 *    1.3 (03/12/2017)
 *      - Added Health Check.
 *
 *    1.2.1 (07/31/2016)
 *      - Fix iOS UI bug with tamper tile.
 *      - Removed secondary tile.
 *
 *    1.1 (06/17/2016)
 *      - Fixed tamper detection
 *
 *    1.0 (06/17/2016)
 *      - Initial Release 
 *
 *  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.
 *
 */
metadata {
	definition (name:"GoControl Motion Sensor", namespace:"krlaframboise", author: "Kevin LaFramboise") {
		capability "Sensor"
		capability "Battery"
		capability "Motion Sensor"
		capability "Temperature Measurement"		
		capability "Tamper Alert"
		capability "Refresh"
		capability "Configuration"
		capability "Health Check"

		attribute "lastCheckin", "string"
 
		fingerprint mfr:"014F", prod:"2002", model:"0203"
 
		// fingerprint deviceId:"0x2001", inClusters:"0x71, 0x85, 0x80, 0x72, 0x30, 0x86, 0x31, 0x70, 0x84"
	}

	preferences {		
		input "temperatureOffset", "number",
			title: "Temperature Offset:\n(Allows you to adjust the temperature being reported if it's always high or low by a specific amount.  Example: Enter -3 to make it report 3Ā° lower or enter 3 to make it report 3Ā° higher.)",
			range: "-100..100",
			defaultValue: tempOffsetSetting,
			displayDuringSetup: true,
			required: false
		input "temperatureThreshold", "number",
			title: "Temperature Change Threshold:\n(You can use this setting to prevent the device from bouncing back and forth between the same two temperatures.  Example:  If the device is repeatedly reporting 68Ā° and 69Ā°, you can change this setting to 2 and it won't report a new temperature unless 68Ā° changes to 66Ā° or 70Ā°.)",
			range: "1..100",
			defaultValue: tempThresholdSetting,
			displayDuringSetup: true,
			required: false
		input "retriggerWaitTime", "number", 
			title: "Re-Trigger Wait Time (Minutes)\n(When the device detects motion, it waits for at least 1 minute of inactivity before sending the inactive event.  The default re-trigger wait time is 3 minutes.)", 
			range: "1..255", 
			defaultValue: retriggerWaitTimeSetting, 
			displayDuringSetup: true,
			required: false
		input "checkinInterval", "enum",
			title: "Checkin Interval:",
			defaultValue: checkinIntervalSetting,
			required: false,
			displayDuringSetup: true,
			options: checkinIntervalOptions.collect { it.name }
		input "reportBatteryEvery", "enum",
			title: "Battery Reporting Interval:",
			defaultValue: batteryReportingIntervalSetting,
			required: false,
			displayDuringSetup: true,
			options: checkinIntervalOptions.collect { it.name }
		input "debugOutput", "bool", 
			title: "Enable debug logging?", 
			defaultValue: false, 
			displayDuringSetup: true, 
			required: false
	}
}

def updated() {	
	if (!isDuplicateCommand(state.lastUpdated, 3000)) {
		state.lastUpdated = new Date().time
		logTrace "updated()"

		refresh()
	}
}

def configure() {	
	logTrace "configure()"
	def cmds = []
	
	if (!device.currentValue("motion")) {
		sendEvent(name: "motion", value: "active", isStateChange: true, displayed: false)
	}
	
	if (!state.isConfigured) {
		logTrace "Waiting 1 second because this is the first time being configured"
		// Give inclusion time to finish.
		cmds << "delay 1000"			
	}
	
	initializeCheckin()
	
	cmds += [
		wakeUpIntervalSetCmd(checkinIntervalSettingMinutes),
		retriggerWaitTimeSetCmd(),
		batteryGetCmd(),
		temperatureGetCmd()
	]
	return delayBetween(cmds, 250)
}

private initializeCheckin() {
	// Set the Health Check interval so that it can be skipped once plus 2 minutes.
	def checkInterval = ((checkinIntervalSettingMinutes * 2 * 60) + (2 * 60))
	
	sendEvent(name: "checkInterval", value: checkInterval, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
}

def refresh() {	
	clearTamperDetected()
	logDebug "The re-trigger wait time will be sent to the device the next time it wakes up.  If you want this change to happen immediately, open the back cover of the device until the red light turns solid and then close it."
	state.pendingConfig = true	
}

private clearTamperDetected() {	
	if (device.currentValue("tamper") != "clear") {
		logDebug "Resetting Tamper"
		sendEvent(getTamperEventMap("clear"))			
	}
}

private retriggerWaitTimeSetCmd() {
	logTrace "Setting re-trigger wait time to ${retriggerWaitTimeSetting} minutes"
	
	return zwave.configurationV1.configurationSet(scaledConfigurationValue: retriggerWaitTimeSetting, parameterNumber: 1, size: 1).format()	
}

private wakeUpIntervalSetCmd(minutesVal) {
	state.checkinIntervalMinutes = minutesVal
	logTrace "wakeUpIntervalSetCmd(${minutesVal})"
	
	return zwave.wakeUpV2.wakeUpIntervalSet(seconds:(minutesVal * 60), nodeid:zwaveHubNodeId).format()
}

private wakeUpNoMoreInfoCmd() {
	return zwave.wakeUpV2.wakeUpNoMoreInformation().format()
}

private temperatureGetCmd() {
	return zwave.sensorMultilevelV2.sensorMultilevelGet().format()
}

private batteryGetCmd() {
	return zwave.batteryV1.batteryGet().format()
}


def parse(String description) {	
	// log.trace "$description"
	def result = []
	
	sendEvent(name: "lastCheckin", value: convertToLocalTimeString(new Date()), displayed: false, isStateChange: true)
	
	def cmd = zwave.parse(description, [0x71: 2, 0x80: 1, 0x30: 1, 0x31: 2, 0x70: 1, 0x84: 1])
	if (cmd) {
		result += zwaveEvent(cmd)
	}
	else {
		logDebug "Unknown Description: $desc"
	}
	return result
}

def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd)
{
	logTrace "WakeUpNotification"
	def result = []

	if (state.pendingConfig) {
		state.pendingConfig = false
		result += configure()
	}
	else if (canReportBattery()) {
		result << batteryGetCmd()
	}
	if (result) {
		result << "delay 2000"
	}
	result << wakeUpNoMoreInfoCmd()
	return sendResponse(result)
}

private sendResponse(cmds) {
	def actions = []
	cmds?.each { cmd ->
		actions << new hubitat.device.HubAction(cmd)
	}	
	sendHubCommand(actions)
	return []
}

private canReportBattery() {
	def reportEveryMS = (batteryReportingIntervalSettingMinutes * 60 * 1000)
		
	return (!state.lastBatteryReport || ((new Date().time) - state.lastBatteryReport > reportEveryMS)) 
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {	
	def motionVal = cmd.value ? "active" : "inactive"
	def desc = "Motion is $motionVal"
	logDebug "$desc"
	def result = []
	result << createEvent(name: "motion", 
			value: motionVal, 
			isStateChange: true, 
			displayed: true, 
			descriptionText: "$desc")
	return result
}

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
	logTrace "BatteryReport: $cmd"
	def val = (cmd.batteryLevel == 0xFF ? 1 : cmd.batteryLevel)
	if (val > 100) {
		val = 100
	}
	state.lastBatteryReport = new Date().time	
	logDebug "Battery ${val}%"
	
	def isNew = (device.currentValue("battery") != val)
			
	def result = []
	result << createEvent(name: "battery", value: val, unit: "%", display: isNew, isStateChange: isNew)

	return result
}

def zwaveEvent(hubitat.zwave.commands.alarmv2.AlarmReport cmd) {
	def result = []
	if (cmd.alarmType == 7 && cmd.alarmLevel == 0xFF && cmd.zwaveAlarmEvent == 3) {
		logDebug "Tampering Detected"
		result << createEvent(getTamperEventMap("detected"))
	}	
	return result
}

def getTamperEventMap(val) {
	[
		name: "tamper", 
		value: val, 
		isStateChange: true, 
		displayed: (val == "detected"),
		descriptionText: "Tamper is $val"
	]
}

def zwaveEvent(hubitat.zwave.Command cmd) {
	logDebug "Unknown Command: $cmd"
	return []
}

def zwaveEvent(hubitat.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd){
	// logTrace "SensorMultilevelReport: $cmd"
	def result = []
	
	if (cmd.sensorType == 1) {
		def cmdScale = cmd.scale == 1 ? "F" : "C"
		
		// Workaround for firmware bug that adds a -52Ā°F offset when temperature drops below 32Ā°F
		def sensorVal = (cmd.scaledSensorValue <= -21) ? cmd.scaledSensorValue + 52 : cmd.scaledSensorValue 
		
		def temp = "${convertTemperatureIfNeeded(sensorVal, cmdScale, cmd.precision)}"
		def newTemp = safeToInt(temp.endsWith(".") ? temp.replace(".", "") : temp)

		if (tempOffsetSetting != 0) {
			newTemp = (newTemp + tempOffsetSetting)
			logDebug "Adjusted temperature by ${tempOffsetSetting}Ā°"
		}		
		
		def highTemp = (getCurrentTemp() + tempThresholdSetting)
		def lowTemp = (getCurrentTemp() - tempThresholdSetting)
		if (newTemp >= highTemp || newTemp <= lowTemp) {
			result << createEvent(
				name: "temperature",
				value: newTemp,
				unit: getTemperatureScale(),
				isStateChange: true,
				displayed: true)
		}
		else {
			logDebug "Ignoring new temperature of $newTempĀ° because the change is within the ${tempThresholdSetting}Ā° threshold."
		}
	}
	return result
}

private getRetriggerWaitTimeSetting() {
	return safeToInt(settings?.retriggerWaitTime, 3)
}

private getCurrentTemp() {
	return safeToInt(device.currentValue("temperature"), 0)
}

private getTempThresholdSetting() {
	return safeToInt(settings?.temperatureThreshold, 1)
}

private getTempOffsetSetting() {
	return safeToInt(settings?.temperatureOffset, 0)
}

// Settings
private getCheckinIntervalSettingMinutes() {
	return convertOptionSettingToInt(checkinIntervalOptions, checkinIntervalSetting) ?: 360
}

private getCheckinIntervalSetting() {
	return settings?.checkinInterval ?: findDefaultOptionName(checkinIntervalOptions)
}

private getBatteryReportingIntervalSettingMinutes() {
	return convertOptionSettingToInt(checkinIntervalOptions, batteryReportingIntervalSetting) ?: 720
}

private getBatteryReportingIntervalSetting() {
	return settings?.reportBatteryEvery ?: findDefaultOptionName(checkinIntervalOptions)
}

private getCheckinIntervalOptions() {
	[
		[name: "10 Minutes", value: 10],
		[name: "15 Minutes", value: 15],
		[name: "30 Minutes", value: 30],
		[name: "1 Hour", value: 60],
		[name: "2 Hours", value: 120],
		[name: "3 Hours", value: 180],
		[name: formatDefaultOptionName("6 Hours"), value: 360],
		[name: "9 Hours", value: 540],
		[name: "12 Hours", value: 720],
		[name: "18 Hours", value: 1080],
		[name: "24 Hours", value: 1440]
	]
}

private convertOptionSettingToInt(options, settingVal) {
	return safeToInt(options?.find { "${settingVal}" == it.name }?.value, 0)
}

private formatDefaultOptionName(val) {
	return "${val}${defaultOptionSuffix}"
}

private findDefaultOptionName(options) {
	def option = options?.find { it.name?.contains("${defaultOptionSuffix}") }
	return option?.name ?: ""
}

private getDefaultOptionSuffix() {
	return "   (Default)"
}

private safeToInt(val, defaultVal=-1) {
	return "${val}"?.isInteger() ? "${val}".toInteger() : defaultVal
}

private convertToLocalTimeString(dt) {
	def timeZoneId = location?.timeZone?.ID
	if (timeZoneId) {
		return dt.format("MM/dd/yyyy hh:mm:ss a", TimeZone.getTimeZone(timeZoneId))
	}
	else {
		return "$dt"
	}	
}

private isDuplicateCommand(lastExecuted, allowedMil) {
	!lastExecuted ? false : (lastExecuted + allowedMil > new Date().time) 
}

private logDebug(msg) {
	if (settings?.debugOutput || settings?.debugOutput == null) {
		log.debug "$msg"
	}
}

private logTrace(msg) {
	// log.trace "$msg"
}
1 Like

@mfridman

Iā€™m sure @JasonJoelā€™s port will work. But for future reference:

1 Like

I will try it out.

1 Like

It's a simple handler so the standard port method like @JasonJoel posted should work, but that's a really old handler that has a workaround for something SmartThings broke at one point so you might also have to search for:

return sendResponse(result)

and replace it with:

return response(result)

3 Likes

I loaded it up and here is a message I got in the log:

2021-02-15 11:40:22.051 pm errorgroovy.lang.MissingMethodException: No signature of method: user_driver_krlaframboise_GoControl_Motion_Sensor_430.sendHubCommand() is applicable for argument types: (java.util.ArrayList) values: [[delay 1000, delay 250, 8404002A3001, delay 250, 7004010103, ...]] Possible solutions: sendHubCommand(hubitat.device.HubAction), sendHubCommand(hubitat.device.HubMultiAction) on line 205 (parse)

which seems to come from this part:

private response(cmds) {
	def actions = []
	cmds?.each { cmd ->
		actions << new hubitat.device.HubAction(cmd)
	}	
	sendHubCommand(actions)
	return []
}

@JasonJoel @krlaframboise Do I just remove the sendHubCommand(actions) line?

Off the top of my head, try changing this:

actions << new hubitat.device.HubAction(cmd)

to this:

actions << new hubitat.device.HubAction(cmd, hubitat.device.Protocol.ZWAVE)

I didn't think about that the other day, but that is supposed to be explicitly defined now (in the not so distant past it was optional). There maybe other places it needs to be added, too - didn't go back and look.

If you made the change i mentioned above, which was in a different part of the handler, then you can delete that whole block of code.

Oh, so no need for private response(cmds) at all?

Not if you made the change I said above, but it looks like you renamed that function which isn't the change I said that needed to be made...

1 Like

Ah.... That's what was confusing me.

The ST handler was originally written to use the built-in response function, but ST broke that at one point so I swapped it with that sendResponse function.

Since the hub commands don't cleanly port and there's no need to use it there I figured it would just be easier to change that line back, but I forgot that the function would also need to be deleted...

2 Likes

OK, fixing it, thanks. So far the basics seem to be working fine--I get a reasonable temperature reading at 11F, motion detects motion, etc.

@JasonJoel how do we publish this so others can easily find it?

For personal use I just do it, and use it.

For sharing, though, typically I would ask the original author (if I can find them) if they are OK with the modified version being re-distributed. Usually they will be, as long as attribution is made/history is left in the headers section. If they are, then you either post it on github or if you don't have an account there then make a new thread here and post it in the thread. Make sure and use three ` (no spaces) before and after the code block so that it is formatted OK.

Hello - itā€™s years later. Iā€™m one of those completing my massive switch over to Hubitat, though Iā€™d started last December, because SmartThings deleted about 120 of my devices and associated routines in an accidental release. Anyhow, having a hard time using the generic driver with my single Go-Control WAPIRZ-1, @mfridman, would you be kind enough to share the version thatā€™s working for you (Iā€™d make an attempt at recreating the steps listed above but I still have 60 or so devices to go).

Thanks!

[Edited - actually it looks like I managed to get it working fine w/ the default Z-wave driver - so Iā€™m all set. Thanks!]