From d17cadc4c7cbcf365fe403452d42e6325cc522a8 Mon Sep 17 00:00:00 2001 From: Vinay Rao Date: Fri, 22 Apr 2016 17:41:57 -0700 Subject: [PATCH 1/4] stage 2 of adding preferences to support garage sensor in mutli sensor --- .../smartsense-multi.groovy | 85 ++++++++++++++++--- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/devicetypes/smartthings/smartsense-multi.src/smartsense-multi.groovy b/devicetypes/smartthings/smartsense-multi.src/smartsense-multi.groovy index 17f5ddd..8ed1a3d 100644 --- a/devicetypes/smartthings/smartsense-multi.src/smartsense-multi.groovy +++ b/devicetypes/smartthings/smartsense-multi.src/smartsense-multi.groovy @@ -45,17 +45,28 @@ metadata { } preferences { - input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" - input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false + section { + input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" + input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false + } + section { + input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false) + } } tiles(scale: 2) { - multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){ - tileAttribute ("device.contact", key: "PRIMARY_CONTROL") { - attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e" + multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){ + tileAttribute ("device.status", key: "PRIMARY_CONTROL") { attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821" + attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e" + attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821" + attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e" } } + standardTile("contact", "device.contact", width: 2, height: 2) { + state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e") + state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821") + } standardTile("acceleration", "device.acceleration", width: 2, height: 2) { state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0") @@ -78,8 +89,8 @@ metadata { state "battery", label:'${currentValue}% battery', unit:"" } - main(["contact", "acceleration", "temperature"]) - details(["contact", "acceleration", "temperature", "battery"]) + main(["status", "acceleration", "temperature"]) + details(["status", "acceleration", "temperature", "battery"]) } } @@ -87,7 +98,9 @@ def parse(String description) { def results if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) { - results = parseSingleMessage(description) + if (garageSensor != "Yes") { + results = parseSingleMessage(description) + } } else if (description == 'updated') { //TODO is there a better way to handle this like the other device types? @@ -203,7 +216,9 @@ private List parseContactMessage(String description) { parts.each { part -> part = part.trim() if (part.startsWith('contactState:')) { - results.addAll(getContactResult(part, description)) + if (garageSensor != "Yes") { + results.addAll(getContactResult(part, description)) + } } else if (part.startsWith('accelerationState:')) { results << getAccelerationResult(part, description) @@ -225,6 +240,29 @@ private List parseContactMessage(String description) { results } +def updated() { + log.debug "updated called" + log.info "garage value : $garageSensor" + if (garageSensor == "Yes") { + def descriptionText = "Updating device to garage sensor" + if (device.latestValue("status") == "open") { + sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText) + } + else if (device.latestValue("status") == "closed") { + sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText) + } + } + else { + def descriptionText = "Updating device to open/close sensor" + if (device.latestValue("status") == "garage-open") { + sendEvent(name: 'status', value: 'open', descriptionText: descriptionText) + } + else if (device.latestValue("status") == "garage-closed") { + sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText) + } + } +} + private List parseOrientationMessage(String description) { def results = [] def xyzResults = [x: 0, y: 0, z: 0] @@ -254,7 +292,34 @@ private List parseOrientationMessage(String description) { } } - results << getXyzResult(xyzResults, description) + def xyz = getXyzResult(xyzResults, description) + results << xyz + + if (garageSensor == "Yes") { + // Looks for Z-axis orientation as virtual contact state + def a = xyz.value.split(',').collect{it.toInteger()} + def absValueXY = Math.max(Math.abs(a[0]), Math.abs(a[1])) + def absValueZ = Math.abs(a[2]) + log.debug "absValueXY: $absValueXY, absValueZ: $absValueZ" + + def contactValue = null + def garageValue = null + + if (absValueZ > 825 && absValueXY < 175) { + contactValue = 'open' + garageValue = 'garage-open' + log.debug "STATUS: open" + } + else if (absValueZ < 75 && absValueXY > 825) { + contactValue = 'closed' + garageValue = 'garage-closed' + log.debug "STATUS: closed" + } + def linkText = getLinkText(device) + def descriptionText = "${linkText} was ${contactValue == 'open' ? 'opened' : 'closed'}" + results << createEvent(name: "contact", value: contactValue, descriptionText: descriptionText, unit: "", displayed: false) + results << createEvent(name: "status", value: garageValue, descriptionText: descriptionText, unit: "") + } results } From ab2ba8104d0ce6555edcec77a1d9999cad4f16ff Mon Sep 17 00:00:00 2001 From: Matthew Page Date: Sat, 7 May 2016 16:51:30 -0500 Subject: [PATCH 2/4] MSA-1255: The following app submitted will be an endpoint for our IOS app Gidjit. Gidjit is like having a home screen based on your environment. It scans your surroundings looking for home devices, personal electronic devices, times and locations of interest, and more. It will present a list of corresponding launcher panels that allow you to quickly access actions like open an app, control/monitor a device, execute a SmartThings Routine, or link to the control/launch screen of a device. For example based on the users location they will quickly be able to access a preset routine. We are really looking forward to having SmartThings in integration with the app. Link: https://itunes.apple.com/us/app/gidjit-smart-launcher/id961388659?mt=8 The app currently will not have the SmartThing's support until the next version that will be released when this is hopefully approved, as we will also have to go through the Apple approval process allowing them to test the features. Thank you for your time! Matt --- .../gidjit-hub.src/gidjit-hub.groovy | 252 ++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 smartapps/com-gidjit-smartthings-hub/gidjit-hub.src/gidjit-hub.groovy diff --git a/smartapps/com-gidjit-smartthings-hub/gidjit-hub.src/gidjit-hub.groovy b/smartapps/com-gidjit-smartthings-hub/gidjit-hub.src/gidjit-hub.groovy new file mode 100644 index 0000000..a1f84b2 --- /dev/null +++ b/smartapps/com-gidjit-smartthings-hub/gidjit-hub.src/gidjit-hub.groovy @@ -0,0 +1,252 @@ +/** + * Gidjit Hub + * + * Copyright 2016 Matthew Page + * + * 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. + * + */ +definition( + name: "Gidjit Hub", + namespace: "com.gidjit.smartthings.hub", + author: "Matthew Page", + description: "Act as an endpoint so user's of Gidjit can quickly access and control their devices and execute routines. Users can do this quickly as Gidjit filters these actions based on their environment", + category: "Convenience", + iconUrl: "http://www.gidjit.com/appicon.png", + iconX2Url: "http://www.gidjit.com/appicon@2x.png", + iconX3Url: "http://www.gidjit.com/appicon@3x.png", + oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"]) + + + +preferences { + section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") { + input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false + input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false + input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade + //input "bulbs", "capability.colorControl", title: "Control your lights", multiple: true, required: false //windowShade + + } +} +mappings { + path("/structureinfo") { + action: [ + GET: "structureInfo" + ] + } + path("/helloactions") { + action: [ + GET: "helloActions" + ] + } + path("/helloactions/:label") { + action: [ + PUT: "executeAction" + ] + } + + path("/switch/:id/:command") { + action: [ + PUT: "updateSwitch" + ] + } + + path("/thermostat/:id/:command") { + action: [ + PUT: "updateThermostat" + ] + } + + path("/windowshade/:id/:command") { + action: [ + PUT: "updateWindowShade" + ] + } + path("/acquiredata/:id") { + action: [ + GET: "acquiredata" + ] + } +} + + +def installed() { + log.debug "Installed with settings: ${settings}" + + initialize() +} + +def updated() { + log.debug "Updated with settings: ${settings}" + + unsubscribe() + initialize() +} + +def initialize() { + // subscribe to attributes, devices, locations, etc. +} +def helloActions() { + def actions = location.helloHome?.getPhrases()*.label + if(!actions) { + return [] + } + return actions +} +def executeAction() { + def actions = location.helloHome?.getPhrases()*.label + def a = actions?.find() { it == params.label } + if (!a) { + httpError(400, "invalid label $params.label") + return + } + location.helloHome?.execute(params.label) +} +/* this is the primary function called to query at the structure and its devices */ +def structureInfo() { //list all devices + def list = [:] + def currId = location.id + list[currId] = [:] + list[currId].name = location.name + list[currId].id = location.id + list[currId].temperatureScale = location.temperatureScale + list[currId].devices = [:] + + def setValues = { + if (params.brief) { + return [id: it.id, name: it.displayName] + } + def newList = [id: it.id, name: it.displayName, suppCapab: it.capabilities.collect { + "$it.name" + }, suppAttributes: it.supportedAttributes.collect { + "$it.name" + }, suppCommands: it.supportedCommands.collect { + "$it.name" + }] + + return newList + } + switches?.each { + list[currId].devices[it.id] = setValues(it) + } + thermostats?.each { + list[currId].devices[it.id] = setValues(it) + } + windowShades?.each { + list[currId].devices[it.id] = setValues(it) + } + + return list + +} +/* This function returns all of the current values of the specified Devices attributes */ +def acquiredata() { + def resp = [:] + if (!params.id) { + httpError(400, "invalid id $params.id") + return + } + def dev = switches.find() { it.id == params.id } ?: windowShades.find() { it.id == params.id } ?: + thermostats.find() { it.id == params.id } + + if (!dev) { + httpError(400, "invalid id $params.id") + return + } + def att = dev.supportedAttributes + att.each { + resp[it.name] = dev.currentValue("$it.name") + } + return resp +} + +void updateSwitch() { + // use the built-in request object to get the command parameter + def command = params.command + def sw = switches.find() { it.id == params.id } + if (!sw) { + httpError(400, "invalid id $params.id") + return + } + switch(command) { + case "on": + if ( sw.currentSwitch != "on" ) { + sw.on() + } + break + case "off": + if ( sw.currentSwitch != "off" ) { + sw.off() + } + break + default: + httpError(400, "$command is not a valid") + } +} + + +void updateThermostat() { + // use the built-in request object to get the command parameter + def command = params.command + def therm = thermostats.find() { it.id == params.id } + if (!therm || !command) { + httpError(400, "invalid id $params.id") + return + } + def passComm = [ + "off", + "heat", + "emergencyHeat", + "cool", + "fanOn", + "fanAuto", + "fanCirculate", + "auto" + + ] + def passNumParamComm = [ + "setHeatingSetpoint", + "setCoolingSetpoint", + ] + def passStringParamComm = [ + "setThermostatMode", + "setThermostatFanMode", + ] + if (command in passComm) { + therm."$command"() + } else if (command in passNumParamComm && params.p1 && params.p1.isFloat()) { + therm."$command"(Float.parseFloat(params.p1)) + } else if (command in passStringParamComm && params.p1) { + therm."$command"(params.p1) + } else { + httpError(400, "$command is not a valid command") + } +} + +void updateWindowShade() { + // use the built-in request object to get the command parameter + def command = params.command + def ws = windowShades.find() { it.id == params.id } + if (!ws || !command) { + httpError(400, "invalid id $params.id") + return + } + def passComm = [ + "open", + "close", + "presetPosition", + ] + if (command in passComm) { + ws."$command"() + } else { + httpError(400, "$command is not a valid command") + } +} +// TODO: implement event handlers \ No newline at end of file From 0c1208928fc95103dfc2144741616869f2408600 Mon Sep 17 00:00:00 2001 From: jackchi Date: Tue, 24 May 2016 15:17:40 -0700 Subject: [PATCH 3/4] [CHF-74] Add Smartsense device-health related documentation for motion sensor --- .../smartsense-motion-sensor.src/.st-ignore | 2 ++ .../smartsense-motion-sensor.src/README.md | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 devicetypes/smartthings/smartsense-motion-sensor.src/.st-ignore create mode 100644 devicetypes/smartthings/smartsense-motion-sensor.src/README.md diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/.st-ignore b/devicetypes/smartthings/smartsense-motion-sensor.src/.st-ignore new file mode 100644 index 0000000..f78b46e --- /dev/null +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/.st-ignore @@ -0,0 +1,2 @@ +.st-ignore +README.md diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/README.md b/devicetypes/smartthings/smartsense-motion-sensor.src/README.md new file mode 100644 index 0000000..82bff3f --- /dev/null +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/README.md @@ -0,0 +1,30 @@ +# Smartsense Motion Sensor + + + +Works with: + +* [Samsung SmartThings Motion Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-motion-sensor) + +## Table of contents + +* [Capabilities](#capabilities) +* [Health]($health) + +## Capabilities + +* **Configuration** - _configure()_ command called when device is installed or device preferences updated +* **Motion Sensor** - can detect motion +* **Battery** - defines device uses a battery +* **Refresh** - _refresh()_ command for status updates +* **Health Check** - indicates ability to get device health notifications + +## Device Health + +A Category C2 motion sensor that has 120min check-in interval + + + + + + From ecfb99974b4d1d71d7501772d300dd889eb54077 Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Thu, 26 May 2016 16:52:45 -0700 Subject: [PATCH 4/4] DVCSMP-1795 Philips Hue: Add support for Hue Ambience bulb -Add support for Hue White Ambiance Bulb (white,dimmable+color temp) --- .../hue-white-ambiance-bulb.groovy | 110 ++++++++++++++++++ .../hue-connect.src/hue-connect.groovy | 2 + 2 files changed, 112 insertions(+) create mode 100644 devicetypes/smartthings/hue-white-ambiance-bulb/hue-white-ambiance-bulb.groovy diff --git a/devicetypes/smartthings/hue-white-ambiance-bulb/hue-white-ambiance-bulb.groovy b/devicetypes/smartthings/hue-white-ambiance-bulb/hue-white-ambiance-bulb.groovy new file mode 100644 index 0000000..c9bedb7 --- /dev/null +++ b/devicetypes/smartthings/hue-white-ambiance-bulb/hue-white-ambiance-bulb.groovy @@ -0,0 +1,110 @@ +/** + * Hue White Ambiance Bulb + * + * Philips Hue Type "Color Temperature Light" + * + * Author: SmartThings + */ + +// for the UI +metadata { + // Automatically generated. Make future change here. + definition (name: "Hue White Ambiance Bulb", namespace: "smartthings", author: "SmartThings") { + capability "Switch Level" + capability "Actuator" + capability "Color Temperature" + capability "Switch" + capability "Refresh" + + command "refresh" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles (scale: 2){ + multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){ + tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { + attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" + attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" + attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" + attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" + } + tileAttribute ("device.level", key: "SLIDER_CONTROL") { + attributeState "level", action:"switch level.setLevel", range:"(0..100)" + } + } + + controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") { + state "colorTemperature", action:"color temperature.setColorTemperature" + } + + valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "colorTemperature", label: '${currentValue} K' + } + + standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { + state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" + } + + main(["rich-control"]) + details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"]) + } +} + +// parse events into attributes +def parse(description) { + log.debug "parse() - $description" + def results = [] + + def map = description + if (description instanceof String) { + log.debug "Hue Ambience Bulb stringToMap - ${map}" + map = stringToMap(description) + } + + if (map?.name && map?.value) { + results << createEvent(name: "${map?.name}", value: "${map?.value}") + } + results +} + +// handle commands +void on() { + log.trace parent.on(this) + sendEvent(name: "switch", value: "on") +} + +void off() { + log.trace parent.off(this) + sendEvent(name: "switch", value: "off") +} + +void setLevel(percent) { + log.debug "Executing 'setLevel'" + if (percent != null && percent >= 0 && percent <= 100) { + parent.setLevel(this, percent) + sendEvent(name: "level", value: percent) + sendEvent(name: "switch", value: "on") + } else { + log.warn "$percent is not 0-100" + } +} + +void setColorTemperature(value) { + if (value) { + log.trace "setColorTemperature: ${value}k" + parent.setColorTemperature(this, value) + sendEvent(name: "colorTemperature", value: value) + sendEvent(name: "switch", value: "on") + } else { + log.warn "Invalid color temperature" + } +} + +void refresh() { + log.debug "Executing 'refresh'" + parent.manualRefresh() +} + diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 95faa6f..0ebf042 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -323,6 +323,8 @@ private getDeviceType(hueType) { return "Hue Bulb" else if (hueType?.equalsIgnoreCase("Color Light")) return "Hue Bloom" + else if (hueType?.equalsIgnoreCase("Color Temperature Light")) + return "Hue White Ambiance Bulb" else return null }