Compare commits

...

46 Commits

Author SHA1 Message Date
Juan Pablo Risso
777f8f7e20 PENG-161 - Logitech Harmony don't allow undefined commands (#965)
https://smartthings.atlassian.net/browse/PENG-161

extra )

New getCapabilityName()

Small fixes

Extra colon

capName

Added .id
2016-06-16 15:26:39 -04:00
Vinay Rao
eac48382e8 Merge pull request #993 from SmartThingsCommunity/staging
Rolling down hotfix to master
2016-06-14 15:55:53 -07:00
Vinay Rao
9f09a4b0b2 Merge pull request #991 from SmartThingsCommunity/production
Rolling down hotfix
2016-06-14 13:10:03 -07:00
Donald C. Kirker
3c47fe7b60 Merge pull request #990 from dkirker/astra-devices
Add re-type for Vision Motion Sensor Plus.
2016-06-14 11:41:04 -07:00
Donald Kirker
45a0822e9b Add re-type for Vision Motion Sensor Plus. 2016-06-14 11:07:31 -07:00
Rohan Desai
8cc87f3858 Merge pull request #966 from rohandesai/PENG-160
PENG-160 Alfred workflow should not allow undefined commands
2016-06-13 14:17:10 -07:00
Vinay Rao
e818695947 Merge pull request #987 from SmartThingsCommunity/staging
Roll down hotfix
2016-06-13 10:50:29 -07:00
Vinay Rao
bbba20288e Merge pull request #986 from larsfinander/DVCSMP-1819_Philips_Hue_Incorrect_folder_name_for_Ambiance_bulb_DTH_staging
DVCSMP-1819 Philips Hue: Incorrect folder name for Ambiance bulb DTH
2016-06-13 10:49:34 -07:00
Lars Finander
ff9dd3f6e2 DVCSMP-1819 Philips Hue: Incorrect folder name for Ambiance bulb DTH
-Renamed folder to .src
2016-06-13 10:46:25 -07:00
Vinay Rao
a94a62d34c Merge pull request #982 from rohandesai/netatmo-hotfix
NPE fix for NetAtmo
2016-06-09 11:30:51 -07:00
Rohan Desai
e861d3c256 added fix for NPE 2016-06-09 11:04:55 -07:00
Vinay Rao
7adff88d0f Merge pull request #978 from SmartThingsCommunity/master
Rolling up master to staging
2016-06-07 16:47:52 -07:00
Vinay Rao
ad1f1b2dc9 Merge pull request #977 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-06-07 16:44:35 -07:00
Vinay Rao
8d8b039dda Merge pull request #975 from SmartThingsCommunity/staging
Rolling up staging to production
2016-06-07 12:23:55 -07:00
Duncan McKee
c875547942 Merge pull request #947 from SmartThingsCommunity/DPROT-110
DPROT-110 Add manufacturer fingerprints to Z-Wave Motion Sensor
2016-06-07 12:11:17 -04:00
Zach Varberg
1676a9c381 Merge pull request #942 from varzac/update-smartsense-multi
DVCSMP-1801: mfgCode in the ZigBee library for smartsense-multi-sensor
2016-06-07 09:50:39 -05:00
Rohan Desai
49d293e749 PENG-160 UBI should not allow undefined commands
- added some more changes

added changes to alfred workflow
2016-06-03 15:25:50 -07:00
Lars Finander
5d1b033486 Merge pull request #962 from larsfinander/DVCSMP-1676_Philips_Hue_timeout_message_search
DVCSMP-1676 Philips Hue: Need to provide timeout message if search ta…
2016-06-03 10:26:29 -07:00
Lars Finander
31f77513da 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
2016-06-02 21:13:00 -07:00
rohandesai
98d7829d1a Merge pull request #957 from rohandesai/peng-158-staging
PENG-158 UBI should not allow undefined commands
2016-06-02 14:33:49 -07:00
Vinay Rao
c6f706e47a Merge pull request #960 from SmartThingsCommunity/staging
Rolling down Staging to master
2016-06-02 14:30:49 -07:00
Vinay Rao
532afd7336 Merge pull request #959 from rohandesai/delete-prempoint
removed Prempoint from staging
2016-06-02 14:30:02 -07:00
Rohan Desai
ac7f1a0c66 removed Prempoint from staging 2016-06-02 14:24:29 -07:00
Lars Finander
cb26f055d7 Merge pull request #950 from larsfinander/DVCSMP-400_Philips_Hue_Not_Yet_Configured
DVCSMP-400 Philips Hue: Hue bridge & Bulbs displaying as Not Yet Conf…
2016-06-02 12:14:44 -07:00
Rohan Desai
34107f935e PENG-158 UBI should not allow undefined commands
- now validating commands per capability of the device in the smartapp

