Homeseer HS-WD200 Dimmer Button Mapping

Thank you! I'll give it a shot later tonight.

Can I ask what you did to get the event to match the centralscene notification? I don't have the old code on front of me here, but the declaration looks the same. It looks like each of the events are overloaded using the class... But did the class definition change?

I didn't modify anything with CentralSceneNotification, so you you aren't missing anything there. The issue is that a SupervisionGet would contain an encapsulated command, which this driver was previously not handling (just discarding). Now it should unencapsulate those and pass the report, if any, along to whatever method would ordinarily handle the contents--which was apparently scenes (and probably more), explaining why those wouldn't work.

Getting close!

[dev:32](http://192.168.1.148/logs#dev32)2020-12-01 02:53:27.369 am [error](http://192.168.1.148/device/edit/32)groovy.lang.MissingMethodException: No signature of method: user_driver_homeseer_WD200__Dimmer_CUSTOM_387.command() is applicable for argument types: (hubitat.zwave.commands.supervisionv1.SupervisionReport) values: [SupervisionReport(moreStatusUpdates:false, reserved:0, sessionID:43, status:255, duration:0)] on line 213 (parse)

[dev:32](http://192.168.1.148/logs#dev32)2020-12-01 02:53:27.336 am [debug](http://192.168.1.148/device/edit/32)sceneNumber: 1 keyAttributes: 4

[dev:32](http://192.168.1.148/logs#dev32)2020-12-01 02:53:27.332 am [debug](http://192.168.1.148/device/edit/32)SupervisionGet: SupervisionGet(statusUpdates:false, reserved:0, sessionID:43, commandLength:5, commandClassIdentifier:91, commandIdentifier:3, commandByte:[91, 132, 1])

[dev:32](http://192.168.1.148/logs#dev32)2020-12-01 02:53:27.327 am [debug](http://192.168.1.148/device/edit/32)parse() for: zw device: 31, command: 6C01, payload: 2B 05 5B 03 5B 84 01 , isMulticast: false

(Seriously, I'm just gonna wire one of these up in a box with a plug and send it to you)

I did a quick search, and found an example with secure() instead of command() in that call, so I tried that.

Seems to have alleviated the error, but then this is the logging (top is latest log entry)...

[dev:32](http://192.168.1.148/logs#dev32)2020-12-01 03:08:34.282 am [debug](http://192.168.1.148/device/edit/32)Parse returned null for command null

[dev:32](http://192.168.1.148/logs#dev32)2020-12-01 03:08:34.241 am [debug](http://192.168.1.148/device/edit/32)sceneNumber: 1 keyAttributes: 4

[dev:32](http://192.168.1.148/logs#dev32)2020-12-01 03:08:34.225 am [debug](http://192.168.1.148/device/edit/32)SupervisionGet: SupervisionGet(statusUpdates:false, reserved:0, sessionID:45, commandLength:5, commandClassIdentifier:91, commandIdentifier:3, commandByte:[93, 132, 1])

[dev:32](http://192.168.1.148/logs#dev32)2020-12-01 03:08:34.171 am [debug](http://192.168.1.148/device/edit/32)parse() for: zw device: 31, command: 6C01, payload: 2D 05 5B 03 5D 84 01 , isMulticast: false

Not sure if that top entry means things worked or not. Doesn't seem like Webcore responded.

OK, sorry. The "secure" seems to have fixed it. I am not sure why that second parse() is called with nulls, but I added some debugging and when I tap 3x this is definitely called...

def tapUp3Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▲▲▲")
	[name: "pushed", value: 5, descriptionText: "$device.displayName Tap-Up-3 (button 5) pushed", 
    isStateChange: true, type: "$buttonType"]
}

Of course, I am still seeing a problem. When I set and click this "push" tile in the device page, there are no log entries, but the state changes to the correct taps AND Webcore receives and processes the event.

HOWEVER, when I click the button physically, I see the logs but Webcore does not see the event.

Messing around a bit, it doesn't seem to be affected by $buttonType. So I am thinking that the physical press runs in a different context than the digital press? And so the event is not being captured by Webcore.

Let me know if you have an idea why that might be, otherwise I will be taking this over to the Webcore people.

I'll run it through a full test tomorrow and let you know how things look.

Thank you so much!

Thanks for figuring that out! I indeed used the wrong name for the name of that wrapper method (I've been editing drivers that use both of these names recently and slipped here). For anyone looking for an edited version, this is what I'd use:

(And note that if the status LEDs aren't working for you, the first thing I'd try is switching to the "Device" driver before this and running "Delete All States.")

/**
 *  HomeSeer HS-WD200+
 *
 *  Original Copyright 2018 HomeSeer
 *
 *  Modified from the work by DarwinsDen device handler for the WD100 version 1.03
 *
 *
 *  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.
 *
 *	Author: HomeSeer
 *	Date: 12/2017
 *   Modified: 12/2020 by RMoRobert
 *
 *	Changelog:
 *
 * 1.0	Initial Version (HomeSeer)
 * 1.0  Adaped for Hubitat by RMoRobert
 *
 *
 *   Button Mappings:
 *
 *   ACTION          BUTTON#    BUTTON ACTION
 *   Single-Tap Up     1        pushed
 *   Single-Tap Down   2        pushed
 *   Double-Tap Up     3        pushed
 *   Double-Tap Down   4        pushed
 *   Triple-Tap Up     5        pushed
 *   Triple-Tap Down   6        pushed
 *   4 taps up         7        pushed
 *   4 taps down       8       pushed
 *   5 taps up         9       pushed
 *   5 taps down       10       pushed
 *   Hold Up           1 	     held
 *   Hold Down         2 	     held
 *
 */

import groovy.transform.Field

@Field static final Map commandClassVersions = [
   0x20: 1,   //Basic
   0x26: 1,   //SwitchMultiLevel
   0x5B: 1,   //CentralScene
   0x70: 1    //Configuration
]

metadata {
	definition (name: "WD200+ Dimmer", namespace: "homeseer", author: "support@homeseer.com") {
		capability "Switch Level"
		capability "Actuator"
		capability "Indicator"
		capability "Switch"
		capability "Polling"
		capability "Refresh"
		capability "Sensor"
      capability "PushableButton"
      capability "HoldableButton"
      //capability "ReleasableButton"
      capability "Configuration"

      
      command "push", ["NUMBER"]
      command "hold", ["NUMBER"]
      //command "release", ["NUMBER"]

      command "setStatusLed", [[name:"LED*", type: "NUMBER", description: "LED number: 1-7 (bottom to top)"],
                               [name:"Color*", type: "NUMBER", description: "Color: 0=off, 1=red, 2=green, 3=blue, 4=magenta, 5=yellow, 6=cyan, 7=white"],
                               [name:"Blink", type: "NUMBER", description: "Blink: 0=no, 1=yes"]]
      command "setSwitchModeNormal"
      command "setSwitchModeStatus"
      command "setDefaultColor", [[name:"Color*", type: "NUMBER", description: "Color: 0=white, 1=red, 2=green, 3=blue, 4=magenta, 5=yellow, 6=cyan"]]
      
      fingerprint mfr: "000C", prod: "4447", model: "3036"
}
   preferences {      
      input "doubleTapToFullBright", "bool", title: "Double-tap up sets to full brightness",  defaultValue: false,  displayDuringSetup: true, required: false	       
      input "singleTapToFullBright", "bool", title: "Single-tap up sets to full brightness",  defaultValue: false,  displayDuringSetup: true, required: false	
      input "doubleTapDownToDim",    "bool", title: "Double-tap down sets to 25% level",      defaultValue: false,  displayDuringSetup: true, required: false	       
      input "reverseSwitch", "bool", title: "Reverse Switch",  defaultValue: false,  displayDuringSetup: true, required: false
      input "bottomled", "bool", title: "Bottom LED On if Load is Off",  defaultValue: false,  displayDuringSetup: true, required: false
      input "localcontrolramprate", "number", title: "Press Configure button after changing preferences\n\nLocal Ramp Rate: Duration (0-90) (1=1 sec) [default: 3]", defaultValue: 3,range: "0..90", required: false
      input "remotecontrolramprate", "number", title: "Remote Ramp Rate: duration (0-90) (1=1 sec) [default: 3]", defaultValue: 3, range: "0..90", required: false   
      input "color", "enum", title: "Default LED Color", options: ["White", "Red", "Green", "Blue", "Magenta", "Yellow", "Cyan"], description: "Select Color", required: false
      input "enableDebug", "bool", title: "Enable debug logging"
      input "enableInfo", "bool", title: "Enable descriptionText logging"
   }
}

def parse(String description) {
	def result = null
   if (enableDebug) log.debug "parse() for: $description"
    if (description != "updated") {
	    def cmd = zwave.parse(description, commandClassVersions)	
        if (cmd) {
		    result = zwaveEvent(cmd)
	    }
    }
    if (!result){
        if (enableDebug) log.debug "Parse returned ${result} for command ${cmd}"
    }
    else {
		if (enableDebug) log.debug "Parse returned ${result}"
    }   
	return result
}

String secure(String cmd){
   return zwaveSecureEncap(cmd)
}

String secure(hubitat.zwave.Command cmd){
   return zwaveSecureEncap(cmd)
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) {
   dimmerEvents(cmd)
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {
	 dimmerEvents(cmd)
}

def zwaveEvent(hubitat.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd) {
   dimmerEvents(cmd) 
}

def zwaveEvent(hubitat.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd) {
	dimmerEvents(cmd)
}

private dimmerEvents(hubitat.zwave.Command cmd) {
	def value = (cmd.value ? "on" : "off")
	def result = [createEvent(name: "switch", value: value)]
   if (state.lastLevel != cmd.value && enableInfo) log.info "$device.displayName level is ${cmd.value}%"
   if (value != device.currentValue("switch") && enableInfo) log.info "$device.displayName switch is ${value}" 
   state.lastLevel = cmd.value
	if (cmd.value && cmd.value <= 100) {
		result << createEvent(name: "level", value: cmd.value, unit: "%")   
	}
	return result
}

def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
	if (enableDebug) log.debug "ConfigurationReport $cmd"
	def value = "when off"
	if (cmd.configurationValue[0] == 1) {value = "when on"}
	if (cmd.configurationValue[0] == 2) {value = "never"}
	createEvent([name: "indicatorStatus", value: value])
}

def zwaveEvent(hubitat.zwave.commands.hailv1.Hail cmd) {
	createEvent([name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false])
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
	if (enableDebug) log.debug "manufacturerId:   ${cmd.manufacturerId}"
	if (enableDebug) log.debug "manufacturerName: ${cmd.manufacturerName}"
    state.manufacturer=cmd.manufacturerName
	if (enableDebug) log.debug "productId:        ${cmd.productId}"
	if (enableDebug) log.debug "productTypeId:    ${cmd.productTypeId}"
	def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
	updateDataValue("MSR", msr)	
    setFirmwareVersion()
    createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}

def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {	
    //updateDataValue("applicationVersion", "${cmd.applicationVersion}")
    if (enableDebug) log.debug ("received Version Report")
    if (enableDebug) log.debug "applicationVersion:      ${cmd.applicationVersion}"
    if (enableDebug) log.debug "applicationSubVersion:   ${cmd.applicationSubVersion}"
    state.firmwareVersion=cmd.applicationVersion+'.'+cmd.applicationSubVersion
    if (enableDebug) log.debug "zWaveLibraryType:        ${cmd.zWaveLibraryType}"
    if (enableDebug) log.debug "zWaveProtocolVersion:    ${cmd.zWaveProtocolVersion}"
    if (enableDebug) log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
    setFirmwareVersion()
    createEvent([descriptionText: "Firmware V"+state.firmwareVersion, isStateChange: false])
}

def zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv2.FirmwareMdReport cmd) { 
    if (enableDebug) log.debug ("received Firmware Report")
    if (enableDebug) log.debug "checksum:       ${cmd.checksum}"
    if (enableDebug) log.debug "firmwareId:     ${cmd.firmwareId}"
    if (enableDebug) log.debug "manufacturerId: ${cmd.manufacturerId}"
    [:]
}

def zwaveEvent(hubitat.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd) {
   if (enableInfo) log.info "$device.displayName level is on"
	[createEvent(name:"switch", value:"on"), response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))]
}

def zwaveEvent(hubitat.zwave.Command cmd) {
	// Handles all Z-Wave commands we aren't interested in
   if (enableDebug) log.debug "skip: $cmd"
	return []
}

def zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd) {
   if (enableDebug) log.debug "SupervisionGet: $cmd"
  hubitat.zwave.Command encapCmd = cmd.encapsulatedCommand(commandClassVersions)
  sendHubCommand(new hubitat.device.HubAction(secure(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0)), hubitat.device.Protocol.ZWAVE))
  if (encapCmd) {
     return zwaveEvent(encapCmd)
  }
}

def on() {
	//sendEvent(tapUp1Response("digital"))
	delayBetween([
			secure(zwave.basicV1.basicSet(value: 0xFF)),
			secure(zwave.switchMultilevelV1.switchMultilevelGet())
	], 5000)
}

def off() {
	//sendEvent(tapDown1Response("digital"))
	delayBetween([
			secure(zwave.basicV1.basicSet(value: 0x00)),
			secure(zwave.switchMultilevelV1.switchMultilevelGet())
	],5000)
}

List<String> setLevel(value) {
	if (enableDebug) log.debug "setLevel >> value: $value"
	def valueaux = value as Integer
	def level = Math.max(Math.min(valueaux, 99), 0)
   if (level <= 0) {      
      return delayBetween([
            seucre(zwave.basicV1.basicSet(value: 0x00)),
            secure(zwave.switchMultilevelV1.switchMultilevelGet())
      ],5000)
   }
   else {
      def result = []
      result += response(secure(zwave.basicV1.basicSet(value: level)))
      result += response(secure("delay 5000"))
      result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
      result += response(secure("delay 5000"))
      result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
      return result
   }
}

   List<String> setLevel(value, duration) {
	if (enableDebug) log.debug "setLevel >> value: $value, duration: $duration"
  def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
  return [
     secure(zwave.switchMultilevelV2.switchMultilevelSet(value: value < 100 ? value : 99, dimmingDuration: dimmingDuration))
  ]
   }

/*
 *  Set dimmer to status mode, then set the color of the individual LED
 *
 *  led = 1-7
 *  color = 0=0ff
 *          1=red
 *          2=green
 *          3=blue
 *          4=magenta
 *          5=yellow
 *          6=cyan
 *          7=white
 */
def setStatusLed(led, color, blink) {
   if (enableDebug) log.debug "setStatusLED >> led: $led, color: $color, blink: $blink"
    List cmds= []
    
    if(state.statusled1==null) {    	
    	   state.statusled1=0
        state.statusled2=0
        state.statusled3=0
        state.statusled4=0
        state.statusled5=0
        state.statusled6=0
        state.statusled7=0
        state.blinkval=0
    }
    
    state."statusled{$led}" = color
   
    if(state.statusled1==0 && state.statusled2==0 && state.statusled3==0 && state.statusled4==0 && state.statusled5==0 && state.statusled6==0 && state.statusled7==0)
    {
    	// no LEDS are set, put back to NORMAL mode
        cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: 0, parameterNumber: 13, size: 1))
    }
    else
    {
    	// at least one LED is set, put to status mode
        cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: 1, parameterNumber: 13, size: 1))
        // set the LED to color
        cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: color.toInteger(), parameterNumber: (led.toInteger() + 20), size: 1))
        // check if LED should be blinking
        Integer blinkval = state.blinkval ?: 0
        if(blink)
        {
            switch(led) {
            	case 1:
                	blinkval = blinkval | 0x1
                    break
                case 2:
                	blinkval = blinkval | 0x2
                    break
                case 3:
                	blinkval = blinkval | 0x4
                    break
                case 4:
                	blinkval = blinkval | 0x8
                    break
                case 5:
                	blinkval = blinkval | 0x10
                    break
                case 6:
                	blinkval = blinkval | 0x20
                    break
                case 7:
                	blinkval = blinkval | 0x40
                    break
            }
        	cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: blinkval, parameterNumber: 31, size: 1))
            // set blink speed, also enables blink, 1=100ms blink frequency
            cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: 5, parameterNumber: 30, size: 1))
            state.blinkval = blinkval
        }
        else {
        	switch(led.toInteger()) {
            	case 1:
                	blinkval = blinkval & 0xFE
                    break
                case 2:
                	blinkval = blinkval & 0xFD
                    break
                case 3:
                	blinkval = blinkval & 0xFB
                    break
                case 4:
                	blinkval = blinkval & 0xF7
                    break
                case 5:
                	blinkval = blinkval & 0xEF
                    break
                case 6:
                	blinkval = blinkval & 0xDF
                    break
                case 7:
                	blinkval = blinkval & 0xBF
                    break
            }
            cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: blinkval, parameterNumber: 31, size: 1))
            state.blinkval = blinkval
        }
    }
 	return delayBetween(cmds, 500)
}

