Compare commits

...

47 Commits

Author SHA1 Message Date
Michael Macaulay
d787c1b41e MSA-1338: I believe this should replace the current app on the app store "TCP Bulbs (connect)" as it's a more reliable integration.
I did a couple of things to make this more reliable:
1.  I re-fetch the authorization token every 5 minutes
2.  If a request fails, I again re-try to fetch the token and re-submit the request
3.  I try again on 400 errors in addition to 500 and 401 errors.

Code is all open source under Apache 2.0 license hosted at github at https://github.com/mmacaula/tcp-bulbs
2016-06-04 15:02:55 -05: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
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
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
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
Jason Botello
aa3a18f421 Merge pull request #865 from SmartThingsCommunity/MSA-1255-1
MSA-1255: Gidjit - Smart Launcher with SmartThings support
2016-05-25 15:13:55 -05: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
Vinay Rao
fc6b14b85e Merge pull request #921 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-05-24 16:27:08 -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
Vinay Rao
f131fb71cf Merge pull request #918 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-05-24 13:51:26 -07:00
Vinay Rao
9c27ed6cb7 Merge pull request #913 from SmartThingsCommunity/revert-904-revert-901-DVCSMP-1739-revert-untested-code
Revert "DVCSMP-1739 Revert "Undo DVCSMP-1739 Revert "make sure reformat the storeGraphData"""
2016-05-23 16:05:09 -07:00
Dwight Liu
35edaa19c7 Revert "DVCSMP-1739 Revert "Undo DVCSMP-1739 Revert "make sure reformat the storeGraphData""" 2016-05-23 14:38:40 -07:00
Vinay Rao
dc09201866 Merge pull request #912 from larsfinander/DVCSMP-1770_Add_MSR_to_zwave-switch_staging
DVCSMP-1770 Add MSR to zwave-switch and configure at pairing
2016-05-23 14:22:40 -07:00
Lars Finander
6afcbf8f70 DVCSMP-1770 Add MSR to zwave-switch and configure at pairing
-Make ZWave switch and Dimmer switch behave the same and save MSR and
manufacturer name in data
2016-05-23 14:13:10 -07:00
rohandesai
2894d52efa Merge pull request #909 from SmartThingsCommunity/MSA-1285-3
MSA-1285: May 2016 Submission
2016-05-20 13:39:33 -07:00
Will Price
a21f9f177c MSA-1285: Updated submission, already reviewed, just cleaning up the pull request. 2016-05-20 14:22:19 -05:00
rohandesai
02f968b8cb Merge pull request #889 from SmartThingsCommunity/MSA-1278-2
MSA-1278: Updates to dynamic object execution for security compliance
2016-05-20 10:30:48 -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
Juan Pablo Risso
b105d9d80e DVCSMP-1775 - Call poll() every 5 minutes to reduce duplicated calls (#902)
* DVCSMP-1775 - Call poll() every 5 minutes to reduce duplicated calls

Call poll() every 5 minutes instead of discovery() to reduce duplicated
getActivityList() call.

* Better error logging
2016-05-19 13:58:44 -04:00
Chelsea Okey
9bfad5d6a4 Merge pull request #908 from ch3lsea/INTL-527
INTL-527 Temperature Notifications Should Respect Location Temp Scale
2016-05-19 10:41:01 -05:00
chelseaokey
85a335d365 INTL-527 Temperature Notifications Should Respect Location Temp Scale 2016-05-19 09:59:45 -05:00
Jim Anderson
40e6778e31 Merge pull request #898 from jimmyjames/add-tiles-readme
DOCS-284 adding README for example/test tiles
2016-05-18 13:58:45 -05:00
Vinay Rao
bd0ccd0c21 Merge pull request #903 from SmartThingsCommunity/staging
Rolling up staging to production Deploy May 17
2016-05-17 09:58:29 -07:00
Jim Anderson
26a0f6f939 DOCS-284 adding README for example/test tiles 2016-05-13 21:10:54 -05:00
Matthew Hartley
56eef9cf22 MSA-1278: The Prempoint SmartApp is a service bridge to allow Prempoint users to import, share, and manage devices that are controlled by their SmartThings hub. 2016-05-12 14:28:40 -05:00
Vinay Rao
566425c531 Merge pull request #869 from SmartThingsCommunity/staging
Rolling up staging to production 05-10
2016-05-10 13:40:49 -07:00
Matthew Page
ab2ba8104d 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
2016-05-07 16:51:30 -05:00
Vinay Rao
973c16f088 Merge pull request #842 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-05-03 Release
2016-05-03 10:20:53 -07:00
Vinay Rao
b05d956d95 Merge pull request #830 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-04-26 Release
2016-04-26 10:59:05 -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
15 changed files with 1472 additions and 167 deletions

View File

@@ -94,11 +94,11 @@ def parse(String description) {
def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
log.debug "Parse returned ${result?.descriptionText}"
storeGraphData(result.name, result.value)
} else {
log.debug "zwave.parse returned null command. Cannot create event"
}
log.debug "Parse returned ${result?.descriptionText}"
storeGraphData(result.name, result.value)
return result
}

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