removed commented out code
2016-06-02 11:18:54 -07:00
Lars Finander
cc2d19e951 DVCSMP-400 Philips Hue: Hue bridge & Bulbs displaying as Not Yet Configured 2016-06-01 18:06:46 -07:00
rohandesai
031a15ec86 Merge pull request #897 from rohandesai/PENG-158
PENG-158 UBI should not allow undefined commands
2016-06-01 15:22:45 -07:00
Vinay Rao
fc2db2575d Merge pull request #949 from SmartThingsCommunity/staging
Rolling down hotfix to master
2016-06-01 12:18:54 -07:00
Vinay Rao
fd549631e6 Merge pull request #948 from SmartThingsCommunity/revert-828-stage2_v1_multi_garage
Revert "DVCSMP-1107 Stage 2 of adding preferences to support garage s…
2016-06-01 12:11:49 -07:00
Vinay Rao
417c246d61 Revert "DVCSMP-1107 Stage 2 of adding preferences to support garage sensor in v1 safari mutli sensor" 2016-06-01 12:09:38 -07:00
Zach Varberg
038d770691 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
2016-06-01 10:23:30 -05:00
Duncan McKee
ce28ec2039 DPROT-110 Add manufacturer fingerprints to Z-Wave Motion Sensor 2016-05-31 18:29:40 -04:00
Vinay Rao
0f1781c02e Merge pull request #946 from SmartThingsCommunity/master
Rolling up master to staging
2016-05-31 14:22:14 -07:00
Vinay Rao
4ce6ee0890 Merge pull request #945 from SmartThingsCommunity/staging
Rolling down staging hotfixes to master
2016-05-31 14:14:13 -07:00
Vinay Rao
f8050a5cd5 Merge pull request #944 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-05-31 14:07:40 -07:00
Jack Chi
d44dac448b Merge pull request #920 from jackchi/smartsense-motion-power
[CHF-74] Add Smartsense device-health related documentation
2016-05-31 11:24:42 -07:00
Vinay Rao
67c20abcba Merge pull request #828 from workingmonk/stage2_v1_multi_garage
DVCSMP-1107 Stage 2 of adding preferences to support garage sensor in v1 safari mutli sensor
2016-05-31 11:13:58 -07:00
Duncan McKee
d56e132896 Merge pull request #941 from SmartThingsCommunity/INTL-626-prod
INTL-626 Add mfr fingerprint for Fibaro Smoke Sensor
2016-05-31 14:08:48 -04:00
Duncan McKee
009ec2539d INTL-626 Add mfr fingerprint for Fibaro Smoke Sensor 2016-05-31 14:00:59 -04:00
Lars Finander
ff0860cbe1 Merge pull request #934 from larsfinander/DVCSMP-1795_Philips_Hue_Hue_Ambience_bulb
DVCSMP-1795 Philips Hue: Add support for Hue Ambience bulb
2016-05-27 09:57:24 -07:00
Lars Finander
ecfb99974b DVCSMP-1795 Philips Hue: Add support for Hue Ambience bulb
-Add support for Hue White Ambiance Bulb (white,dimmable+color temp)
2016-05-27 09:45:26 -07:00
Vinay Rao
c2f18a91be Merge pull request #906 from SmartThingsCommunity/master
Rolling up master to staging for next week's deploy
2016-05-24 16:28:51 -07:00
jackchi
0c1208928f [CHF-74] Add Smartsense device-health related documentation for motion sensor 2016-05-24 15:17:40 -07:00
Vinay Rao
02d9963fab Merge pull request #919 from SmartThingsCommunity/staging
Rolling up staging to production for release May 24
2016-05-24 13:59:11 -07:00
Rohan Desai
32f0385e30 PENG-158 UBI should not allow undefined commands
- now validating commands per capability of the device in the smartapp

removed commented out code
2016-05-19 11:59:13 -07:00
Vinay Rao
d17cadc4c7 stage 2 of adding preferences to support garage sensor in mutli sensor 2016-04-22 17:41:57 -07:00
13 changed files with 442 additions and 476 deletions

View File

@@ -21,6 +21,7 @@ metadata {
attribute "tamper", "enum", ["detected", "clear"]
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
fingerprint mfr:"010F", prod:"0C02", model:"1002"
}
simulator {
//battery

View File

@@ -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()
}

View File

