From 32f0385e30dd3e27ef18856bdcc0ed3176c87873 Mon Sep 17 00:00:00 2001 From: Rohan Desai Date: Fri, 13 May 2016 14:49:37 -0700 Subject: [PATCH 1/7] PENG-158 UBI should not allow undefined commands - now validating commands per capability of the device in the smartapp removed commented out code --- smartapps/smartthings/ubi.src/ubi.groovy | 94 +++++++++++++++++++----- 1 file changed, 75 insertions(+), 19 deletions(-) diff --git a/smartapps/smartthings/ubi.src/ubi.groovy b/smartapps/smartthings/ubi.src/ubi.groovy index a00f5abfc00..6e8d357b919 100644 --- a/smartapps/smartthings/ubi.src/ubi.groovy +++ b/smartapps/smartthings/ubi.src/ubi.groovy @@ -107,8 +107,8 @@ mappings { path("/locks") { action: [ GET: "listLocks", - PUT: "updateLock", - POST: "updateLock" + PUT: "updateLocks", + POST: "updateLocks" ] } path("/locks/:id") { @@ -442,31 +442,87 @@ def executePhrase() { } private void updateAll(devices) { + def type = params.param1 def command = request.JSON?.command - if (command) - { - command = command.toLowerCase() - devices."$command"() + if (!devices) { + httpError(404, "Devices not found") + } + if (command){ + devices.each { device -> + executeCommand(device, type, command) + } } } private void update(devices) { log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id" - //def command = request.JSON?.command - def command = params.command - if (command) - { - command = command.toLowerCase() - def device = devices.find { it.id == params.id } - if (!device) - { + def type = params.param1 + def command = request.JSON?.command + def device = devices?.find { it.id == params.id } + + if (!device) { httpError(404, "Device not found") - } - else - { - device."$command"() - } } + + if (command) { + executeCommand(device, type, command) + } +} + +/** + * Validating the command passed by the user based on capability. + * @return boolean + */ +def validateCommand(device, deviceType, command) { + def capabilityCommands = getDeviceCapabilityCommands(device.capabilities) + def currentDeviceCapability = getCapabilityName(deviceType) + if (capabilityCommands[currentDeviceCapability]) { + return command in capabilityCommands[currentDeviceCapability] ? true : false + } else { + // Handling other device types here, which don't accept commands + httpError(400, "Bad request.") + } +} + +/** + * Need to get the attribute name to do the lookup. Only + * doing it for the device types which accept commands + * @return attribute name of the device type + */ +def getCapabilityName(type) { + switch(type) { + case "switches": + return "Switch" + case "locks": + return "Lock" + default: + return type + } +} + +/** + * Constructing the map over here of + * supported commands by device capability + * @return a map of device capability -> supported commands + */ +def getDeviceCapabilityCommands(deviceCapabilities) { + def map = [:] + deviceCapabilities.collect { + map[it.name] = it.commands.collect{ it.name.toString() } + } + return map +} + +/** + * Validates and executes the command + * on the device or devices + */ +def executeCommand(device, type, command) { + if (validateCommand(device, type, command)) { + device."$command"() + } else { + httpError(403, "Access denied. This command is not supported by current capability.") + } } private show(devices, type) { From ce28ec2039c312d86fb8f09ab30b5e8f93c0239b Mon Sep 17 00:00:00 2001 From: Duncan McKee Date: Tue, 31 May 2016 18:29:40 -0400 Subject: [PATCH 2/7] DPROT-110 Add manufacturer fingerprints to Z-Wave Motion Sensor --- .../zwave-motion-sensor.groovy | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/devicetypes/smartthings/zwave-motion-sensor.src/zwave-motion-sensor.groovy b/devicetypes/smartthings/zwave-motion-sensor.src/zwave-motion-sensor.groovy index 7a532e9ad48..014900be396 100644 --- a/devicetypes/smartthings/zwave-motion-sensor.src/zwave-motion-sensor.groovy +++ b/devicetypes/smartthings/zwave-motion-sensor.src/zwave-motion-sensor.groovy @@ -21,6 +21,13 @@ metadata { capability "Motion Sensor" capability "Sensor" capability "Battery" + + fingerprint mfr: "011F", prod: "0001", model: "0001", deviceJoinName: "Schlage Motion Sensor" // Schlage motion + fingerprint mfr: "014A", prod: "0001", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion + fingerprint mfr: "014A", prod: "0004", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion + + fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814 + fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02 + fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC } simulator { @@ -125,9 +132,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) } if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) { result << response(zwave.batteryV1.batteryGet()) - result << response("delay 1200") + } else { + result << response(zwave.wakeUpV1.wakeUpNoMoreInformation()) } - result << response(zwave.wakeUpV1.wakeUpNoMoreInformation()) result } From 038d770691e96dcf320cb7af1f5be66b74af9f1e Mon Sep 17 00:00:00 2001 From: Zach Varberg Date: Tue, 31 May 2016 14:35:26 -0500 Subject: [PATCH 3/7] Use mfgCode in the ZigBee library for smartsense-multi-sensor With the change to the ZigBee library in appengine to add the optional manufacturers code, the custom writing of ZigBee commands can be replaced with calls to the library This resolves https://smartthings.atlassian.net/browse/DVCSMP-1801 --- .../smartsense-multi-sensor.groovy | 77 +++++-------------- 1 file changed, 18 insertions(+), 59 deletions(-) diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index 1e30759ffe9..d13dffb35c0 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -403,39 +403,21 @@ def refresh() { if (device.getDataValue("manufacturer") == "SmartThings") { log.debug "Refreshing Values for manufacturer: SmartThings " - refreshCmds = refreshCmds + [ - /* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602) - seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer. - Separating these out in a separate if-else because I do not want to touch Centralite part - as of now. - */ - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global write 0xFC02 0 0x20 {01}", "delay 200", - "send 0x${device.deviceNetworkId} 1 1", "delay 400", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global write 0xFC02 2 0x21 {7602}", "delay 200", - "send 0x${device.deviceNetworkId} 1 1", "delay 400", - ] + /* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276) + seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer. + Separating these out in a separate if-else because I do not want to touch Centralite part + as of now. + */ + refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode]) + refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode]) } else { - refreshCmds = refreshCmds + [ - /* sensitivity - default value (8) */ - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global write 0xFC02 0 0x20 {02}", "delay 200", - "send 0x${device.deviceNetworkId} 1 1", "delay 400", - ] + refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode]) } //Common refresh commands - refreshCmds = refreshCmds + [ - "st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200", - "st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global read 0xFC02 0x0010", - "send 0x${device.deviceNetworkId} 1 1","delay 400" - ] + refreshCmds += zigbee.readAttribute(0x0402, 0x0000) + + zigbee.readAttribute(0x0001, 0x0020) + + zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) return refreshCmds + enrollResponse() } @@ -443,38 +425,15 @@ def refresh() { def configure() { sendEvent(name: "checkInterval", value: 7200, displayed: false) - String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting" - def configCmds = [ - "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200", - "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200", - "zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - - "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200", - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" - ] + def configCmds = enrollResponse() + + zigbee.batteryConfig() + + zigbee.temperatureConfig() + + zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) + + zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + + zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + + zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) return configCmds + refresh() } From cc2d19e9510e529153a3bb9751425a65041c774f Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Wed, 1 Jun 2016 18:03:27 -0700 Subject: [PATCH 4/7] DVCSMP-400 Philips Hue: Hue bridge & Bulbs displaying as Not Yet Configured --- smartapps/smartthings/hue-connect.src/hue-connect.groovy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 0ebf042525f..8cc2fe99c47 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -352,6 +352,7 @@ def addBulbs() { d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub) if (d) { log.debug "created ${d.displayName} with id $dni" + d.completedSetup = true d.refresh() } } else { @@ -361,6 +362,7 @@ def addBulbs() { //backwards compatable newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub) + d?.completedSetup = true d?.refresh() } } else { @@ -399,6 +401,7 @@ def addBridge() { } if (newbridge) { d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub) + d?.completedSetup = true log.debug "created ${d.displayName} with id ${d.deviceNetworkId}" def childDevice = getChildDevice(d.deviceNetworkId) childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber) From 31f77513dac75b117fdc32446860187e17633557 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Thu, 2 Jun 2016 19:24:44 -0700 Subject: [PATCH 5/7] DVCSMP-1676 Philips Hue: Need to provide timeout message if search takes too long. DVCSMP-1675 Changed to show bridge serial number instead of IP -Shows timeout screen after 5 minutes -Removed old image code that didnt work for one of the pages -Handled null pointer when adding unsupported devices --- .../hue-connect.src/hue-connect.groovy | 69 +++++++++++++------ 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 8cc2fe99c47..d3381f9261e 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -30,6 +30,7 @@ definition( preferences { page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5) page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5) + page(name:"bridgeDiscoveryFailed", title:"Bridge Discovery Failed", content:"bridgeDiscoveryFailed", refreshTimeout:0) page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5) page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5) } @@ -53,12 +54,21 @@ def bridgeDiscovery(params=[:]) def options = bridges ?: [] def numFound = options.size() ?: 0 - if (numFound == 0 && state.bridgeRefreshCount > 25) { - log.trace "Cleaning old bridges memory" - state.bridges = [:] - state.bridgeRefreshCount = 0 - app.updateSetting("selectedHue", "") - } + if (numFound == 0) { + if (state.bridgeRefreshCount == 25) { + log.trace "Cleaning old bridges memory" + state.bridges = [:] + app.updateSetting("selectedHue", "") + } else if (state.bridgeRefreshCount > 100) { + // five minutes have passed, give up + // there seems to be a problem going back from discovey failed page in some instances (compared to pressing next) + // however it is probably a SmartThings settings issue + state.bridges = [:] + app.updateSetting("selectedHue", "") + state.bridgeRefreshCount = 0 + return bridgeDiscoveryFailed() + } + } ssdpSubscribe() @@ -79,6 +89,13 @@ def bridgeDiscovery(params=[:]) } } +def bridgeDiscoveryFailed() { + return dynamicPage(name:"bridgeDiscoveryFailed", title: "Bridge Discovery Failed", nextPage: "bridgeDiscovery") { + section("Failed to discover any Hue Bridges. Please confirm that the Hue Bridge is connected to the same network as your SmartThings Hub, and that it has power.") { + } + } +} + def bridgeLinking() { int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int @@ -88,19 +105,15 @@ def bridgeLinking() def nextPage = "" def title = "Linking with your Hue" def paragraphText - def hueimage = null if (selectedHue) { paragraphText = "Press the button on your Hue Bridge to setup a link. " - hueimage = "http://huedisco.mediavibe.nl/wp-content/uploads/2013/09/pair-bridge.png" } else { paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next." - hueimage = null } if (state.username) { //if discovery worked nextPage = "bulbDiscovery" title = "Success!" paragraphText = "Linking to your hub was a success! Please click 'Next'!" - hueimage = null } if((linkRefreshcount % 2) == 0 && !state.username) { @@ -110,8 +123,6 @@ def bridgeLinking() return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) { section("") { paragraph """${paragraphText}""" - if (hueimage != null) - image "${hueimage}" } } } @@ -135,13 +146,14 @@ def bulbDiscovery() { if((bulbRefreshCount % 5) == 0) { discoverHueBulbs() } + def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) } + def title = selectedBridge?.value?.name ?: "Find bridges" return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions } section { - def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges" href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true] } @@ -348,18 +360,19 @@ def addBulbs() { def newHueBulb if (bulbs instanceof java.util.Map) { newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni } - if (newHueBulb != null) { - d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub) + + if (newHueBulb != null) { + d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub) if (d) { log.debug "created ${d.displayName} with id $dni" d.completedSetup = true d.refresh() } - } else { - log.debug "$dni in not longer paired to the Hue Bridge or ID changed" - } + } else { + log.debug "$dni in not longer paired to the Hue Bridge or ID changed" + } } else { - //backwards compatable + //backwards compatable newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub) d?.completedSetup = true @@ -369,7 +382,7 @@ def addBulbs() { log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'" if (bulbs instanceof java.util.Map) { // Update device type if incorrect - def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni } + def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni } upgradeDeviceType(d, newHueBulb?.value?.type) } } @@ -489,7 +502,21 @@ void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) { def bridges = getHueBridges() def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())} if (bridge) { - bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true] + // serialNumber from API is in format of 0017882413ad (mac address), however on the actual bridge only last six + // characters are printed on the back so using that to identify bridge + def idNumber = body?.device?.serialNumber?.text() + if (idNumber?.size() >= 6) + idNumber = idNumber[-6..-1].toUpperCase() + + // usually in form of bridge name followed by (ip), i.e. defaults to Philips Hue (192.168.1.2) + // replace IP with serial number to make it easier for user to identify + def name = body?.device?.friendlyName?.text() + def index = name?.indexOf('(') + if (index != -1) { + name = name.substring(0,index) + name += " ($idNumber)" + } + bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), verified: true] } else { log.error "/description.xml returned a bridge that didn't exist" } From ff9dd3f6e2df5901d6518940d70698d06da2c1bd Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Mon, 13 Jun 2016 10:45:36 -0700 Subject: [PATCH 6/7] DVCSMP-1819 Philips Hue: Incorrect folder name for Ambiance bulb DTH -Renamed folder to .src --- .../hue-white-ambiance-bulb.groovy | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename devicetypes/smartthings/{hue-white-ambiance-bulb => hue-white-ambiance-bulb.src}/hue-white-ambiance-bulb.groovy (100%) diff --git a/devicetypes/smartthings/hue-white-ambiance-bulb/hue-white-ambiance-bulb.groovy b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy similarity index 100% rename from devicetypes/smartthings/hue-white-ambiance-bulb/hue-white-ambiance-bulb.groovy rename to devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy From 45a0822e9bb7576f8431ec9682de00648446f2fc Mon Sep 17 00:00:00 2001 From: Donald Kirker Date: Tue, 14 Jun 2016 11:07:31 -0700 Subject: [PATCH 7/7] Add re-type for Vision Motion Sensor Plus. --- .../zwave-door-window-sensor.groovy | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/devicetypes/smartthings/zwave-door-window-sensor.src/zwave-door-window-sensor.groovy b/devicetypes/smartthings/zwave-door-window-sensor.src/zwave-door-window-sensor.groovy index 2c020dffa22..6011f7e6c98 100644 --- a/devicetypes/smartthings/zwave-door-window-sensor.src/zwave-door-window-sensor.groovy +++ b/devicetypes/smartthings/zwave-door-window-sensor.src/zwave-door-window-sensor.groovy @@ -29,6 +29,7 @@ metadata { fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82" fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+ fingerprint deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window + fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98,0x86,0x72,0x5A,0x85,0x59,0x73,0x80,0x71,0x31,0x70,0x84,0x7A" // Vision Motion } // simulator metadata @@ -263,5 +264,9 @@ def retypeBasedOnMSR() { log.debug "Changing device type to Door / Window Sensor Plus (SG)" setDeviceType("Door / Window Sensor Plus (SG)") break + case "0109-2002-0205": // Vision Motion + log.debug "Changing device type to Vision Motion Sensor Plus (SG)" + setDeviceType("Vision Motion Sensor Plus (SG)") + break } }