/*
 * Set Dimmer to Normal dimming mode (exit status mode)
 *
 */
def setSwitchModeNormal() {
	def cmds= []
    cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 13, size: 1))
    delayBetween(cmds, 500)
}

/*
 * Set Dimmer to Status mode (exit normal mode)
 *
 */
def setSwitchModeStatus() {
	def cmds= []
    cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 13, size: 1))
    delayBetween(cmds, 500)
}

/*
 * Set the color of the LEDS for normal dimming mode, shows the current dim level
 */
def setDefaultColor(color) {
	def cmds= []
    cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [color], parameterNumber: 14, size: 1))
    delayBetween(cmds, 500)
}


def poll() {
	secure(zwave.switchMultilevelV1.switchMultilevelGet())
}

def refresh() {
	if (enableDebug) log.debug "refresh() called"
  configure()
}

def zwaveEvent(hubitat.zwave.commands.centralscenev1.CentralSceneNotification cmd) {
    if (enableDebug) log.debug("sceneNumber: ${cmd.sceneNumber} keyAttributes: ${cmd.keyAttributes}")
    def result = []
    
    switch (cmd.sceneNumber) {
      case 1:
          // Up
          switch (cmd.keyAttributes) {
              case 0:
                   // Press Once
                  result += createEvent(tapUp1Response("physical"))  
                  result += createEvent([name: "switch", value: "on", type: "physical"])
       
                  if (singleTapToFullBright)
                  {
                     result += setLevel(99)
                     result += response(secure("delay 5000"))
                     result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
                  } 
                  break
              case 1:
                  result=createEvent([name: "switch", value: "on", type: "physical"])
                  break
              case 2:
                  // Hold
                  result += createEvent(holdUpResponse("physical"))  
                  result += createEvent([name: "switch", value: "on", type: "physical"])    
                  break
              case 3: 
                  // 2 Times
                  result +=createEvent(tapUp2Response("physical"))
                  if (doubleTapToFullBright)
                  {
                     result += setLevel(99)
                     result += response(secure("delay 5000"))
                     result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
                  }                    
                  break
              case 4:
                  // 3 times
                  result=createEvent(tapUp3Response("physical"))
                  break
              case 5:
                  // 4 times
                  result=createEvent(tapUp4Response("physical"))
                  break
              case 6:
                  // 5 times
                  result=createEvent(tapUp5Response("physical"))
                  break
              default:
                  if (enableDebug) log.debug ("unexpected up press keyAttribute: $cmd.keyAttributes")
          }
          break
          
      case 2:
          // Down
          switch (cmd.keyAttributes) {
              case 0:
                  // Press Once
                  result += createEvent(tapDown1Response("physical"))
                  result += createEvent([name: "switch", value: "off", type: "physical"]) 
                  break
              case 1:
                  result=createEvent([name: "switch", value: "off", type: "physical"])
                  break
              case 2:
                  // Hold
                  result += createEvent(holdDownResponse("physical"))
                  result += createEvent([name: "switch", value: "off", type: "physical"]) 
                  break
              case 3: 
                  // 2 Times
                  result+=createEvent(tapDown2Response("physical"))
                  if (doubleTapDownToDim)
                  {
                     result += setLevel(25)
                     result += response(secure("delay 5000"))
                     result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
                  }  
                  break
              case 4:
                  // 3 Times
                  result=createEvent(tapDown3Response("physical"))
                  break
              case 5:
                  // 4 Times
                  result=createEvent(tapDown4Response("physical"))
                  break
              case 6:
                  // 5 Times
                  result=createEvent(tapDown5Response("physical"))
                  break
              default:
                  if (enableDebug) log.debug ("unexpected down press keyAttribute: $cmd.keyAttributes")
           } 
           break
           
      default:
           // unexpected case
           if (enableDebug)  log.debug ("unexpected scene: $cmd.sceneNumber")
   }  
   return result
}