@@ -0,0 +1,2 @@
.st-ignore
README.md

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -337,10 +337,10 @@ def initialize() {
settings.devices.each {
def deviceId = it
def detail = state.deviceDetail[deviceId]
def detail = state?.deviceDetail[deviceId]
try {
switch(detail.type) {
switch(detail?.type) {
case 'NAMain':
log.debug "Base station"
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
@@ -487,12 +487,12 @@ def poll() {
log.debug "State: ${state.deviceState}"
settings.devices.each { deviceId ->
def detail = state.deviceDetail[deviceId]
def data = state.deviceState[deviceId]
def child = children.find { it.deviceNetworkId == deviceId }
def detail = state?.deviceDetail[deviceId]
def data = state?.deviceState[deviceId]
def child = children?.find { it.deviceNetworkId == deviceId }
log.debug "Update: $child";
switch(detail.type) {
switch(detail?.type) {
case 'NAMain':
log.debug "Updating NAMain $data"
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())

View File

@@ -1,345 +0,0 @@
/**
* SmartThings service for Prempoint
*
* Author: Prempoint Inc. (c) 2016
*
*/
definition(
name: "Prempoint",
namespace: "prempoint.com",
author: "Prempoint Inc.",
description: "SmartThings service for Prempoint",
category: "Connections",
iconUrl: "http://www.prempoint.com/images/social_app_emblem_50x50.png",
iconX2Url: "http://www.prempoint.com/images/social_app_emblem_100x100.png",
iconX3Url: "http://www.prempoint.com/images/social_app_emblem_150x150.png",
oauth: [displayName: "Prempoint", displayLink: "http://www.prempoint.com/"])
preferences {
section("Allow Prempoint to Control & Access These Things...") {
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
input "garagedoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false
//input "doors", "capability.doorControl", title: "Which Doors?", multiple: true, required: false
input "cameras", "capability.imageCapture", title: "Which Cameras?", multiple: true, required: false
}
}
mappings {
path("/list") {
action: [
GET: "listDevices"
]
}
path("/switches") {
action: [
GET: "listSwitches"
]
}
path("/switches/:id") {
action: [
GET: "showSwitch"
]
}
path("/switches/:id/:command") {
action: [
GET: "updateSwitch"
]
}
path("/switches/:id/:command/:level") {
action: [
GET: "updateSwitch"
]
}
path("/locks") {
action: [
GET: "listLocks"
]
}
path("/locks/:id") {
action: [
GET: "showLock"
]
}
path("/locks/:id/:command") {
action: [
GET: "updateLock"
]
}
path("/doors/:id") {
action: [
GET: "showDoor"
]
}
path("/doors/:id/:command") {
action: [
GET: "updateDoor"
]
}
path("/garagedoors/:id") {
action: [
GET: "showGarageDoor"
]
}
path("/garagedoors/:id/:command") {
action: [
GET: "updateGarageDoor"
]
}
path("/cameras/:id") {
action: [
GET: "showCamera"
]
}
path("/cameras/:id/:command") {
action: [
GET: "updateCamera"
]
}
}
def installed() {}
def updated() {}
def listDevices() {
log.debug "entering listDevices"
//return listSwitches() + listLocks() + listGarageDoors() + listDoors() + listCameras()
return listSwitches() + listLocks() + listGarageDoors() + listCameras()
}
//switches
def listSwitches() {
log.debug "entering listSwitches"
switches.collect{showDevice(it,"switch")}
}
def showSwitch() {
log.debug "entering showSwitches"
show(switches, "switch")
}
def updateSwitch() {
log.debug "entering updateSwitches"
update(switches, "switch")
}
//locks
def listLocks() {
log.debug "entering listLocks"
locks.collect{showDevice(it,"lock")}
}
def showLock() {
log.debug "entering showLock"
show(locks, "lock")
}
def updateLock() {
log.debug "entering updateLock"
update(locks, "lock")
}
//doors
def listDoors() {
log.debug "entering listDoors"
locks.collect{showDevice(it,"door")}
}
def showDoor() {
log.debug "entering showDoors"
show(doors, "door")
}
def updateDoor() {
log.debug "entering updateDoor"
update(doors, "door")
}
//garagedoors
def listGarageDoors() {
log.debug "entering listGarageDoors"
locks.collect{showDevice(it,"garagedoor")}
}
def showGarageDoor() {
log.debug "entering showGarageDoors"
show(garagedoors, "garagedoor")
}
def updateGarageDoor() {
log.debug "entering updateGarageDoor"
update(gargedoors, "garagedoor")
}
//cameras
def listCameras() {
log.debug "entering listCameras"
cameras.collect{showDevice(it,"image")}
}
def showCamera() {
log.debug "entering showCameras"
show(cameras, "camera")
}
def updateCamera() {
log.debug "entering updateCamera"
update(cameras, "camera")
}
def deviceHandler(evt) {}
private update(devices, type) {
def rc = null
//def command = request.JSON?.command
def command = params.command
log.debug "update, request: params: ${params}, devices: $devices.id type=$type command=$command"
// Process the command.
if (command)
{
def dev = devices.find { it.id == params.id }
if (!dev) {
httpError(404, "Device not found: $params.id")
} else if (type == "switch") {
switch(command) {
case "on":
rc = dev.on()
break
case "off":
rc = dev.off()
break
default:
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
}
} else if (type == "lock") {
switch(command) {
case "lock":
rc = dev.lock()
break
case "unlock":
rc = dev.unlock()
break
default:
httpError(400, "Device command=$command is not a valid for device:=$it.id $dev")
}
} else if (type == "door") {
switch(command) {
case "open":
rc = dev.open()
break
case "close":
rc = dev.close()
break
default:
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
}
} else if (type == "garagedoor") {
switch(command) {
case "open":
rc = dev.open()
break
case "close":
rc = dev.close()
break
default:
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
}
} else if (type == "camera") {
switch(command) {
case "take":
rc = dev.take()
log.debug "Device command=$command device=$it.id $dev current image=$it.currentImage"
break
default:
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
}
}
log.debug "executed device=$it.id $dev command=$command rc=$rc"
// Check that the device is a switch that is currently on, supports 'setLevel"
// and that a level was specified.
int level = params.level ? params.level as int : -1;
if ((type == "switch") && (dev.currentValue('switch') == "on") && hasLevel(dev) && (level != -1)) {
log.debug "device about to setLevel=$level"
dev.setLevel(level);
}
// Show the device info if necessary.
if (rc == null) {
rc = showDevice(dev, type)
}
}
return rc
}
private show(devices, type) {
def dev = devices.find { it.id == params.id }
if (!dev) {
httpError(404, "Device not found")
} else {
// Show the device info.
showDevice(dev, type)
}
}
private showDevice(it, type) {
def props = null
// Get the current state for the device type.
def state = [it.currentState(type)]
// Check that whether the a switch device with level support is located and update the returned device type.
def devType = type
if (type == "switch" && hasLevel(it)) {
// Assign "switchWithLevel" to device type.
devType = "switchWithLevel"
// Add the level state.
def levelState = it.currentState("level")
if (levelState) {
state.add(levelState)
}
}
log.debug "device label=$it.label type=$devType"
// Assign the device item properties if appropriate.
if (it) {
props = [id: it.id, label: it.label, type: devType, state: state]
// Add the hub information to the device properties
// if appropriate.
if (it.hub) {
props.put("location", it.hub.hub.location)
}
if (it.currentImage) {
props.put("currentImage", it.currentImage)
}
}
return props
}
private hasLevel(device) {
// Default return value.
def rc = false;
// Get the device supported commands.
def supportedCommands = device.supportedCommands
// Check to see if the "setLevel" was found and assign
// the appropriate return value.
if (supportedCommands) {
// Find the "setLevel" command.
rc = supportedCommands.toString().indexOf("setLevel") != -1
}
log.debug "hasLevel device label=$device.label supportedCommands=$supportedCommands rc=$rc"
return rc
}

View File

@@ -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]
}
@@ -323,6 +335,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
}
@@ -346,26 +360,29 @@ 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
d?.refresh()
}
} else {
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)
}
}
@@ -397,6 +414,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)
@@ -484,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"
}