@@ -0,0 +1,42 @@
# Device Tiles Examples and Reference
This package contains examples of Device tiles, organized by tile type.
## Purpose
Each Device Handler shows example usages of a specific tile, and is meant to represent the variety of permutations that a tile can be configured.
The various tiles can be used by QA to test tiles on all supported mobile devices, and by developers as a reference implementation.
## Installation
1. Self-publish the Device Handlers in this package.
2. Self-publish the Device Tile Controller SmartApp. The SmartApp can be found [here](https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/master/smartapps/smartthings/tile-ux/device-tile-controller.src/device-tile-controller.groovy).
3. Install the SmartApp from the Marketplace, under "My Apps".
4. Select the simulated devices you want to install and press "Done".
The simulated devices can then be found in the "Things" view of "My Home" in the mobile app.
You may wish to create a new room for these simulated devices for easy access.
## Usage
Each simulated device can be interacted with like other devices.
You can use the mobile app to interact with the tiles to see how they look and behave.
## Troubleshooting
If you get an error when installing the simulated devices using the controller SmartApp, ensure that you have published all the Device Handlers for yourself.
Also check live logging to see if there is a specific tile that is causing installation issues.
## FAQ
*Question: A tile isn't behaving as expected. What should I do?*
QA should create a JIRA ticket for any issues or inconsistencies of tiles across devices.
Developers may file a support ticket, and reference the specific tile and issue observed.
*Question: I'd like to contribute an example tile usage that would be helpful for testing and reference purposes. Can I do that?*
We recommend that you open an issue in the SmartThingsPublic repository describing the example tile and usage.
That way we can discuss with you the proposed change, and then if appropriate you can create a PR associated to the issue.

View File