def tapUp1Response(String buttonType) {
   sendEvent(name: "status" , value: "Tap ▲")
	[name: "pushed", value: 1, descriptionText: "$device.displayName Tap-Up-1 (button 1) pushed", 
       isStateChange: true, type: "$buttonType"]
}

def tapDown1Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼")
	[name: "pushed", value: 2, descriptionText: "$device.displayName Tap-Down-1 (button 2) pushed", 
      isStateChange: true, type: "$buttonType"]
}

def tapUp2Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▲▲")
	[name: "pushed", value: 3, descriptionText: "$device.displayName Tap-Up-2 (button 3) pushed", 
       isStateChange: true, type: "$buttonType"]
}

def tapDown2Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼▼")
	[name: "pushed", value: 4, descriptionText: "$device.displayName Tap-Down-2 (button 4) pushed", 
      isStateChange: true, type: "$buttonType"]
}

def tapUp3Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▲▲▲")
	[name: "pushed", value: 5, descriptionText: "$device.displayName Tap-Up-3 (button 5) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapUp4Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▲▲▲▲")
	[name: "pushed", value: 7, descriptionText: "$device.displayName Tap-Up-4 (button 7) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapUp5Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▲▲▲▲▲")
	[name: "pushed", value: 9, descriptionText: "$device.displayName Tap-Up-5 (button 9) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapDown3Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼▼▼")
	[name: "pushed", value: 6, descriptionText: "$device.displayName Tap-Down-3 (button 6) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapDown4Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼▼▼▼")
	[name: "pushed", value: 8, descriptionText: "$device.displayName Tap-Down-3 (button 8) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapDown5Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼▼▼▼▼")
	[name: "pushed", value: 10, descriptionText: "$device.displayName Tap-Down-3 (button 10) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def holdUpResponse(String buttonType) {
    sendEvent(name: "status" , value: "Hold ▲")
	[name: "held", value: 1, descriptionText: "$device.displayName Hold-Up (button 1) held", 
    isStateChange: true, type: "$buttonType"]
}

def holdDownResponse(String buttonType) {
    sendEvent(name: "status" , value: "Hold ▼")
	[name: "held", value: 2, descriptionText: "$device.displayName Hold-Down (button 2) held", 
    isStateChange: true, type: "$buttonType"]
}