View File

@@ -658,29 +658,73 @@ def updateDevice() {
def data = request.JSON
def command = data.command
def arguments = data.arguments
log.debug "updateDevice, params: ${params}, request: ${data}"
if (!command) {
render status: 400, data: '{"msg": "command is required"}'
} else {
def device = allDevices.find { it.id == params.id }
if (device) {
if (device.hasCommand("$command")) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
}
render status: 204, data: "{}"
} else {
render status: 404, data: '{"msg": "Command not supported by this Device"}'
}
} else {
render status: 404, data: '{"msg": "Device not found"}'
}
if (device) {
if (validateCommand(device, command)) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
}
render status: 204, data: "{}"
} else {
render status: 403, data: '{"msg": "Access denied. This command is not supported by current capability."}'
}
} else {
render status: 404, data: '{"msg": "Device not found"}'
}
}
}
/**
* Validating the command passed by the user based on capability.
* @return boolean
*/
def validateCommand(device, command) {
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
def currentDeviceCapability = getCapabilityName(device)
if (currentDeviceCapability != "" && 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(device) {
def capName = ""
if (switches.find{it.id == device.id})
capName = "Switch"
else if (alarms.find{it.id == device.id})
capName = "Alarm"
else if (locks.find{it.id == device.id})
capName = "Lock"
log.trace "Device: $device - Capability Name: $capName"
return capName
}
/**
* 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
}
def listSubscriptions() {
log.debug "listSubscriptions()"
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {

View File

@@ -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) {

View File

@@ -92,22 +92,87 @@ void updateLock() {
private void updateAll(devices) {
def command = request.JSON?.command
if (command) {
devices."$command"()
def type = params.param1
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
if (command) {
def device = devices.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
device."$command"()
}
def type = params.param1
def device = devices?.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
}
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, name) {