@@ -13,14 +13,14 @@
* Documented Header
*
* Change 2: 2014-03-15
* Fixed bug where we weren't coming on when changing
* Fixed bug where we weren't coming on when changing
* levels down.
*
* Change 3: 2014-04-02 (lieberman)
* Change 3: 2014-04-02 (lieberman)
* Changed sendEvent() to createEvent() in parse()
*
* Change 4: 2014-04-12 (wackford)
* Added current power usage tile
* Added current power usage tile
*
* Change 5: 2014-09-14 (wackford)
* a. Changed createEvent() to sendEvent() in parse() to
@@ -33,7 +33,7 @@
* b. added refresh on udate
* c. added uninstallFromChildDevice to handle removing from settings
* d. Changed to allow bulb to 100%, was possible to get past logic at 99
*
*
* Change 7: 2014-11-09 (wackford)
* a. Added bulbpower calcs to device. TCP is broken
* b. Changed to set dim level first then on. Much easier on the eys coming from bright.
@@ -42,7 +42,7 @@
* Code
*****************************************************************
*/
// for the UI
// for the UI
metadata {
definition (name: "TCP Bulb", namespace: "wackford", author: "Todd Wackford") {
capability "Switch"
@@ -52,28 +52,26 @@ metadata {
capability "Switch Level"
attribute "stepsize", "string"
command "levelUp"
command "levelDown"
command "on"
command "off"
command "setBulbPower"
command "on"
command "off"
command "setBulbPower"
}
simulator {
// TODO: define status and reply messages here
}
preferences {
preferences {
input "stepsize", "number", title: "Step Size", description: "Dimmer Step Size", defaultValue: 5
}
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "on", label:'${name}', action:"off", icon:"st.switches.light.on", backgroundColor:"#79b821"
state "off", label:'${name}', action:"on", icon:"st.switches.light.off", backgroundColor:"#ffffff"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
@@ -84,15 +82,15 @@ metadata {
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright"
}
standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
state "default", action:"levelDown", icon:"st.illuminance.illuminance.light"
}
valueTile( "power", "device.power", inactiveLabel: false, decoration: "flat") {
state "power", label: '${currentValue} Watts'
}
standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright"
}
standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
state "default", action:"levelDown", icon:"st.illuminance.illuminance.light"
}
valueTile( "power", "device.power", inactiveLabel: false, decoration: "flat") {
state "power", label: '${currentValue} Watts'
}
main(["switch"])
details(["switch", "lUp", "lDown", "levelSliderControl", "level" , "power", "refresh" ])
@@ -103,10 +101,10 @@ metadata {
def parse(description) {
//log.debug "parse() - $description"
def results = []
if ( description == "updated" )
return
if ( description == "updated" )
return
if (description?.name && description?.value)
{
results << createEvent(name: "${description?.name}", value: "${description?.value}")
@@ -116,73 +114,73 @@ def parse(description) {
// handle commands
def setBulbPower(value) {
state.bulbPower = value
log.debug "In child with bulbPower of ${state.bulbPower}"
log.debug "In child with bulbPower of ${state.bulbPower}"
}
def on() {
log.debug "Executing 'on'"
sendEvent(name:"switch",value:on)
sendEvent(name:"switch",value:on)
parent.on(this)
def levelSetting = device.latestValue("level") as Float ?: 1.0
def bulbPowerMax = device.latestValue("setBulbPower") as Float
def calculatedPower = bulbPowerMax * (levelSetting / 100)
sendEvent(name: "power", value: calculatedPower.round(1))
if (device.latestValue("level") == null) {
//def bulbPowerMax = device.latestValue("setBulbPower") as Float
//def calculatedPower = bulbPowerMax * (levelSetting / 100)
//sendEvent(name: "power", value: calculatedPower.round(1))
if (device.latestValue("level") == null) {
sendEvent( name: "level", value: 1.0 )
}
}
}
def off() {
log.debug "Executing 'off'"
sendEvent(name:"switch",value:off)
sendEvent(name:"switch",value:off)
parent.off(this)
sendEvent(name: "power", value: 0.0)
sendEvent(name: "power", value: 0.0)
}
def levelUp() {
def level = device.latestValue("level") as Integer ?: 0
def step = state.stepsize as float
level+= step
if ( level > 100 )
level = 100
setLevel(level)
def step = state.stepsize as float
level+= step
if ( level > 100 )
level = 100
setLevel(level)
}
def levelDown() {
def level = device.latestValue("level") as Integer ?: 0
def step = state.stepsize as float
level-= step
def step = state.stepsize as float
level-= step
if ( level < 1 )
level = 1
setLevel(level)
level = 1
setLevel(level)
}
def setLevel(value) {
log.debug "in setLevel with value: ${value}"
def level = value as Integer
sendEvent( name: "level", value: level )
sendEvent( name: "switch.setLevel", value:level )
sendEvent( name: "level", value: level )
sendEvent( name: "switch.setLevel", value:level )
parent.setLevel( this, level )
if (( level > 0 ) && ( level <= 100 ))
on()
else
off()
def levelSetting = level as float
def bulbPowerMax = device.latestValue("setBulbPower") as float
def calculatedPower = bulbPowerMax * (levelSetting / 100)
sendEvent(name: "power", value: calculatedPower.round(1))
if (( level > 0 ) && ( level <= 100 ))
on()
else
off()
//def levelSetting = level as float
//def bulbPowerMax = device.latestValue("setBulbPower") as float
//def calculatedPower = bulbPowerMax * (levelSetting / 100)
//sendEvent(name: "power", value: calculatedPower.round(1))
}
def poll() {
@@ -200,29 +198,29 @@ def installed() {
}
def updated() {
initialize()
refresh()
initialize()
refresh()
}
def initialize() {
if ( !settings.stepsize )
state.stepsize = 10 //set the default stepsize
else
state.stepsize = settings.stepsize
state.stepsize = 10 //set the default stepsize
else
state.stepsize = settings.stepsize
}
/*******************************************************************************
Method :uninstalled(args)
(args) :none
returns:Nothing
ERRORS :No error handling is done
Purpose:This is standard ST method.
Gets called when "remove" is selected in child device "preferences"
tile. It also get's called when "deleteChildDevice(child)" is
called from parent service manager app.
*******************************************************************************/
Method :uninstalled(args)
(args) :none
returns:Nothing
ERRORS :No error handling is done
Purpose:This is standard ST method.
Gets called when "remove" is selected in child device "preferences"
tile. It also get's called when "deleteChildDevice(child)" is
called from parent service manager app.
*******************************************************************************/
def uninstalled() {
log.debug "Executing 'uninstall' in device type"
parent.uninstallFromChildDevice(this)
parent.uninstallFromChildDevice(this)
}

View File

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

View File

@@ -0,0 +1,679 @@
/**
* TCP Bulbs (Connect)
*
* Copyright 2014 Todd Wackford
*
* 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.
*
*/
import java.security.MessageDigest;
private apiUrl() { "https://tcp.greenwavereality.com/gwr/gop.php?" }
definition(
name: "TCP Bulbs - more reliable",
namespace: "mmacaula",
author: "Mike Macaulay",
description: "Connect your TCP bulbs to SmartThings using Cloud to Cloud integration. You must create a remote login acct on TCP Mobile App.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp@2x.png",
singleInstance: true
)
preferences {
def msg = """Tap 'Next' after you have entered in your TCP Mobile remote credentials.
Once your credentials are accepted, SmartThings will scan your TCP installation for Bulbs."""
page(name: "selectDevices", title: "Connect Your TCP Lights to SmartThings", install: false, uninstall: true, nextPage: "chooseBulbs") {
section("TCP Connected Remote Credentials") {
input "username", "text", title: "Enter TCP Remote Email/UserName", required: true
input "password", "password", title: "Enter TCP Remote Password", required: true
paragraph msg
}
}
page(name: "chooseBulbs", title: "Choose Bulbs to Control With SmartThings", content: "initialize")
}
def installed() {
debugOut "Installed with settings: ${settings}"
unschedule()
unsubscribe()
setupBulbs()
log.debug "schedule every 5 minutes syncronizeDevices)"
runEvery5Minutes(syncronizeDevices)
}
def updated() {
debugOut "Updated with settings: ${settings}"
unschedule()
setupBulbs()
log.debug "schedule update every 5 minutes syncronizeDevices)"
runEvery5Minutes(syncronizeDevices)
}
def uninstalled()
{
unschedule() //in case we have hanging runIn()'s
}
private removeChildDevices(delete)
{
debugOut "deleting ${delete.size()} bulbs"
debugOut "deleting ${delete}"
delete.each {
deleteChildDevice(it.device.deviceNetworkId)
}
}
def uninstallFromChildDevice(childDevice)
{
def errorMsg = "uninstallFromChildDevice was called and "
if (!settings.selectedBulbs) {
debugOut errorMsg += "had empty list passed in"
return
}
def dni = childDevice.device.deviceNetworkId
if ( !dni ) {
debugOut errorMsg += "could not find dni of device"
return
}
def newDeviceList = settings.selectedBulbs - dni
app.updateSetting("selectedBulbs", newDeviceList)
debugOut errorMsg += "completed succesfully"
}
def setupBulbs() {
debugOut "In setupBulbs"
def bulbs = state.devices
def deviceFile = "TCP Bulb"
selectedBulbs.each { did ->
//see if this is a selected bulb and install it if not already
def d = getChildDevice(did)
if(!d) {
def newBulb = bulbs.find { (it.did) == did }
d = addChildDevice("wackford", deviceFile, did, null, [name: "${newBulb?.name}", label: "${newBulb?.name}", completedSetup: true])
/*if ( isRoom(did) ) { //change to the multi light group icon for a room device
d.setIcon("switch", "on", "st.lights.multi-light-bulb-on")
d.setIcon("switch", "off", "st.lights.multi-light-bulb-off")
d.save()
}*/
} else {
debugOut "We already added this device"
}
}
// Delete any that are no longer in settings
def delete = getChildDevices().findAll { !selectedBulbs?.contains(it.deviceNetworkId) }
removeChildDevices(delete)
//we want to ensure syncronization between rooms and bulbs
//syncronizeDevices()
}
def initialize() {
atomicState.token = ""
getToken()
if ( atomicState.token == "error" ) {
return dynamicPage(name:"chooseBulbs", title:"TCP Login Failed!\r\nTap 'Done' to try again", nextPage:"", install:false, uninstall: false) {
section("") {}
}
} else {
"we're good to go"
debugOut "We have Token."
}
//getGatewayData() //we really don't need anything from the gateway
deviceDiscovery()
def options = devicesDiscovered() ?: []
def msg = """Tap 'Done' after you have selected the desired devices."""
return dynamicPage(name:"chooseBulbs", title:"TCP and SmartThings Connected!", nextPage:"", install:true, uninstall: true) {
section("Tap Below to View Device List") {
input "selectedBulbs", "enum", required:false, title:"Select Bulb/Fixture", multiple:true, options:options
paragraph msg
}
}
}
def deviceDiscovery() {
def data = "<gip><version>1</version><token>${atomicState.token}</token></gip>"
def Params = [
cmd: "RoomGetCarousel",
data: "${data}",
fmt: "json"
]
def cmd = toQueryString(Params)
def rooms = ""
log.debug 'trying to discover devices'
apiPost(cmd) { response ->
rooms = response.data.gip.room
}
debugOut "rooms data = ${rooms}"
def devices = []
def bulbIndex = 1
def lastRoomName = null
def deviceList = []
if ( rooms[1] == null ) {
def roomId = rooms.rid
def roomName = rooms.name
devices = rooms.device
if ( devices[1] != null ) {
debugOut "Room Device Data: did:${roomId} roomName:${roomName}"
//deviceList += ["name" : "${roomName}", "did" : "${roomId}", "type" : "room"]
devices.each({
debugOut "Bulb Device Data: did:${it?.did} room:${roomName} BulbName:${it?.name}"
deviceList += ["name" : "${roomName} ${it?.name}", "did" : "${it?.did}", "type" : "bulb"]
})
} else {
debugOut "Bulb Device Data: did:${it?.did} room:${roomName} BulbName:${it?.name}"
deviceList += ["name" : "${roomName} ${it?.name}", "did" : "${it?.did}", "type" : "bulb"]
}
} else {
rooms.each({
devices = it.device
def roomName = it.name
if ( devices[1] != null ) {
def roomId = it?.rid
debugOut "Room Device Data: did:${roomId} roomName:${roomName}"
//deviceList += ["name" : "${roomName}", "did" : "${roomId}", "type" : "room"]
devices.each({
debugOut "Bulb Device Data: did:${it?.did} room:${roomName} BulbName:${it?.name}"
deviceList += ["name" : "${roomName} ${it?.name}", "did" : "${it?.did}", "type" : "bulb"]
})
} else {
debugOut "Bulb Device Data: did:${devices?.did} room:${roomName} BulbName:${devices?.name}"
deviceList += ["name" : "${roomName} ${devices?.name}", "did" : "${devices?.did}", "type" : "bulb"]
}
})
}
devices = ["devices" : deviceList]
state.devices = devices.devices
}
Map devicesDiscovered() {
def devices = state.devices
def map = [:]
if (devices instanceof java.util.Map) {
devices.each {
def value = "${it?.name}"
def key = it?.did
map["${key}"] = value
}
} else { //backwards compatable
devices.each {
def value = "${it?.name}"
def key = it?.did
map["${key}"] = value
}
}
map
}
def getGatewayData() {
debugOut "In getGatewayData"
def data = "<gip><version>1</version><token>${atomicState.token}</token></gip>"
def qParams = [
cmd: "GatewayGetInfo",
data: "${data}",
fmt: "json"
]
def cmd = toQueryString(qParams)
apiPost(cmd) { response ->
debugOut "the gateway reponse is ${response.data.gip.gateway}"
}
}
def getToken(Closure callback) {
atomicState.token = ""
if (password) {
def hashedPassword = generateMD5(password)
def data = "<gip><version>1</version><email>${username}</email><password>${hashedPassword}</password></gip>"
def qParams = [
cmd : "GWRLogin",
data: "${data}",
fmt : "json"
]
def cmd = toQueryString(qParams)
apiPost(cmd) { response ->
def status = response.data.gip.rc
//sendNotificationEvent("Get token status ${status}")
if (status != "200") {//success code = 200
def errorText = response.data.gip.error
debugOut "Error logging into TCP Gateway. Error = ${errorText}"
atomicState.token = "error"
} else {
atomicState.token = response.data.gip.token
if(callback){
callback.call()
}
}
}
} else {
log.warn "Unable to log into TCP Gateway. Error = Password is null"
atomicState.token = "error"
}
}
def apiPost(String data, Integer retryCount = 0, Closure callback) {
debugOut "In apiPost with data: ${data}"
def params = [
uri: apiUrl(),
body: data
]
httpPost(params) {
response ->
def rc = response.data.gip.rc
if ( rc == "200" ) {
debugOut ("Return Code = ${rc} = Command Succeeded.")
callback.call(response)
} else if ( rc.startsWith("4") || rc.startsWith("5") ) {
debugOut "Return Code = ${rc} = Error: Something happened!" //Error code from gateway
sendNotificationEvent("Return Code = ${rc} = Error: Something happened! Retry # ${retryCount}" )
log.debug "Refreshing Token"
if(retryCount > 5){
// give up, send a notification
sendNotificationEvent("TCP Lighting is having Communication Errors. Error code = ${rc}. Gave up after ${retryCount} tries")
}
getToken({ ->
def updatedTokenData = data.replaceFirst("<token>[^<]*</token>", '<token>${atomicState.token}</token>')
// try again if we got our token
sendNotificationEvent('re-fetched token, trying again')
apiPost(updatedTokenData, retryCount++, callback)
})
//callback.call(response) //stubbed out so getToken works (we had race issue)
} else {
log.error "Return Code = ${rc} = Error!" //Error code from gateway
sendNotificationEvent("TCP Lighting is having Communication Errors. Error code = ${rc}. Check that TCP Gateway is online")
callback.call(response)
}
}
}
//this is not working. TCP power reporting is broken. Leave it here for future fix
def calculateCurrentPowerUse(deviceCapability, usePercentage) {
debugOut "In calculateCurrentPowerUse()"
debugOut "deviceCapability: ${deviceCapability}"
debugOut "usePercentage: ${usePercentage}"
def calcPower = usePercentage * 1000
def reportPower = calcPower.round(1) as String
debugOut "report power = ${reportPower}"
return reportPower
}
def generateSha256(String s) {
MessageDigest digest = MessageDigest.getInstance("SHA-256")
digest.update(s.bytes)
new BigInteger(1, digest.digest()).toString(16).padLeft(40, '0')
}
def generateMD5(String s) {
MessageDigest digest = MessageDigest.getInstance("MD5")
digest.update(s.bytes);
new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
}
String toQueryString(Map m) {
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
}
def checkDevicesOnline(bulbs) {
debugOut "In checkDevicesOnline()"
def onlineBulbs = []
def thisBulb = []
bulbs.each {
def dni = it?.did
thisBulb = it
def data = "<gip><version>1</version><token>${atomicState.token}</token><did>${dni}</did></gip>"
def qParams = [
cmd: "DeviceGetInfo",
data: "${data}",
fmt: "json"
]
def cmd = toQueryString(qParams)
def bulbData = []
apiPost(cmd) { response ->
bulbData = response.data.gip
}
if ( bulbData?.offline == "1" ) {
debugOut "${it?.name} is offline with offline value of ${bulbData?.offline}"
} else {
debugOut "${it?.name} is online with offline value of ${bulbData?.offline}"
onlineBulbs += thisBulb
}
}
return onlineBulbs
}
def syncronizeDevices() {
debugOut "In syncronizeDevices"
def update = getChildDevices().findAll { selectedBulbs?.contains(it.deviceNetworkId) }
update.each {
def dni = getChildDevice( it.deviceNetworkId )
debugOut "dni = ${dni}"
if (isRoom(dni)) {
pollRoom(dni)
} else {
poll(dni)
}
}
getToken()
}
boolean isRoom(dni) {
def device = state.devices.find() {(( it.type == 'room') && (it.did == "${dni}"))}
}
boolean isBulb(dni) {
def device = state.devices.find() {(( it.type == 'bulb') && (it.did == "${dni}"))}
}
def debugEvent(message, displayEvent) {
def results = [
name: "appdebug",
descriptionText: message,
displayed: displayEvent
]
log.debug "Generating AppDebug Event: ${results}"
sendEvent (results)
}
def debugOut(msg) {
log.debug msg
//sendNotificationEvent(msg) //Uncomment this for troubleshooting only
}
/**************************************************************************
Child Device Call In Methods
**************************************************************************/
def on(childDevice) {
debugOut "On request from child device"
def dni = childDevice.device.deviceNetworkId
def data = ""
def cmd = ""
if ( isRoom(dni) ) { // this is a room, not a bulb
data = "<gip><version>1</version><token>$atomicState.token</token><rid>${dni}</rid><type>power</type><value>1</value></gip>"
cmd = "RoomSendCommand"
} else {
data = "<gip><version>1</version><token>$atomicState.token</token><did>${dni}</did><type>power</type><value>1</value></gip>"
cmd = "DeviceSendCommand"
}
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
debugOut "ON result: ${response.data}"
}
//we want to ensure syncronization between rooms and bulbs
//runIn(2, "syncronizeDevices")
}
def off(childDevice) {
debugOut "Off request from child device"
def dni = childDevice.device.deviceNetworkId
def data = ""
def cmd = ""
if ( isRoom(dni) ) { // this is a room, not a bulb
data = "<gip><version>1</version><token>$atomicState.token</token><rid>${dni}</rid><type>power</type><value>0</value></gip>"
cmd = "RoomSendCommand"
} else {
data = "<gip><version>1</version><token>$atomicState.token</token><did>${dni}</did><type>power</type><value>0</value></gip>"
cmd = "DeviceSendCommand"
}
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
debugOut "${response.data}"
}
//we want to ensure syncronization between rooms and bulbs
//runIn(2, "syncronizeDevices")
}
def setLevel(childDevice, value) {
debugOut "setLevel request from child device"
def dni = childDevice.device.deviceNetworkId
def data = ""
def cmd = ""
if ( isRoom(dni) ) { // this is a room, not a bulb
data = "<gip><version>1</version><token>${atomicState.token}</token><rid>${dni}</rid><type>level</type><value>${value}</value></gip>"
cmd = "RoomSendCommand"
} else {
data = "<gip><version>1</version><token>${atomicState.token}</token><did>${dni}</did><type>level</type><value>${value}</value></gip>"
cmd = "DeviceSendCommand"
}
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
debugOut "${response.data}"
}
//we want to ensure syncronization between rooms and bulbs
//runIn(2, "syncronizeDevices")
}
// Really not called from child, but called from poll() if it is a room
def pollRoom(dni) {
debugOut "In pollRoom"
def data = ""
def cmd = ""
def roomDeviceData = []
data = "<gip><version>1</version><token>${atomicState.token}</token><rid>${dni}</rid><fields>name,power,control,status,state</fields></gip>"
cmd = "RoomGetDevices"
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
roomDeviceData = response.data.gip
}
debugOut "Room Data: ${roomDeviceData}"
def totalPower = 0
def totalLevel = 0
def cnt = 0
def onCnt = 0 //used to tally on/off states
roomDeviceData.device.each({
if ( getChildDevice(it.did) ) {
totalPower += it.other.bulbpower.toInteger()
totalLevel += it.level.toInteger()
onCnt += it.state.toInteger()
cnt += 1
}
})
def avgLevel = totalLevel/cnt
def usingPower = totalPower * (avgLevel / 100) as float
def room = getChildDevice( dni )
//the device is a room but we use same type file
sendEvent( dni, [name: "setBulbPower",value:"${totalPower}"] ) //used in child device calcs
//if all devices in room are on, room is on
if ( cnt == onCnt ) { // all devices are on
sendEvent( dni, [name: "switch",value:"on"] )
sendEvent( dni, [name: "power",value:usingPower.round(1)] )
} else { //if any device in room is off, room is off
sendEvent( dni, [name: "switch",value:"off"] )
sendEvent( dni, [name: "power",value:0.0] )
}
debugOut "Room Using Power: ${usingPower.round(1)}"
}
def poll(childDevice) {
debugOut "In poll() with ${childDevice}"
def dni = childDevice.device.deviceNetworkId
def bulbData = []
def data = ""
def cmd = ""
if ( isRoom(dni) ) { // this is a room, not a bulb
pollRoom(dni)
return
}
data = "<gip><version>1</version><token>${atomicState.token}</token><did>${dni}</did></gip>"
cmd = "DeviceGetInfo"
def qParams = [
cmd: cmd,
data: "${data}",
fmt: "json"
]
cmd = toQueryString(qParams)
apiPost(cmd) { response ->
bulbData = response.data.gip
debugOut "This Bulbs Data Return = ${bulbData}"
def bulb = getChildDevice( dni )
//set the devices power max setting to do calcs within the device type
if ( bulbData.other.bulbpower )
sendEvent( dni, [name: "setBulbPower",value:"${bulbData.other.bulbpower}"] )
if (( bulbData.state == "1" ) && ( bulb?.currentValue("switch") != "on" ))
sendEvent( dni, [name: "switch",value:"on"] )
if (( bulbData.state == "0" ) && ( bulb?.currentValue("switch") != "off" ))
sendEvent( dni, [name: "switch",value:"off"] )
//if ( bulbData.level != bulb?.currentValue("level")) {
// sendEvent( dni, [name: "level",value: "${bulbData.level}"] )
// sendEvent( dni, [name: "setLevel",value: "${bulbData.level}"] )
//}
if (( bulbData.state == "1" ) && ( bulbData.other.bulbpower )) {
def levelSetting = bulbData.level as float
def bulbPowerMax = bulbData.other.bulbpower as float
def calculatedPower = bulbPowerMax * (levelSetting / 100)
sendEvent( dni, [name: "power", value: calculatedPower.round(1)] )
}
if (( bulbData.state == "0" ) && ( bulbData.other.bulbpower ))
sendEvent( dni, [name: "power", value: 0.0] )
}
}

View File

@@ -21,6 +21,12 @@ preferences()
section("Allow Simple Control to Monitor and Control 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 "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
}
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
@@ -31,12 +37,17 @@ preferences()
mappings {
path("/devices") {
action: [
GET: "getDevices"
]
}
path("/:deviceType/devices") {
action: [
GET: "getDevices",
POST: "handleDevicesWithIDs"
]
}
path("/device/:id") {
}
path("/device/:deviceType/:id") {
action: [
GET: "getDevice",
POST: "updateDevice"
@@ -93,33 +104,40 @@ def handleDevicesWithIDs()
//log.debug("ids: ${ids}")
def command = data?.command
def arguments = data?.arguments
def type = params?.deviceType
//log.debug("device type: ${type}")
if (command)
{
def success = false
def statusCode = 404
//log.debug("command ${command}, arguments ${arguments}")
for (devId in ids)
{
def device = allDevices.find { it.id == devId }
if (device) {
if (arguments) {
//log.debug("device: ${device}")
// Check if we have a device that responds to the specified command
if (validateCommand(device, type, command)) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
}
success = true
}
else {
device."$command"()
}
statusCode = 200
} else {
//log.debug("device not found ${devId}")
statusCode = 403
}
}
if (success)
def responseData = "{}"
switch (statusCode)
{
render status: 200, data: "{}"
}
else
{
render status: 404, data: '{"msg": "Device not found"}'
case 403:
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
break
case 404:
responseData = '{"msg": "Device not found"}'
break
}
render status: statusCode, data: responseData
}
else
{
@@ -164,25 +182,101 @@ def updateDevice()
def data = request.JSON
def command = data?.command
def arguments = data?.arguments
def type = params?.deviceType
//log.debug("device type: ${type}")
//log.debug("updateDevice, params: ${params}, request: ${data}")
if (!command) {
render status: 400, data: '{"msg": "command is required"}'
} else {
def statusCode = 404
def device = allDevices.find { it.id == params.id }
if (device) {
if (arguments) {
device."$command"(*arguments)
// Check if we have a device that responds to the specified command
if (validateCommand(device, type, command)) {
if (arguments) {
device."$command"(*arguments)
}
else {
device."$command"()
}
statusCode = 200
} else {
device."$command"()
statusCode = 403
}
render status: 204, data: "{}"
} else {
render status: 404, data: '{"msg": "Device not found"}'
}
def responseData = "{}"
switch (statusCode)
{
case 403:
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
break
case 404:
responseData = '{"msg": "Device not found"}'
break
}
render status: statusCode, data: responseData
}
}
/**
* Validating the command passed by the user based on capability.
* @return boolean
*/
def validateCommand(device, deviceType, command) {
//log.debug("validateCommand ${command}")
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
//log.debug("capabilityCommands: ${capabilityCommands}")
def currentDeviceCapability = getCapabilityName(deviceType)
//log.debug("currentDeviceCapability: ${currentDeviceCapability}")
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"
case "thermostats":
return "Thermostat"
case "doorControls":
return "Door Control"
case "colorControls":
return "Color Control"
case "musicPlayers":
return "Music Player"
case "switchLevels":
return "Switch Level"
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
}
def listSubscriptions()
{
//log.debug "listSubscriptions()"
@@ -361,7 +455,13 @@ def agentDiscovery(params=[:])
}
section("Allow Simple Control to Monitor and Control These Things...")
{
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
}
}
}
@@ -672,5 +772,3 @@ def List getRealHubFirmwareVersions()
}

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

@@ -73,7 +73,8 @@ def temperatureHandler(evt) {
// TODO: Send "Temperature back to normal" SMS, turn switch off
} else {
log.debug "Temperature dropped below $tooCold: sending SMS to $phone1 and activating $mySwitch"
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:"F"}")
def tempScale = location.temperatureScale ?: "F"
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
switch1?.on()
}
}