def push(Number button) {
   switch (button as Integer) {
      case 1:
         sendEvent(tapUp1Response("digital"))
         break
      case 2:
         sendEvent(tapDown1Response("digital"))
         break
      case 3:
         sendEvent(tapUp2Response("digital"))
         break
      case 4:
         sendEvent(tapDown2Response("digital"))
         break
      case 5:
         sendEvent(tapUp3Response("digital"))
         break
      case 6:
         sendEvent(tapDown3Response("digital"))
         break
      case 7:
         sendEvent(tapUp4Response("digital"))
         break
      case 8:
         sendEvent(tapDown4Response("digital"))
         break
      case 9:
         sendEvent(tapUp5Response("digital"))
         break
      case 10:
         sendEvent(tapDown5Response("digital"))
         break
      default:
         log.warn "Invalid button number in push(): $button"
   }
}

def hold(Number button) {
   if (button as Integer == 1) {
	   sendEvent(holdUpResponse("digital"))
   }
   else if (button as Integer == 2) {
      sendEvent(holdDownResponse("digital"))
   }
   else {
      log.warn "Invalid button in hold(): $button"
   }
}
/*
def release(Number button) {
   if (button as Integer == 1) {
	   sendEvent(releaseUpResponse("digital"))
   }
   else if (button as Integer == 2) {
      sendEvent(releaseDownResponse("digital"))
   }
   else {
      log.warn "Invalid button in release(): $button"
   }
}
*/


def setFirmwareVersion() {
   def versionInfo = ''
   if (state.manufacturer)
   {
      versionInfo=state.manufacturer+' '
   }
   if (state.firmwareVersion)
   {
      versionInfo=versionInfo+"Firmware V"+state.firmwareVersion
   }
   else 
   {
     versionInfo=versionInfo+"Firmware unknown"
   }   
   sendEvent(name: "firmwareVersion",  value: versionInfo, isStateChange: true, displayed: false)
}

def configure() {
   log.trace ("configure() called")
 
   sendEvent(name: "numberOfButtons", value: 10)
   def commands = []
   commands << setDimRatePrefs()
   commands << secure(zwave.switchMultilevelV1.switchMultilevelGet())
   commands << secure(zwave.manufacturerSpecificV1.manufacturerSpecificGet())
   commands << secure(zwave.versionV1.versionGet())
   return delayBetween(commands,500)
}

def setDimRatePrefs() 
{
   if (enableDebug) log.trace ("set prefs")
   def cmds = []

	if (color)
    {
        switch (color) {
        	case "White":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 14, size: 1))
                break
      		case "Red":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 14, size: 1))
                break
            case "Green":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [2], parameterNumber: 14, size: 1))
                break
            case "Blue":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [3], parameterNumber: 14, size: 1))
                break
            case "Magenta":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [4], parameterNumber: 14, size: 1))
                break
            case "Yellow":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [5], parameterNumber: 14, size: 1))
                break
            case "Cyan":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [6], parameterNumber: 14, size: 1))
                break
            
            
      	}
    }    
   
   if(localcontrolramprate != null) {
   		//if (enableDebug) log.debug localcontrolramprate
   		def localcontrolramprate = Math.max(Math.min(localcontrolramprate, 90), 0)
   		cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [localcontrolramprate], parameterNumber: 12, size: 1))
   }
   
   if(remotecontrolramprate != null) {
   		if (enableDebug) log.debug remotecontrolramprate
   		def remotecontrolramprate = Math.max(Math.min(remotecontrolramprate, 90), 0)
   		cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [remotecontrolramprate], parameterNumber: 11, size: 1))
   }
   
   if (reverseSwitch)
   {
       cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1))
   }
   else
   {
      cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1))
   }
   
   if (bottomled)
   {
       cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1))
   }
   else
   {
      cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1))
   }
   
   //Enable the following configuration gets to verify configuration in the logs
   //cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: 7))
   //cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: 8))
   //cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: 9))
   //cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: 10))
   
   return cmds
}
 
def updated()
{
   def cmds= []
   cmds << setDimRatePrefs
   delayBetween(cmds, 500)
}
3 Likes

Very cool, thanks.

Do you have any idea why it's not triggering webcore? I posted in their forums, but no response yet.

I am about to light up Rule Machine to see how it does.

I'm not sure, but if you get an event on the Hubitat side (check the "Events" button on the device page in Hubitat to be sure rather than going by logs), it should theoretically be usable by an app. WebCoRE, as you probably know, was originally written for SmartThings, and Hubitat's button model is a bit different, but the current HE fork has been adapted for that. I don't have any of the HomeSeer devices, but a quick test with a virtual button worked for me:

webCoRE piston screenshot

Testing this, Rule Machine also fails to capture a physical tap, but triggers with a digital push.

I added a log line here...

def tapUp3Response(String buttonType) {
    if (enableDebug) log.debug "Tap ▲▲▲"
    sendEvent(name: "status" , value: "Tap ▲▲▲")
	[name: "pushed", value: 5, descriptionText: "$device.displayName Tap-Up-3 (button 5) pushed", 
    isStateChange: true, type: "$buttonType"]
}

Here is the log if I use the digital button to push 5:

[dev:32][...][debug] Tap ▲▲▲

Here's the log if I press it physically (latest at top):

[dev:32][...][debug] Parse returned null for command null

[dev:32][...][debug] Tap ▲▲▲

[dev:32][...][debug] sceneNumber: 1 keyAttributes: 4

[dev:32][...][debug] SupervisionGet: SupervisionGet(statusUpdates:false, reserved:0, sessionID:55, commandLength:5, commandClassIdentifier:91, commandIdentifier:3, commandByte:[103, 132, 1])

[dev:32][...][debug] parse() for: zw device: 31, command: 6C01, payload: 37 05 5B 03 67 84 01 , isMulticast: false

So it's clearly handling the ZWave event and invoking the same function to send the "pushed value 5 event.

Differences:

  1. Button Type; but I have recoded this to send either button type and neither rule machine or Webcore cares, they still only process the digital press

  2. The physical press seems to generate a second null event?

So it seems to me that this is probably not a webcore issue.

Lastly, if I insert the debug statement AFTER that last line in the squate bracket above, I get this error:

[dev:32](http://192.168.1.148/logs#dev32)2020-12-01 03:14:36.779 pm [error](http://192.168.1.148/device/edit/32)java.lang.NullPointerException: Cannot get property 'name' on null object on line 599 (push)

I am not familiar with Groovy and Java that much, what is that bracket statement and why can't I insert code after it in that function? Is it possible that the second parse() call we see in the physical press is mucking with the event?

OK, I just did a physical 3-tap and then a "Push 5" from the device page...

The bottom entry was the single status event generated by the three taps.

The top two entries are an identical status PLUS a Pushed 5 event.

So it appears that the Pushed 5 event is not being generated when this is a physical tap.

Name Value Unit Description Text Source Type Date
pushed 5 Front Lights Tap-Up-3 (button 5) pushed DEVICE digital 2020-12-01 03:19:29.592 PM EST
status Tap ▲▲▲ DEVICE 2020-12-01 03:19:29.591 PM EST
status Tap ▲▲▲ DEVICE 2020-12-01 03:18:43.682 PM EST

(Aside: I can't get Webcore to trigger on a status event, but that seems like a webcore question)

AHA! Figured it out...

The Supervisionv1 handler was not returning the result, so a null was being passed back to the original zwaveEvent Call...

Code that works...

def zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd) {
  result = null
  hubitat.zwave.Command encapCmd = cmd.encapsulatedCommand(commandClassVersions)
  if (encapCmd) {
     result = zwaveEvent(encapCmd)
  }
  sendHubCommand(new hubitat.device.HubAction(secure(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0)), hubitat.device.Protocol.ZWAVE))
  return result
}

This would have been next to impossible for you to debug without a physical device.

You rock!

Working Revision:

/**
 *  HomeSeer HS-WD200+
 *
 *  Original Copyright 2018 HomeSeer
 *
 *  Modified from the work by DarwinsDen device handler for the WD100 version 1.03
 *
 *
 *  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.
 *
 *	Author: HomeSeer
 *	Date: 12/2017
 *   Modified: 11/2020
 *
 *	Changelog:
 *
 * 1.0	Initial Version (HomeSeer)
 * 1.0  Adaped for Hubitat by RMoRobert
 *
 *
 *   Button Mappings:
 *
 *   ACTION          BUTTON#    BUTTON ACTION
 *   Single-Tap Up     1        pushed
 *   Single-Tap Down   2        pushed
 *   Double-Tap Up     3        pushed
 *   Double-Tap Down   4        pushed
 *   Triple-Tap Up     5        pushed
 *   Triple-Tap Down   6        pushed
 *   4 taps up         7        pushed
 *   4 taps down       8       pushed
 *   5 taps up         9       pushed
 *   5 taps down       10       pushed
 *   Hold Up           1 	     held
 *   Hold Down         2 	     held
 *
 */

import groovy.transform.Field

@Field static final Map commandClassVersions = [
   0x20: 1,   //Basic
   0x26: 1,   //SwitchMultiLevel
   0x5B: 1,   //CentralScene
   0x70: 1    //Configuration
]

metadata {
	definition (name: "WD200+ Dimmer", namespace: "homeseer", author: "support@homeseer.com") {
		capability "Switch Level"
		capability "Actuator"
		capability "Indicator"
		capability "Switch"
		capability "Polling"
		capability "Refresh"
		capability "Sensor"
      capability "PushableButton"
      capability "HoldableButton"
      //capability "ReleasableButton"
      capability "Configuration"

      
      command "push", ["NUMBER"]
      command "hold", ["NUMBER"]
      //command "release", ["NUMBER"]

      command "setStatusLed", [[name:"LED*", type: "NUMBER", description: "LED number: 1-7 (bottom to top)"],
                               [name:"Color*", type: "NUMBER", description: "Color: 0=off, 1=red, 2=green, 3=blue, 4=magenta, 5=yellow, 6=cyan, 7=white"],
                               [name:"Blink", type: "NUMBER", description: "Blink: 0=no, 1=yes"]]
      command "setSwitchModeNormal"
      command "setSwitchModeStatus"
      command "setDefaultColor", [[name:"Color*", type: "NUMBER", description: "Color: 0=white, 1=red, 2=green, 3=blue, 4=magenta, 5=yellow, 6=cyan"]]
      
      fingerprint mfr: "000C", prod: "4447", model: "3036"
}
   preferences {      
      input "doubleTapToFullBright", "bool", title: "Double-tap up sets to full brightness",  defaultValue: false,  displayDuringSetup: true, required: false	       
      input "singleTapToFullBright", "bool", title: "Single-tap up sets to full brightness",  defaultValue: false,  displayDuringSetup: true, required: false	
      input "doubleTapDownToDim",    "bool", title: "Double-tap down sets to 25% level",      defaultValue: false,  displayDuringSetup: true, required: false	       
      input "reverseSwitch", "bool", title: "Reverse Switch",  defaultValue: false,  displayDuringSetup: true, required: false
      input "bottomled", "bool", title: "Bottom LED On if Load is Off",  defaultValue: false,  displayDuringSetup: true, required: false
      input "localcontrolramprate", "number", title: "Press Configure button after changing preferences\n\nLocal Ramp Rate: Duration (0-90) (1=1 sec) [default: 3]", defaultValue: 3,range: "0..90", required: false
      input "remotecontrolramprate", "number", title: "Remote Ramp Rate: duration (0-90) (1=1 sec) [default: 3]", defaultValue: 3, range: "0..90", required: false   
      input "color", "enum", title: "Default LED Color", options: ["White", "Red", "Green", "Blue", "Magenta", "Yellow", "Cyan"], description: "Select Color", required: false
      input "enableDebug", "bool", title: "Enable debug logging"
      input "enableInfo", "bool", title: "Enable descriptionText logging"
   }
}

def parse(String description) {
	def result = null
   if (enableDebug) log.debug "parse() for: $description"
    if (description != "updated") {
	    def cmd = zwave.parse(description, commandClassVersions)	
        if (cmd) {
		    result = zwaveEvent(cmd)
	    }
    }
    if (!result){
        if (enableDebug) log.debug "Parse returned ${result} for command ${cmd}"
    }
    else {
		if (enableDebug) log.debug "Parse returned ${result}"
    }   
	return result
}

String secure(String cmd){
   return zwaveSecureEncap(cmd)
}

String secure(hubitat.zwave.Command cmd){
   return zwaveSecureEncap(cmd)
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicReport cmd) {
   dimmerEvents(cmd)
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {
	 dimmerEvents(cmd)
}

def zwaveEvent(hubitat.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd) {
   dimmerEvents(cmd) 
}

def zwaveEvent(hubitat.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd) {
	dimmerEvents(cmd)
}

private dimmerEvents(hubitat.zwave.Command cmd) {
	def value = (cmd.value ? "on" : "off")
	def result = [createEvent(name: "switch", value: value)]
   if (state.lastLevel != cmd.value && enableInfo) log.info "$device.displayName level is ${cmd.value}%"
   if (value != device.currentValue("switch") && enableInfo) log.info "$device.displayName switch is ${value}" 
   state.lastLevel = cmd.value
	if (cmd.value && cmd.value <= 100) {
		result << createEvent(name: "level", value: cmd.value, unit: "%")   
	}
	return result
}

def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
	if (enableDebug) log.debug "ConfigurationReport $cmd"
	def value = "when off"
	if (cmd.configurationValue[0] == 1) {value = "when on"}
	if (cmd.configurationValue[0] == 2) {value = "never"}
	createEvent([name: "indicatorStatus", value: value])
}

def zwaveEvent(hubitat.zwave.commands.hailv1.Hail cmd) {
	createEvent([name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false])
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
	if (enableDebug) log.debug "manufacturerId:   ${cmd.manufacturerId}"
	if (enableDebug) log.debug "manufacturerName: ${cmd.manufacturerName}"
    state.manufacturer=cmd.manufacturerName
	if (enableDebug) log.debug "productId:        ${cmd.productId}"
	if (enableDebug) log.debug "productTypeId:    ${cmd.productTypeId}"
	def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
	updateDataValue("MSR", msr)	
    setFirmwareVersion()
    createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}

def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {	
    //updateDataValue("applicationVersion", "${cmd.applicationVersion}")
    if (enableDebug) log.debug ("received Version Report")
    if (enableDebug) log.debug "applicationVersion:      ${cmd.applicationVersion}"
    if (enableDebug) log.debug "applicationSubVersion:   ${cmd.applicationSubVersion}"
    state.firmwareVersion=cmd.applicationVersion+'.'+cmd.applicationSubVersion
    if (enableDebug) log.debug "zWaveLibraryType:        ${cmd.zWaveLibraryType}"
    if (enableDebug) log.debug "zWaveProtocolVersion:    ${cmd.zWaveProtocolVersion}"
    if (enableDebug) log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
    setFirmwareVersion()
    createEvent([descriptionText: "Firmware V"+state.firmwareVersion, isStateChange: false])
}

def zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv2.FirmwareMdReport cmd) { 
    if (enableDebug) log.debug ("received Firmware Report")
    if (enableDebug) log.debug "checksum:       ${cmd.checksum}"
    if (enableDebug) log.debug "firmwareId:     ${cmd.firmwareId}"
    if (enableDebug) log.debug "manufacturerId: ${cmd.manufacturerId}"
    [:]
}

def zwaveEvent(hubitat.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd) {
   if (enableInfo) log.info "$device.displayName level is on"
	[createEvent(name:"switch", value:"on"), response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))]
}

def zwaveEvent(hubitat.zwave.Command cmd) {
	// Handles all Z-Wave commands we aren't interested in
   if (enableDebug) log.debug "skip: $cmd"
	return []
}

def zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd) {
  result = null
  hubitat.zwave.Command encapCmd = cmd.encapsulatedCommand(commandClassVersions)
  if (encapCmd) {
     result = zwaveEvent(encapCmd)
  }
  sendHubCommand(new hubitat.device.HubAction(secure(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0)), hubitat.device.Protocol.ZWAVE))
  return result
}

def on() {
	//sendEvent(tapUp1Response("digital"))
	delayBetween([
			secure(zwave.basicV1.basicSet(value: 0xFF)),
			secure(zwave.switchMultilevelV1.switchMultilevelGet())
	], 5000)
}

def off() {
	//sendEvent(tapDown1Response("digital"))
	delayBetween([
			secure(zwave.basicV1.basicSet(value: 0x00)),
			secure(zwave.switchMultilevelV1.switchMultilevelGet())
	],5000)
}

List<String> setLevel(value) {
	if (enableDebug) log.debug "setLevel >> value: $value"
	def valueaux = value as Integer
	def level = Math.max(Math.min(valueaux, 99), 0)
   if (level <= 0) {      
      return delayBetween([
            seucre(zwave.basicV1.basicSet(value: 0x00)),
            secure(zwave.switchMultilevelV1.switchMultilevelGet())
      ],5000)
   }
   else {
      def result = []
      result += response(secure(zwave.basicV1.basicSet(value: level)))
      result += response(secure("delay 5000"))
      result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
      result += response(secure("delay 5000"))
      result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
      return result
   }
}

   List<String> setLevel(value, duration) {
	if (enableDebug) log.debug "setLevel >> value: $value, duration: $duration"
  def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
  return [
     secure(zwave.switchMultilevelV2.switchMultilevelSet(value: value < 100 ? value : 99, dimmingDuration: dimmingDuration))
  ]
   }

/*
 *  Set dimmer to status mode, then set the color of the individual LED
 *
 *  led = 1-7
 *  color = 0=0ff
 *          1=red
 *          2=green
 *          3=blue
 *          4=magenta
 *          5=yellow
 *          6=cyan
 *          7=white
 */
def setStatusLed(led, color, blink) {
   if (enableDebug) log.debug "setStatusLED >> led: $led, color: $color, blink: $blink"
    List cmds= []
    
    if(state.statusled1==null) {    	
    	   state.statusled1=0
        state.statusled2=0
        state.statusled3=0
        state.statusled4=0
        state.statusled5=0
        state.statusled6=0
        state.statusled7=0
        state.blinkval=0
    }
    
    state."statusled{$led}" = color
   
    if(state.statusled1==0 && state.statusled2==0 && state.statusled3==0 && state.statusled4==0 && state.statusled5==0 && state.statusled6==0 && state.statusled7==0)
    {
    	// no LEDS are set, put back to NORMAL mode
        cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: 0, parameterNumber: 13, size: 1))
    }
    else
    {
    	// at least one LED is set, put to status mode
        cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: 1, parameterNumber: 13, size: 1))
        // set the LED to color
        cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: color.toInteger(), parameterNumber: (led.toInteger() + 20), size: 1))
        // check if LED should be blinking
        Integer blinkval = state.blinkval ?: 0
        if(blink)
        {
            switch(led) {
            	case 1:
                	blinkval = blinkval | 0x1
                    break
                case 2:
                	blinkval = blinkval | 0x2
                    break
                case 3:
                	blinkval = blinkval | 0x4
                    break
                case 4:
                	blinkval = blinkval | 0x8
                    break
                case 5:
                	blinkval = blinkval | 0x10
                    break
                case 6:
                	blinkval = blinkval | 0x20
                    break
                case 7:
                	blinkval = blinkval | 0x40
                    break
            }
        	cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: blinkval, parameterNumber: 31, size: 1))
            // set blink speed, also enables blink, 1=100ms blink frequency
            cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: 5, parameterNumber: 30, size: 1))
            state.blinkval = blinkval
        }
        else {
        	switch(led.toInteger()) {
            	case 1:
                	blinkval = blinkval & 0xFE
                    break
                case 2:
                	blinkval = blinkval & 0xFD
                    break
                case 3:
                	blinkval = blinkval & 0xFB
                    break
                case 4:
                	blinkval = blinkval & 0xF7
                    break
                case 5:
                	blinkval = blinkval & 0xEF
                    break
                case 6:
                	blinkval = blinkval & 0xDF
                    break
                case 7:
                	blinkval = blinkval & 0xBF
                    break
            }
            cmds << secure(zwave.configurationV2.configurationSet(scaledConfigurationValue: blinkval, parameterNumber: 31, size: 1))
            state.blinkval = blinkval
        }
    }
 	return delayBetween(cmds, 500)
}

/*
 * Set Dimmer to Normal dimming mode (exit status mode)
 *
 */
def setSwitchModeNormal() {
	def cmds= []
    cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 13, size: 1))
    delayBetween(cmds, 500)
}

/*
 * Set Dimmer to Status mode (exit normal mode)
 *
 */
def setSwitchModeStatus() {
	def cmds= []
    cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 13, size: 1))
    delayBetween(cmds, 500)
}

/*
 * Set the color of the LEDS for normal dimming mode, shows the current dim level
 */
def setDefaultColor(color) {
	def cmds= []
    cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [color], parameterNumber: 14, size: 1))
    delayBetween(cmds, 500)
}


def poll() {
	secure(zwave.switchMultilevelV1.switchMultilevelGet())
}

def refresh() {
	if (enableDebug) log.debug "refresh() called"
  configure()
}

def zwaveEvent(hubitat.zwave.commands.centralscenev1.CentralSceneNotification cmd) {
    if (enableDebug) log.debug("sceneNumber: ${cmd.sceneNumber} keyAttributes: ${cmd.keyAttributes}")
    def result = []
    
    switch (cmd.sceneNumber) {
      case 1:
          // Up
          switch (cmd.keyAttributes) {
              case 0:
                   // Press Once
                  result += createEvent(tapUp1Response("physical"))  
                  result += createEvent([name: "switch", value: "on", type: "physical"])
       
                  if (singleTapToFullBright)
                  {
                     result += setLevel(99)
                     result += response(secure("delay 5000"))
                     result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
                  } 
                  break
              case 1:
                  result=createEvent([name: "switch", value: "on", type: "physical"])
                  break
              case 2:
                  // Hold
                  result += createEvent(holdUpResponse("physical"))  
                  result += createEvent([name: "switch", value: "on", type: "physical"])    
                  break
              case 3: 
                  // 2 Times
                  result +=createEvent(tapUp2Response("physical"))
                  if (doubleTapToFullBright)
                  {
                     result += setLevel(99)
                     result += response(secure("delay 5000"))
                     result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
                  }                    
                  break
              case 4:
                  // 3 times
                  result=createEvent(tapUp3Response("physical"))
                  break
              case 5:
                  // 4 times
                  result=createEvent(tapUp4Response("physical"))
                  break
              case 6:
                  // 5 times
                  result=createEvent(tapUp5Response("physical"))
                  break
              default:
                  if (enableDebug) log.debug ("unexpected up press keyAttribute: $cmd.keyAttributes")
          }
          break
          
      case 2:
          // Down
          switch (cmd.keyAttributes) {
              case 0:
                  // Press Once
                  result += createEvent(tapDown1Response("physical"))
                  result += createEvent([name: "switch", value: "off", type: "physical"]) 
                  break
              case 1:
                  result=createEvent([name: "switch", value: "off", type: "physical"])
                  break
              case 2:
                  // Hold
                  result += createEvent(holdDownResponse("physical"))
                  result += createEvent([name: "switch", value: "off", type: "physical"]) 
                  break
              case 3: 
                  // 2 Times
                  result+=createEvent(tapDown2Response("physical"))
                  if (doubleTapDownToDim)
                  {
                     result += setLevel(25)
                     result += response(secure("delay 5000"))
                     result += response(secure(zwave.switchMultilevelV1.switchMultilevelGet()))
                  }  
                  break
              case 4:
                  // 3 Times
                  result=createEvent(tapDown3Response("physical"))
                  break
              case 5:
                  // 4 Times
                  result=createEvent(tapDown4Response("physical"))
                  break
              case 6:
                  // 5 Times
                  result=createEvent(tapDown5Response("physical"))
                  break
              default:
                  if (enableDebug) log.debug ("unexpected down press keyAttribute: $cmd.keyAttributes")
           } 
           break
           
      default:
           // unexpected case
           if (enableDebug)  log.debug ("unexpected scene: $cmd.sceneNumber")
   }  
   return result
}