View File

@@ -73,7 +73,8 @@ def temperatureHandler(evt) {
// TODO: Send "Temperature back to normal" SMS, turn switch off
} else {
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:"F"}")
def tempScale = location.temperatureScale ?: "F"
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
switch1?.on()
}
}

View File

@@ -339,7 +339,7 @@ def initialize() {
state.aux = 0
if (selectedhubs || selectedactivities) {
addDevice()
runEvery5Minutes("discovery")
runEvery5Minutes("poll")
}
}
@@ -394,9 +394,9 @@ def discovery() {
}
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection to the hub timed out. Please restart the hub and try again."
state.resethub = true
state.resethub = true
} catch (e) {
log.warn "Hostname in certificate didn't match. Please try again later."
log.info "Logitech Harmony - Error: $e"
}
return null
}
@@ -474,7 +474,7 @@ def activity(dni,mode) {
def poll() {
// GET THE LIST OF ACTIVITIES
if (state.HarmonyAccessToken) {
getActivityList()
getActivityList()
def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
try {
@@ -520,14 +520,17 @@ def poll() {
return "Poll completed $map - $state.hubs"
}
} catch (groovyx.net.http.HttpResponseException e) {
if (e.statusCode == 401) { // token is expired
state.remove("HarmonyAccessToken")
return "Harmony Access token has expired"
}
} catch(Exception e) {
log.trace e
}
}
if (e.statusCode == 401) { // token is expired
state.remove("HarmonyAccessToken")
log.warn "Harmony Access token has expired"
}
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection to the hub timed out. Please restart the hub and try again."
state.resethub = true
} catch (e) {
log.info "Logitech Harmony - Error: $e"
}
}
}

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