def tapUp1Response(String buttonType) {
   sendEvent(name: "status" , value: "Tap ▲")
	[name: "pushed", value: 1, descriptionText: "$device.displayName Tap-Up-1 (button 1) pushed", 
       isStateChange: true, type: "$buttonType"]
}

def tapDown1Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼")
	[name: "pushed", value: 2, descriptionText: "$device.displayName Tap-Down-1 (button 2) pushed", 
      isStateChange: true, type: "$buttonType"]
}

def tapUp2Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▲▲")
	[name: "pushed", value: 3, descriptionText: "$device.displayName Tap-Up-2 (button 3) pushed", 
       isStateChange: true, type: "$buttonType"]
}

def tapDown2Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼▼")
	[name: "pushed", value: 4, descriptionText: "$device.displayName Tap-Down-2 (button 4) pushed", 
      isStateChange: true, type: "$buttonType"]
}

def tapUp3Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▲▲▲")
	[name: "pushed", value: 5, descriptionText: "$device.displayName Tap-Up-3 (button 5) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapUp4Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▲▲▲▲")
	[name: "pushed", value: 7, descriptionText: "$device.displayName Tap-Up-4 (button 7) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapUp5Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▲▲▲▲▲")
	[name: "pushed", value: 9, descriptionText: "$device.displayName Tap-Up-5 (button 9) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapDown3Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼▼▼")
	[name: "pushed", value: 6, descriptionText: "$device.displayName Tap-Down-3 (button 6) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapDown4Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼▼▼▼")
	[name: "pushed", value: 8, descriptionText: "$device.displayName Tap-Down-3 (button 8) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def tapDown5Response(String buttonType) {
    sendEvent(name: "status" , value: "Tap ▼▼▼▼▼")
	[name: "pushed", value: 10, descriptionText: "$device.displayName Tap-Down-3 (button 10) pushed", 
    isStateChange: true, type: "$buttonType"]
}

def holdUpResponse(String buttonType) {
    sendEvent(name: "status" , value: "Hold ▲")
	[name: "held", value: 1, descriptionText: "$device.displayName Hold-Up (button 1) held", 
    isStateChange: true, type: "$buttonType"]
}

def holdDownResponse(String buttonType) {
    sendEvent(name: "status" , value: "Hold ▼")
	[name: "held", value: 2, descriptionText: "$device.displayName Hold-Down (button 2) held", 
    isStateChange: true, type: "$buttonType"]
}

def push(Number button) {
   switch (button as Integer) {
      case 1:
         sendEvent(tapUp1Response("digital"))
         break
      case 2:
         sendEvent(tapDown1Response("digital"))
         break
      case 3:
         sendEvent(tapUp2Response("digital"))
         break
      case 4:
         sendEvent(tapDown2Response("digital"))
         break
      case 5:
         sendEvent(tapUp3Response("digital"))
         break
      case 6:
         sendEvent(tapDown3Response("digital"))
         break
      case 7:
         sendEvent(tapUp4Response("digital"))
         break
      case 8:
         sendEvent(tapDown4Response("digital"))
         break
      case 9:
         sendEvent(tapUp5Response("digital"))
         break
      case 10:
         sendEvent(tapDown5Response("digital"))
         break
      default:
         log.warn "Invalid button number in push(): $button"
   }
}

def hold(Number button) {
   if (button as Integer == 1) {
	   sendEvent(holdUpResponse("digital"))
   }
   else if (button as Integer == 2) {
      sendEvent(holdDownResponse("digital"))
   }
   else {
      log.warn "Invalid button in hold(): $button"
   }
}
/*
def release(Number button) {
   if (button as Integer == 1) {
	   sendEvent(releaseUpResponse("digital"))
   }
   else if (button as Integer == 2) {
      sendEvent(releaseDownResponse("digital"))
   }
   else {
      log.warn "Invalid button in release(): $button"
   }
}
*/


def setFirmwareVersion() {
   def versionInfo = ''
   if (state.manufacturer)
   {
      versionInfo=state.manufacturer+' '
   }
   if (state.firmwareVersion)
   {
      versionInfo=versionInfo+"Firmware V"+state.firmwareVersion
   }
   else 
   {
     versionInfo=versionInfo+"Firmware unknown"
   }   
   sendEvent(name: "firmwareVersion",  value: versionInfo, isStateChange: true, displayed: false)
}

def configure() {
   log.trace ("configure() called")
 
   sendEvent(name: "numberOfButtons", value: 10)
   def commands = []
   commands << setDimRatePrefs()
   commands << secure(zwave.switchMultilevelV1.switchMultilevelGet())
   commands << secure(zwave.manufacturerSpecificV1.manufacturerSpecificGet())
   commands << secure(zwave.versionV1.versionGet())
   return delayBetween(commands,500)
}

def setDimRatePrefs() 
{
   if (enableDebug) log.trace ("set prefs")
   def cmds = []

	if (color)
    {
        switch (color) {
        	case "White":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 14, size: 1))
                break
      		case "Red":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 14, size: 1))
                break
            case "Green":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [2], parameterNumber: 14, size: 1))
                break
            case "Blue":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [3], parameterNumber: 14, size: 1))
                break
            case "Magenta":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [4], parameterNumber: 14, size: 1))
                break
            case "Yellow":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [5], parameterNumber: 14, size: 1))
                break
            case "Cyan":
            	cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [6], parameterNumber: 14, size: 1))
                break
            
            
      	}
    }    
   
   if(localcontrolramprate != null) {
   		//if (enableDebug) log.debug localcontrolramprate
   		def localcontrolramprate = Math.max(Math.min(localcontrolramprate, 90), 0)
   		cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [localcontrolramprate], parameterNumber: 12, size: 1))
   }
   
   if(remotecontrolramprate != null) {
   		if (enableDebug) log.debug remotecontrolramprate
   		def remotecontrolramprate = Math.max(Math.min(remotecontrolramprate, 90), 0)
   		cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [remotecontrolramprate], parameterNumber: 11, size: 1))
   }
   
   if (reverseSwitch)
   {
       cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1))
   }
   else
   {
      cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1))
   }
   
   if (bottomled)
   {
       cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1))
   }
   else
   {
      cmds << secure(zwave.configurationV2.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1))
   }
   
   //Enable the following configuration gets to verify configuration in the logs
   //cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: 7))
   //cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: 8))
   //cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: 9))
   //cmds << secure(zwave.configurationV1.configurationGet(parameterNumber: 10))
   
   return cmds
}
 
def updated()
{
   def cmds= []
   cmds << setDimRatePrefs
   delayBetween(cmds, 500)
}

Ah, yes, that would do it. Thanks for figuring that out! (Probably would have still been scratching my head for a while wondering what I missed even if I did have one in front of me...haha.) Edited the above with a change that should also fix this.

I basically put a log entry around every step until I realized what was going on.

Do they have a debugging env for this?

No, log.debug (or .trace, .warn, .error, or .info) is probably the best tool we have to see what's happening when. During development, sometimes I'll use weird things (like log.error) just to get my attention more easily if I want to be sure to see something, then of course remove that line when I'm done. But no, no line-by-line debugger or anything (unless the code in question is just regular Groovy that doesn't depend on the Hubitat environment, of course, in which case you have more options).

I am in process of moving from ST and found the HE driver for WS200 a bit lacking. Your port from ST is great because that's what I was using there. Thanks for the work.

I was unable to get configuration to set local and remote ramps with your driver. I did some digging, looking at the return values for configure() using your driver on HE, and also the WS200 driver running on ST and found some differences. The format for the ramp parms differed between the two and on ST a lot of info is returned where on yours nothing is returned.

I got it working and thought I will post my changes.

I have been programing in one language or another for many years, but have done only a little in Groovy. I am used to a strong typed language and you will see that in my changes. I am sure that I did not implement them in the best way possible, that said it seems to work.

Sorry for the unformatted stuff below, please point me to how to edit post's. (edited to only show key changes)

Changes to configure()
...
// was commands<< setDimRatePrefs()
def commands = setDimRatePrefs()
commands << secure(zwave.switchMultilevelV1.switchMultilevelGet())
...

Changes to setDimRatePrefs()
...
// change from def to int
int localcontrolramprate = Math.max(Math.min(localcontrolramprate, 90), 0)

// changed from def to int
int remoteramprate = Math.max(Math.min(remotecontrolramprate, 90), 0)

Before I go ahead and start switching drivers around, does anyone who’s been working on this port know if the one driver will work unmodified for HomeSeer WD100+ and WS200+ models as well, since I have all three? And I miss the triple-tap functionality on some of my WD100+ dimmers that I used to have before I migrated from ST.

Also, has anyone made this available on GitHub or HPM? I’d feel better knowing that the most recent updates/fixes are included in code I retrieve that way than scanning through Community threads, even as great as they are for engendering this cooperative development work!

Guys, the driver seems to work, but it appears to break the Switch Dashboard app:

ERROR: Unsupported switches (missing setStatusLED/setIndicator command):

  • Hall (WD200+ Dimmer)

With debug turned on, all I get is:

[app:292] 2021-02-17 01:16:55.097 am [debug] Unsupported devices: [Hall]

or

[app:292] 2021-02-17 01:16:26.787 am [error] Hall is not a usable HomeSeer or Inovelli device (ID:311, Name:'HS-WD200+ Scene Capable Wall Dimmer' Type:'WD200+ Dimmer')

And yet I can see that setStatusLED() is in the code that I pasted in from above.

Any ideas?

Hmm, just noticed that using the code in post #42, (please confirm that is the code that is most current/should be working), that after testing with the driver’s manual setStatusLED function, (attempted to set LED 4 to Green), I now have a state variable “statusled{4} : 2” in addition to “statusled4: 0”.

Looks like a simple error, but before I attempt to debug code that’s probably already been fixed, can @ArchStanton or @bertabcd1234 which post or other repository contains the latest/best code?

Thanks

Guys,
As another thought, and a possible "simpler way" to address the multi-tap button problem, I'm currently working on a driver where I implement multi-tap in my drivers using a custom attribute

		attribute "multiTapButton", "number"	

This can be used in addition to the "legacy" methods of having capability PushableButton, HoldableButton, ReleaseableButton, and doubleTap. What I do is that, on a central scene event, I assign a decimal number like 1.1 or 2.3, where the first part is the button number, the second is the number of taps. The idea is that I can then create rule machine rules that trigger on the attribute change and compare to the expected value (i.e., if I'm looking for 4 taps on button #3, I look for the value 4.3. Here's the code to handle this (which I'll eventually release as a community driver). Using the decimal form makes the code device-independent as this easily extends to whatever number of buttons the device supports and is simple to understand. I had also considered using negative numbers for "special" meaning, like -4.1 to mean "button 4, hold", but I figured the hold and released were already handled using existing Capabilities and there wasn't much value in changing those.

In the sample code, below, I also include the possibility of using custom attributes

        attribute "buttonTripleTapped", "number"	
	attribute "buttonFourTaps", "number"	
	attribute "buttonFiveTaps", "number"	

which work more like the existing button Capabilities, But I think the better approach is the use of the multiTapButton attribute so there is only 1 attribute to have to consider in rule machine - thus, I may remove the triple / four / five from my final code.

///////////////////////////////////////////////////////////////////////////////////////////////
///////////////                  Central Scene Processing          ////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////

//The next two import lines usually would go at the very top of the .groovy driver file
import java.util.concurrent.* // Available (white-listed) concurrency classes: ConcurrentHashMap, ConcurrentLinkedQueue, Semaphore, SynchronousQueue
import groovy.transform.Field

// Use a concurrentHashMap to hold the last reported state. This is used for "held" state checking
// In a "held" state, the device will send "held down refresh" messages at either 200 mSecond or 55 second intervals.
// Hubitat should not generated repreated "held" messages in response to a refresh, so inhibit those
// Since the concurrentHashMap is @Field static -- its data structure is common to all devices using this
// Driver, therefore you have to key it using the device.deviceNetworkId to get the value for a particuarl device.
@Field static  ConcurrentHashMap centralSceneButtonState = new ConcurrentHashMap<String, String>()

String getCentralSceneButtonState(Integer button) { 
 	String key = "${device.deviceNetworkId}.Button.${button}"
	return centralSceneButtonState.get(key)
}

String setCentralSceneButtonState(Integer button, String state) {
 	String key = "${device.deviceNetworkId}.Button.${button}"
	centralSceneButtonState.put(key, state)
	return centralSceneButtonState.get(key)
}

void getCentralSceneInfo() {
	// Not currently used.
	sendToDevice(zwave.centralSceneV3.centralSceneSupportedGet() )
}

void zwaveEvent(hubitat.zwave.commands.centralscenev3.CentralSceneNotification cmd)
{

	// Check if central scene is already in a held state, if so, and you get another held message, its a refresh, so don't send a sendEvent
	if ((getCentralSceneButtonState(cmd.sceneNumber as Integer) == "held") && (cmd.keyAttributes == 2)) return

	// Central scene events should be sent with isStateChange:true since it is valid to send two of the same events in a row (except held, whcih is handled in previous line)
    Map event = [value:cmd.sceneNumber, type:"physical", unit:"button#", isStateChange:true]
	
	event.name = [	0:"pushed", 1:"released", 2:"held",  3:"doubleTapped", 
					4:"buttonTripleTapped", 5:"buttonFourTaps", 6:"buttonFiveTaps"].get(cmd.keyAttributes as Integer)
	
	String tapDescription = [	0:"Pushed", 1:"Released", 2:"Held",  3:"Double-Tapped", 
								4:"Three Taps", 5:"Four Taps", 6:"Five Taps"].get(cmd.keyAttributes as Integer)
    
	// Save the event name for event that is about to be sent using sendEvent. This is important for 'held' state refresh checking
	setCentralSceneButtonState(cmd.sceneNumber, event.name)	
	
	event.descriptionText="${device.displayName}: Button #${cmd.sceneNumber}: ${tapDescription}"

	if (device.hasAttribute( event.name )) sendEvent(event)
	
	// Next code is for the custom attribute "multiTapButton".
	Integer taps = [0:1, 3:2, 4:3, 5:4, 6:5].get(cmd.keyAttributes as Integer)
	if ( taps && device.hasAttribute("multiTapButton") )
	{
		event.name = "multiTapButton"
		event.unit = "Button #.Tap Count"
		event.value = ("${cmd.sceneNumber}.${taps}" as Float)
		sendEvent(event)		
	} 
}