Compare commits

...

54 Commits

Author SHA1 Message Date
Eric Ryherd
c3d1c70109 MSA-687: I'm resubmitting the EZMultipli for publication as requested.
I originally submitted this back in April.
The device type works so lets get it published for everyone to use!
Thanks,
Eric
2015-11-16 07:55:35 -06:00
Vinay Rao
8b6942525d Merge pull request #280 from workingmonk/lock_raw_command
temporary changes to the lock DTH
2015-11-13 15:18:22 -08:00
Vinay Rao
1a8eab065b Merge pull request #279 from workingmonk/cree_new_api
[DEVC-226] Cree DTH refresh
2015-11-13 12:35:01 -08:00
Vinay Rao
74e3e9f70d Merge pull request #278 from workingmonk/recertified_devices
[DEVC-231] Moving re-certified device to generic zigbee DTH.
2015-11-12 20:41:31 -08:00
Vinay Rao
738d41f5af Merge pull request #276 from workingmonk/osram_new_bulbs
[DEVC-224] Osram color temperature
2015-11-12 20:30:34 -08:00
Vinay Rao
cf69d6127d temporary changes to the lock DTH to account for the upcoming zigbee library changes 2015-11-12 20:08:03 -08:00
Vinay Rao
ef4a32ac2e Cree DTH refresh 2015-11-12 19:28:57 -08:00
Vinay Rao
ac7592d132 Moving re-certified device to generic zigbee DTH. 2015-11-12 19:18:32 -08:00
Vinay Rao
a0a3cd7cf9 Merge pull request #247 from workingmonk/yale_lock
New fingerprints for Yale lock and fixing the battery issue
2015-11-12 19:07:20 -08:00
Vinay Rao
14399f8fc8 Merge pull request #277 from workingmonk/new_bulbs
[DEVC-242] Adding fingerprints for osram and sengled dimmable bulbs
2015-11-12 19:00:55 -08:00
Vinay Rao
1f76b9e960 adding fingerprints for osram and sengled dimmable bulbs 2015-11-12 17:53:21 -08:00
Vinay Rao
5d1630b367 Adding fingerprint for new bulbs and transition old color temperature osram bulbs to new one 2015-11-12 17:31:45 -08:00
Vinay Rao
0af48a5caf new fingerprints for Yale. battery issue resolution for Yale 2015-11-12 17:06:07 -08:00
Juan Pablo Risso
a5aeeed77f Merge pull request #267 from juano2310/HUE_NEW
Reverted to previously working getBridgeIP()
2015-11-09 16:30:35 -05:00
Juan Pablo Risso
0445b415f7 Reverted to previously working getBridgeIP() 2015-11-09 15:55:32 -05:00
Juan Pablo Risso
303219fcc5 Merge pull request #266 from juano2310/Harmony_Global
Replaced atomicState with State
2015-11-09 14:27:46 -05:00
Juan Pablo Risso
4e88a3ac60 Replaced atomicState with State 2015-11-09 12:56:38 -05:00
Juan Pablo Risso
9b8be908e2 Merge pull request #261 from juano2310/Harmony_Global
Harmony Global Oauth
2015-11-05 16:03:09 -05:00
Juan Pablo Risso
ea65ed58dc Added a "," 2015-11-05 14:24:15 -05:00
Juan Pablo Risso
d19ec0b525 removed function ShardUrl() 2015-11-05 14:19:20 -05:00
Juan Pablo Risso
9654c27ca8 added singleInstance: true 2015-11-05 14:15:50 -05:00
juano2310
dec9ff20b0 Harmony Global Oauth 2015-11-05 14:11:38 -05:00
Mike Robinet
c62fd52a26 Merge pull request #253 from mrobinet/crex-1094
CREX-1094 Delete stale device subscriptions on IFTTT app update
2015-11-05 11:28:43 -06:00
Juan Pablo Risso
1195161232 Merge pull request #251 from juano2310/harmony
Jawbone Global Oauth
2015-11-05 10:59:24 -05:00
Juan Pablo Risso
5f1ff8a5c6 Code Cleanup 2015-11-05 10:07:19 -05:00
bflorian
7aabd9bc5f Deleted lights on when door opens after sundown 2015-11-04 14:44:06 -08:00
bflorian
f260e36d54 More name/namespace changes 2015-11-04 14:09:51 -08:00
bflorian
594b61c852 Merge branch 'master' of https://github.com/SmartThingsCommunity/SmartThingsPublic 2015-11-04 13:59:38 -08:00
bflorian
c416560f19 Misc filename and namespace changes. 2015-11-04 13:57:17 -08:00
Juan Pablo Risso
1ee768016f Merge pull request #252 from juano2310/HUE_NEW
Force Level = 1% to 1
2015-11-04 16:11:58 -05:00
Mike Robinet
54e5334cca CREX-1094 Delete stale device subscriptions on IFTTT app update 2015-11-04 14:38:28 -06:00
Juan Pablo Risso
8c55b6314a Force Level = 1% to 1
This ensures that the bulb will be able to dim to it minimum
2015-11-04 15:07:26 -05:00
juano2310
ac5f15efd8 buildActionUrl("hookCallback") 2015-11-04 14:18:46 -05:00
juano2310
bbc680746e Jawbone Global Oauth 2015-11-04 13:05:49 -05:00
bflorian
702f0f87d0 Merge branch 'master' into staging 2015-11-04 09:41:38 -08:00
bflorian
d830c1fae0 Filename corrections 2015-11-03 18:08:51 -08:00
bflorian
f337e8a085 Corrected filename of Z-Wave Device Multichannel 2015-11-03 17:55:11 -08:00
Tom Manley
8f6201507a Merge pull request #246 from tpmanley/feature/lock-battery-fix
Fix 'Low Battery Handler' exception caused by non-integer battery events
2015-11-03 16:47:30 -06:00
Matt Nohr
0171644a77 Merge branch 'master' into staging 2015-11-02 15:45:08 -06:00
Matthew Nohr
58187006b7 Merge pull request #136 from mrobinet/master
CREX-3129 Update service manager apps to be singleton
2015-11-02 15:44:36 -06:00
Donald C. Kirker
5e6bfb5857 Merge pull request #214 from keenhome/update-keen-home-smart-vent-handler
update keen home smart vent device handler
2015-11-02 10:03:18 -08:00
Tom Manley
2d22b5a384 Fix 'Low Battery Handler' exception caused by non-integer battery events
ZigBee locks report battery percentage remaining in .5% increments. However
the Low Battery Handler Smart App in Hello Home expects it to be an integer.
2015-11-02 09:51:03 -06:00
Juan Pablo Risso
10dee44c0a Merge pull request #240 from SmartThingsCommunity/master
Master -> Staging
2015-10-31 00:21:46 -04:00
Juan Pablo Risso
20e5b51aef Merge pull request #239 from juano2310/WEMO_FFinal4
DVCSMP-1189: Wemo refactor final
2015-10-31 00:14:03 -04:00
juano2310
20f1a76889 Wemo refactor final (DVCSMP-1189)
https://smartthings.atlassian.net/browse/DVCSMP-1189

Detect and mark device offline within 5 minutes.
Show Device offline in device tile.
Show Device offline in Recent Activity.
Log the current IP address to Recent Activity.
Log the changed IP address to Recent Activity.
Support 'Turning on' and 'Turning off' (blindly changing the state of
device to ON or OFF without confirming bulb responded correctly)
Turn on / off through Wemo-App reflected timely in SmartThings
App/Ecosystem.
Manual turn on / off of device is reflected timely in SmartThings
App/Ecosystem.

Lower case createEvent

Bug Fixes

Bug fixes

setOffline

Minor cosmetic fixes
2015-10-30 21:56:01 -04:00
Juan Pablo Risso
6db15b12c1 Merge pull request #230 from juano2310/hue_ff
Fix DVCSMP-1227 & DVCSMP-1232
2015-10-30 15:08:26 -04:00
Warodom Khamphanchai
aa93850c62 Merge pull request #209 from kwarodom/aeonMultiSensor6
DVCSMP-668: Aeon Multi Sensor 6
2015-10-28 20:37:36 -07:00
Juan Pablo Risso
47210ca8b4 Fix to Hue reverts dimmer settings (DVCSMP-1227)
if you use the hue native app to adjust the dimmer setting, smartthings will reset the dimmer to previous value when toggling from ST app (and automations)
2015-10-28 14:12:35 -04:00
Mike Cousins
cbd5c91d52 update keen home smart vent device handler 2015-10-28 14:07:09 -04:00
Warodom Khamphanchai
794ff6b68a DVCSMP-668
- Show batteryStatus tile insteady of battery tile to be able to display both when sensor is USB powered or battery powered
- Remove background for illuminance. This can be added when we have best practice of showing colors for lux.
- Instead of using powerSupply:failed, configurationGet cmd is sent and then the configure() is triggered by configurationReport to determine powerSupply (USB Cable/Battery)
- Instead of querying battery level on wake up, battery report is put in association group 2 that is configured to report every 6 hours by default
- Update configure() to  send both unsecure and secure configuration commands when sensor is joined normally or securely
2015-10-21 15:48:31 -07:00
Warodom Khamphanchai
ef21fd4257 DVCSMP-668
The following changes has been made to the original Aeon Multisensor device type handler to improve and modernize it:
1. Add "powerSupply" attribute to be able to tell power source (USB Cable/Battery)
2. Add preference page for user to customize "motion delay time", "motion sensitivity", and "sensor report interval"
3. Add color backgroud of "illuminance" value tile
4. Add tile for "ultravioletIndex"
5. Add tile for "powerSupply"
6. Modify updated() to be able to send configuration commands to sensor whether it is powered by USB cable or battery
7. When battery operated, send command to get update battery level if it hasn't been reported for a while
8. Report MSR of the sensor
9. Add handle for ConfigurationReport command class to update the "powerSupply" tile and change opetion mode of the sensor accordingly
10. Update configure() to configure parameters changed by user in preference page
11. Take out the "Configure" tile and instead send the configuration commands on every wakeup (sensor is battey powered)
2015-10-21 15:48:31 -07:00
Kristofer Schaller
1965f10584 Merge pull request #192 from tslagle13/master
Fix requirement for SMS
2015-10-20 13:42:08 -07:00
tslagle13
ecb975540b Fix requirement for SMS
Removed requirement to provide a SMS number is the user does not have contacts. Add logic to verify a number was provided before sending SMS.
2015-10-14 10:05:02 -07:00
Mike Robinet
8ba5eaf74d CREX-3129 Update parent and service manager apps to be singleton 2015-09-24 15:51:51 -05:00
46 changed files with 1192 additions and 823 deletions

View File

@@ -0,0 +1,294 @@
// Express Controls EZMultiPli Multi-sensor
// Motion Sensor - Temperature - Light level - 8 Color Indicator LED - Z-Wave Range Extender - Wall Powered
// driver for SmartThings
// The EZMultiPli is also known as the HSM200 from HomeSeer.com
//
// v0.1.0 - DrZWave - chose better icons, Got color LED to work - first fully functional version
// v0.0.9 - jrs - got the temp and luminance to work. Motion works. Debugging the color wheel.
// v0.0.8 - DrZWave 2/25/2015 - change the color control to be tiles since there are only 8 colors.
// v0.0.7 - jrs - 02/23/2015 - Jim Sulin
metadata {
definition (name: "EZmultiPli", namespace: "DrZWave", author: "Eric Ryherd", oauth: true) {
capability "Motion Sensor"
capability "Temperature Measurement"
capability "Illuminance Measurement"
capability "Switch"
capability "Color Control"
capability "Configuration"
command "setColor"
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x71, 0x31, 0x33, 0x72, 0x86, 0x59, 0x85, 0x70, 0x77, 0x5A, 0x7A, 0x73, 0xEF, 0x20"
} // end definition
simulator {
// messages the device returns in response to commands it receives
status "motion" : "command: 7105000000FF07, payload: 07"
status "no motion" : "command: 7105000000FF07, payload: 00"
for (int i = 0; i <= 100; i += 20) {
status "temperature ${i}F": new physicalgraph.zwave.Zwave().sensorMultilevelV5.sensorMultilevelReport(
scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1).incomingMessage()
}
for (int i = 0; i <= 100; i += 20) {
status "luminance ${i} %": new physicalgraph.zwave.Zwave().sensorMultilevelV5.sensorMultilevelReport(
scaledSensorValue: i, precision: 0, sensorType: 3).incomingMessage()
}
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
state "color", action:"setColor"
}
} //end simulator
tiles {
standardTile("motion", "device.motion", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) {
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
}
standardTile("switch", "device.switch", canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', icon:"st.switches.light.on", backgroundColor:"#79b821"
state "turningOff", label:'${name}', icon:"st.switches.light.off", backgroundColor:"#ffffff"
}
valueTile("temperature", "device.temperature") {
state "temperature", label:'${currentValue}°', unit:"F", icon:"st.Weather.weather2", // would be better if the units would switch to the desired units of the system (imperial or metric)
backgroundColors:[
[value: 0, color: "#1010ff"], // blue=cold
[value: 65, color: "#a0a0f0"],
[value: 70, color: "#e0e050"],
[value: 75, color: "#f0d030"], // yellow
[value: 80, color: "#fbf020"],
[value: 85, color: "#fbdc01"],
[value: 90, color: "#fb3a01"],
[value: 95, color: "#fb0801"] // red=hot
]
} // icons to use would be st.Weather.weather2 or st.alarm.temperature.normal - see http://scripts.3dgo.net/smartthings/icons/ for a list of icons
valueTile("illuminance", "device.illuminance", inactiveLabel: false) {
// jrs 4/7/2015 - Null on display
// state "luminosity", label:'${currentValue} ${unit}'
state "luminosity", label:'${currentValue}%', unit:"%", icon:"st.illuminance.illuminance.bright",
backgroundColors:[
[value: 25, color: "#404040"],
[value: 50, color: "#808080"],
[value: 75, color: "#a0a0a0"],
[value: 90, color: "#e0e0e0"]
]
}
// jrs 4/7/2015
controlTile("rgbSelector", "device.color", "color", height: 1, width: 1) {
state "color", action:"color control.setColor"
}
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
main ("motion")
details(["motion", "temperature", "illuminance", "configure", "switch", "rgbSelector"])
} // end tiles
preferences {
input "OnTime", "number", title: "No Motion Interval", description: "N minutes lights stay on after no motion detected [0, 1-127]", defaultValue: 10, displayDuringSetup: true, required: false
input "OnLevel", "number", title: "Dimmer Onlevel", description: "Dimmer OnLevel for associated node 2 lights [0, 1-99, -1]", defaultValue: -1, displayDuringSetup: true, required: false
input "LiteMin", "number", title: "Luminance Report Frequency", description: "Luminance report sent every N minutes", defaultValue: 60, displayDuringSetup: true, required: false
input "TempMin", "number", title: "Temperature Report Frequency", description: "Temperature report sent every N minutes", defaultValue: 60, displayDuringSetup: true, required: false
input "TempAdj", "number", title: "Temperature Calibration", description: "Adjust temperature up/down N tenths of a degree F [(-127)-(+128)]", defaultValue: 0, displayDuringSetup: true, required: false
}
} // end metadata
// Parse incoming device messages from device to generate events
def parse(String description){
def result = []
def cmd = zwave.parse(description, [0x31: 5]) // 0x31=SensorMultilevel which we force to be version 5
if (cmd) {
result << createEvent(zwaveEvent(cmd))
}
log.debug "Parse returned ${result}"
return result
}
// Event Generation
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
def map = [:]
switch (cmd.sensorType) {
case 0x01: // SENSOR_TYPE_TEMPERATURE_VERSION_1
def cmdScale = cmd.scale == 1 ? "F" : "C"
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
map.unit = getTemperatureScale()
map.name = "temperature"
log.debug "Temperature report"
break;
case 0x03 : // SENSOR_TYPE_LUMINANCE_VERSION_1
map.value = cmd.scaledSensorValue.toInteger().toString()
map.unit = "%"
map.name = "illuminance"
log.debug "Luminance report"
break;
}
return map
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def map = [:]
if (cmd.notificationType==0x07) { // NOTIFICATION_TYPE_BURGLAR
if (cmd.event==0x07 || cmd.event==0x08) {
map.name = "motion"
map.value = "active"
map.descriptionText = "$device.displayName motion detected"
log.debug "motion recognized"
} else if (cmd.event==0) {
map.name = "motion"
map.value = "inactive"
map.descriptionText = "$device.displayName no motion detected"
log.debug "No motion recognized"
}
}
if (map.name != "motion") {
log.debug "unmatched parameters for cmd: ${cmd.toString()}}"
}
return map
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
}
def on() {
log.debug "Turning Light 'on'"
delayBetween([
zwave.basicV1.basicSet(value: 0xFF).format(),
zwave.basicV1.basicGet().format()
], 500)
}
def off() {
log.debug "Turning Light 'off'"
delayBetween([
zwave.basicV1.basicSet(value: 0x00).format(),
zwave.basicV1.basicGet().format()
], 500)
}
def setColor(value) {
log.debug "setColor() : ${value} and red value is= ${value.red}"
def cmds = []
def myred=value.red >128 ? 255 : 0 // the EZMultiPli has just on/off for each of the 3 channels RGB so convert the 0-255 value into 0 or 255.
def mygreen=value.green >128 ? 255 : 0
def myblue=value.blue>128 ? 255 : 0
// cmds << zwave.colorControlV1.stateSet(stateDataLength: 3, VariantGroup1: [0x02, myred], VariantGroup2:[ 0x03, mygreen], VariantGroup3:[0x04,myblue]).format() // ST support for this command as of 2015/02/23 does not support the color IDs so this command cannot be used.
// So instead we'll use these commands to hack around the lack of support of the above command
cmds << zwave.basicV1.basicSet(value: 0x00).format() // As of 2015/02/23 ST is not supporting stateSet properly but found this hack that works.
if (myred!=0) {
cmds << zwave.colorControlV1.startCapabilityLevelChange(capabilityId: 0x02, startState: myred, ignoreStartState: True, updown: True).format()
cmds << zwave.colorControlV1.stopStateChange(capabilityId: 0x02).format()
}
if (mygreen!=0) {
cmds << zwave.colorControlV1.startCapabilityLevelChange(capabilityId: 0x03, startState: mygreen, ignoreStartState: True, updown: True).format()
cmds << zwave.colorControlV1.stopStateChange(capabilityId: 0x03).format()
}
if (myblue!=0) {
cmds << zwave.colorControlV1.startCapabilityLevelChange(capabilityId: 0x04, startState: myblue, ignoreStartState: True, updown: True).format()
cmds << zwave.colorControlV1.stopStateChange(capabilityId: 0x04).format()
}
delayBetween(cmds, 100)
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
[:]
}
// ensure we are passing acceptable param values for LiteMin & TempMin configs
def checkLiteTempInput(value) {
if (value == null) {
value=60
}
def liteTempVal = value.toInteger()
switch (liteTempVal) {
case { it < 0 }:
return 60 // bad value, set to default
break
case { it > 127 }:
return 127 // bad value, greater then MAX, set to MAX
break
default:
return liteTempVal // acceptable value
}
}
// ensure we are passing acceptable param value for OnTime config
def checkOnTimeInput(value) {
if (value == null) {
value=10
}
def onTimeVal = value.toInteger()
switch (onTimeVal) {
case { it < 0 }:
return 10 // bad value set to default
break
case { it > 127 }:
return 127 // bad value, greater then MAX, set to MAX
break
default:
return onTimeVal // acceptable value
}
}
// ensure we are passing acceptable param value for OnLevel config
def checkOnLevelInput(value) {
if (value == null) {
value=99
}
def onLevelVal = value.toInteger()
switch (onLevelVal) {
case { it < -1 }:
return -1 // bad value set to default
break
case { it > 99 }:
return 99 // bad value, greater then MAX, set to MAX
break
default:
return onLevelVal // acceptable value
}
}
// ensure we are passing an acceptable param value for TempAdj configs
def checkTempAdjInput(value) {
if (value == null) {
value=0
}
def tempAdjVal = value.toInteger()
switch (tempAdjVal) {
case { it < -127 }:
return 0 // bad value, set to default
break
case { it > 128 }:
return 128 // bad value, greater then MAX, set to MAX
break
default:
return tempAdjVal // acceptable value
}
}
def configure() {
log.debug "OnTime=${settings.OnTime} OnLevel=${settings.OnLevel}"
def cmd = delayBetween([
zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, configurationValue: [checkOnTimeInput(settings.OnTime)]).format(),
zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, configurationValue: [checkOnLevelInput(settings.OnLevel)]).format(),
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, configurationValue: [checkLiteTempInput(settings.LiteMin)]).format(),
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, configurationValue: [checkLiteTempInput(settings.TempMin)]).format(),
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, configurationValue: [checkTempAdjInput(settings.TempAdj)]).format()
], 100)
log.debug cmd
cmd
}

View File

@@ -1,12 +1,9 @@
/**
* Keen Home Smart Vent
*
* Author: Keen Home
* Date: 2015-06-23
*/
// keen home smart vent
// http://www.keenhome.io
// SmartThings Device Handler v1.0.0
metadata {
definition (name: "Keen Home Smart Vent", namespace: "Keen Home", author: "Gregg Altschul") {
definition (name: "Keen Home Smart Vent", namespace: "Keen Home", author: "Keen Home") {
capability "Switch Level"
capability "Switch"
capability "Configuration"
@@ -21,6 +18,7 @@ metadata {
command "getBattery"
command "getTemperature"
command "setZigBeeIdTile"
command "clearObstruction"
fingerprint endpoint: "1",
profileId: "0104",
@@ -42,9 +40,10 @@ metadata {
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", action:"switch.off", icon:"st.vents.vent-open-text", backgroundColor:"#53a7c0"
state "off", action:"switch.on", icon:"st.vents.vent-closed", backgroundColor:"#ffffff"
state "obstructed", action: "switch.off", icon:"st.vents.vent-closed", backgroundColor:"#ff0000"
state "on", action: "switch.off", icon: "st.vents.vent-open-text", backgroundColor: "#53a7c0"
state "off", action: "switch.on", icon: "st.vents.vent-closed", backgroundColor: "#ffffff"
state "obstructed", action: "clearObstruction", icon: "st.vents.vent-closed", backgroundColor: "#ff0000"
state "clearing", action: "", icon: "st.vents.vent-closed", backgroundColor: "#ffff33"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
@@ -206,12 +205,12 @@ private Map makeOnOffResult(rawValue) {
private Map makeLevelResult(rawValue) {
def linkText = getLinkText(device)
// log.debug "rawValue: ${rawValue}"
def value = Integer.parseInt(rawValue, 16)
def rangeMax = 254
// catch obstruction level
if (value == 255) {
log.debug "obstructed"
log.debug "${linkText} is obstructed"
// Just return here. Once the vent is power cycled
// it will go back to the previous level before obstruction.
// Therefore, no need to update level on the display.
@@ -220,24 +219,9 @@ private Map makeLevelResult(rawValue) {
value: "obstructed",
descriptionText: "${linkText} is obstructed. Please power cycle."
]
} else if ( device.currentValue("switch") == "obstructed" &&
value == 254) {
// When the device is reset after an obstruction, the switch
// state will be obstructed and the value coming from the device
// will be 254. Since we're not using heating/cooling mode from
// the device type handler, we need to bump it down to the lower
// (cooling) range
sendEvent(makeOnOffResult(1)) // clear the obstructed switch state
value = rangeMax
}
// else if (device.currentValue("switch") == "off") {
// sendEvent(makeOnOffResult(1)) // turn back on if in off state
// }
// log.debug "pre-value: ${value}"
value = Math.floor(value / rangeMax * 100)
// log.debug "post-value: ${value}"
return [
name: "level",
@@ -327,35 +311,79 @@ private def makeSerialResult(serial) {
value: serial,
descriptionText: "${linkText} has serial ${serial}" ]
}
/**** COMMAND METHODS ****/
// def mfgCode() {
// ["zcl mfg-code 0x115B", "delay 200"]
// }
// takes a level from 0 to 100 and translates it to a ZigBee move to level with on/off command
private def makeLevelCommand(level) {
def rangeMax = 254
def scaledLevel = Math.round(level * rangeMax / 100)
log.debug "scaled level for ${level}%: ${scaledLevel}"
// convert to hex string and pad to two digits
def hexLevel = new BigInteger(scaledLevel.toString()).toString(16).padLeft(2, '0')
"st cmd 0x${device.deviceNetworkId} 1 8 4 {${hexLevel} 0000}"
}
/**** COMMAND METHODS ****/
def on() {
log.debug "on()"
def linkText = getLinkText(device)
log.debug "open ${linkText}"
// only change the state if the vent is not obstructed
if (device.currentValue("switch") == "obstructed") {
log.error("cannot open because ${linkText} is obstructed")
return
}
sendEvent(makeOnOffResult(1))
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
}
def off() {
log.debug "off()"
def linkText = getLinkText(device)
log.debug "close ${linkText}"
// only change the state if the vent is not obstructed
if (device.currentValue("switch") == "obstructed") {
log.error("cannot close because ${linkText} is obstructed")
return
}
sendEvent(makeOnOffResult(0))
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
}
// does this work?
def toggle() {
log.debug "toggle()"
def clearObstruction() {
def linkText = getLinkText(device)
log.debug "attempting to clear ${linkText} obstruction"
"st cmd 0x${device.deviceNetworkId} 1 6 2 {}"
sendEvent([
name: "switch",
value: "clearing",
descriptionText: "${linkText} is clearing obstruction"
])
// send a move command to ensure level attribute gets reset for old, buggy firmware
// then send a reset to factory defaults
// finally re-configure to ensure reports and binding is still properly set after the rtfd
[
makeLevelCommand(device.currentValue("level")), "delay 500",
"st cmd 0x${device.deviceNetworkId} 1 0 0 {}", "delay 5000"
] + configure()
}
def setLevel(value) {
log.debug "setting level: ${value}"
def linkText = getLinkText(device)
// only change the level if the vent is not obstructed
def currentState = device.currentValue("switch")
if (currentState == "obstructed") {
log.error("cannot set level because ${linkText} is obstructed")
return
}
sendEvent(name: "level", value: value)
if (value > 0) {
sendEvent(name: "switch", value: "on", descriptionText: "${linkText} is on by setting a level")
@@ -363,29 +391,26 @@ def setLevel(value) {
else {
sendEvent(name: "switch", value: "off", descriptionText: "${linkText} is off by setting level to 0")
}
def rangeMax = 254
def computedLevel = Math.round(value * rangeMax / 100)
log.debug "computedLevel: ${computedLevel}"
def level = new BigInteger(computedLevel.toString()).toString(16)
log.debug "level: ${level}"
if (level.size() < 2){
level = '0' + level
}
"st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 0000}"
makeLevelCommand(value)
}
def getOnOff() {
log.debug "getOnOff()"
// disallow on/off updates while vent is obstructed
if (device.currentValue("switch") == "obstructed") {
log.error("cannot update open/close status because ${getLinkText(device)} is obstructed")
return []
}
["st rattr 0x${device.deviceNetworkId} 1 0x0006 0"]
}
def getPressure() {
log.debug "getPressure()"
// using a Keen Home specific attribute in the pressure measurement cluster
[
"zcl mfg-code 0x115B", "delay 200",
"zcl global read 0x0403 0x20", "delay 200",
@@ -395,12 +420,13 @@ def getPressure() {
def getLevel() {
log.debug "getLevel()"
// rattr = read attribute
// 0x${} = device net id
// 1 = endpoint
// 8 = cluster id (level control, in this case)
// 0 = attribute within cluster
// sendEvent(name: "level", value: value)
// disallow level updates while vent is obstructed
if (device.currentValue("switch") == "obstructed") {
log.error("cannot update level status because ${getLinkText(device)} is obstructed")
return []
}
["st rattr 0x${device.deviceNetworkId} 1 0x0008 0x0000"]
}
@@ -425,78 +451,59 @@ def setZigBeeIdTile() {
name: "zigbeeId",
value: device.zigbeeId,
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ])
return [
return [
name: "zigbeeId",
value: device.zigbeeId,
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ]
}
def refresh() {
getOnOff() +
getOnOff() +
getLevel() +
getTemperature() +
getPressure() +
getBattery()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
def configure() {
log.debug "CONFIGURE"
log.debug "zigbeeId: ${device.hub.zigbeeId}"
// get ZigBee ID by hidden tile because that's the only way we can do it
setZigBeeIdTile()
def configCmds = [
// binding commands
// bind reporting clusters to hub
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0008 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0402 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0403 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500",
// configure report commands
// [cluster] [attr] [type] [min-interval] [max-interval] [min-change]
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500"
// mike 2015/06/22: preconfigured; see tech spec
// configure report commands
// zcl global send-me-a-report [cluster] [attr] [type] [min-interval] [max-interval] [min-change]
// report with these parameters is preconfigured in firmware, can be overridden here
// vent on/off state - type: boolean, change: 1
// "zcl global send-me-a-report 6 0 0x10 5 60 {01}", "delay 200",
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
// mike 2015/06/22: preconfigured; see tech spec
// report with these parameters is preconfigured in firmware, can be overridden here
// vent level - type: int8u, change: 1
// "zcl global send-me-a-report 8 0 0x20 5 60 {01}", "delay 200",
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
// mike 2015/06/22: temp and pressure reports are preconfigured, but
// we'd like to override their settings for our own purposes
// report with these parameters is preconfigured in firmware, can be overridden here
// temperature - type: int16s, change: 0xA = 10 = 0.1C
"zcl global send-me-a-report 0x0402 0 0x29 10 60 {0A00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
// "zcl global send-me-a-report 0x0402 0 0x29 60 60 {0A00}", "delay 200",
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
// mike 2015/06/22: use new custom pressure attribute
// pressure - type: int32u, change: 1 = 0.1Pa
"zcl mfg-code 0x115B", "delay 200",
"zcl global send-me-a-report 0x0403 0x20 0x22 10 60 {010000}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500"
// report with these parameters is preconfigured in firmware, can be overridden here
// keen home custom pressure (tenths of Pascals) - type: int32u, change: 1 = 0.1Pa
// "zcl mfg-code 0x115B", "delay 200",
// "zcl global send-me-a-report 0x0403 0x20 0x22 60 60 {010000}", "delay 200",
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
// mike 2015/06/22: preconfigured; see tech spec
// report with these parameters is preconfigured in firmware, can be overridden here
// battery - type: int8u, change: 1
// "zcl global send-me-a-report 1 0x21 0x20 60 3600 {01}", "delay 200",
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",

View File

@@ -24,6 +24,8 @@ metadata {
capability "Battery"
attribute "tamper", "enum", ["detected", "clear"]
attribute "batteryStatus", "string"
attribute "powerSupply", "enum", ["USB Cable", "Battery"]
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
}
@@ -63,6 +65,19 @@ metadata {
status "wake up" : "command: 8407, payload: "
}
preferences {
input description: "Please consult AEOTEC MULTISENSOR 6 operating manual for advanced setting options. You can skip this configuration to use default settings",
title: "Advanced Configuration", displayDuringSetup: true, type: "paragraph", element: "paragraph"
input "motionDelayTime", "enum", title: "Motion Sensor Delay Time",
options: ["20 seconds", "40 seconds", "1 minute", "2 minutes", "3 minutes", "4 minutes"], defaultValue: "${motionDelayTime}", displayDuringSetup: true
input "motionSensitivity", "enum", title: "Motion Sensor Sensitivity", options: ["normal","maximum","minimum"], defaultValue: "${motionSensitivity}", displayDuringSetup: true
input "reportInterval", "enum", title: "Sensors Report Interval",
options: ["8 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "${reportInterval}", displayDuringSetup: true
}
tiles(scale: 2) {
multiAttributeTile(name:"motion", type: "generic", width: 6, height: 4){
tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
@@ -85,53 +100,78 @@ metadata {
valueTile("humidity", "device.humidity", inactiveLabel: false, width: 2, height: 2) {
state "humidity", label:'${currentValue}% humidity', unit:""
}
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
state "illuminance", label:'${currentValue} ${unit}', unit:"lux"
}
valueTile("ultravioletIndex", "device.ultravioletIndex", inactiveLabel: false, width: 2, height: 2) {
state "ultravioletIndex", label:'${currentValue} UV index', unit:""
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main(["motion", "temperature", "humidity", "illuminance"])
details(["motion", "temperature", "humidity", "illuminance", "battery"])
valueTile("batteryStatus", "device.batteryStatus", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "batteryStatus", label:'${currentValue}', unit:""
}
valueTile("powerSupply", "device.powerSupply", height: 2, width: 2, decoration: "flat") {
state "powerSupply", label:'${currentValue} powered', backgroundColor:"#ffffff"
}
main(["motion", "temperature", "humidity", "illuminance", "ultravioletIndex"])
details(["motion", "temperature", "humidity", "illuminance", "ultravioletIndex", "batteryStatus"])
}
}
def updated()
{
if (state.sec && !isConfigured()) {
// in case we miss the SCSR
def updated() {
log.debug "Updated with settings: ${settings}"
log.debug "${device.displayName} is now ${device.latestValue("powerSupply")}"
if (device.latestValue("powerSupply") == "USB Cable") { //case1: USB powered
response(configure())
} else if (device.latestValue("powerSupply") == "Battery") { //case2: battery powered
// setConfigured("false") is used by WakeUpNotification
setConfigured("false") //wait until the next time device wakeup to send configure command after user change preference
} else { //case3: power source is not identified, ask user to properly pair the sensor again
log.warn "power source is not identified, check it sensor is powered by USB, if so > configure()"
def request = []
request << zwave.configurationV1.configurationGet(parameterNumber: 101)
response(commands(request))
}
}
def parse(String description)
{
def parse(String description) {
log.debug "parse() >> description: $description"
def result = null
if (description.startsWith("Err 106")) {
state.sec = 0
log.debug "parse() >> Err 106"
result = createEvent( name: "secureInclusion", value: "failed", isStateChange: true,
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
} else if (description != "updated") {
log.debug "parse() >> zwave.parse(description)"
def cmd = zwave.parse(description, [0x31: 5, 0x30: 2, 0x84: 1])
if (cmd) {
result = zwaveEvent(cmd)
}
}
log.debug "Parsed '${description}' to ${result.inspect()}"
log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
return result
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
{
//this notification will be sent only when device is battery powered
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) {
def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
def cmds = []
if (!isConfigured()) {
// we're still in the process of configuring a newly joined device
log.debug("late configure")
result += response(configure())
result << response(configure())
} else {
result += response(zwave.wakeUpV1.wakeUpNoMoreInformation())
log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
result << response(cmds)
}
result
}
@@ -149,10 +189,29 @@ def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulat
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
response(configure())
log.info "Executing zwaveEvent 98 (SecurityV1): 03 (SecurityCommandsSupportedReport) with cmd: $cmd"
state.sec = 1
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.NetworkKeyVerify cmd) {
state.sec = 1
log.info "Executing zwaveEvent 98 (SecurityV1): 07 (NetworkKeyVerify) with cmd: $cmd (node is securely included)"
def result = [createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful", isStateChange: true)]
result
}
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.info "Executing zwaveEvent 72 (ManufacturerSpecificV2) : 05 (ManufacturerSpecificReport) with cmd: $cmd"
log.debug "manufacturerId: ${cmd.manufacturerId}"
log.debug "manufacturerName: ${cmd.manufacturerName}"
log.debug "productId: ${cmd.productId}"
log.debug "productTypeId: ${cmd.productTypeId}"
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
updateDataValue("MSR", msr)
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def result = []
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) {
map.value = 1
@@ -162,11 +221,14 @@ def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
map.value = cmd.batteryLevel
}
state.lastbatt = now()
createEvent(map)
result << createEvent(map)
if (device.latestValue("powerSupply") != "USB Cable"){
result << createEvent(name: "batteryStatus", value: "${map.value} % battery", displayed: false)
}
result
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
{
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
def map = [:]
switch (cmd.sensorType) {
case 1:
@@ -208,7 +270,6 @@ def motionEvent(value) {
}
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
setConfigured()
motionEvent(cmd.sensorValue)
}
@@ -225,47 +286,112 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
result << createEvent(name: "tamper", value: "clear", displayed: false)
break
case 3:
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName was moved")
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName was tampered")
break
case 7:
result << motionEvent(1)
break
}
} else {
log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
result << createEvent(descriptionText: cmd.toString(), isStateChange: false)
}
result
}
def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
log.debug "ConfigurationReport: $cmd"
def result = []
def value
if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 0) {
value = "USB Cable"
if (!isConfigured()) {
log.debug("ConfigurationReport: configuring device")
result << response(configure())
}
result << createEvent(name: "batteryStatus", value: value, displayed: false)
result << createEvent(name: "powerSupply", value: value, displayed: false)
}else if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 1) {
value = "Battery"
result << createEvent(name: "powerSupply", value: value, displayed: false)
} else if (cmd.parameterNumber == 101){
result << response(configure())
}
result
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.debug "General zwaveEvent cmd: ${cmd}"
createEvent(descriptionText: cmd.toString(), isStateChange: false)
}
def configure() {
// This sensor joins as a secure device if you double-click the button to include it
if (device.device.rawDescription =~ /98/ && !state.sec) {
log.debug "Multi 6 not sending configure until secure"
return []
}
log.debug "Multi 6 configure()"
def request = [
// send no-motion report 20 seconds after motion stops
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: 20),
log.debug "${device.displayName} is configuring its settings"
def request = []
// report every 8 minutes (threshold reports don't work on battery power)
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 8*60),
//1. set association groups for hub
request << zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)
// report automatically on threshold change
zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: 1),
request << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId)
//2. automatic report flags
// param 101 -103 [4 bytes] 128: light sensor, 64 humidity, 32 temperature sensor, 2 ultraviolet sensor, 1 battery sensor -> send command 227 to get all reports
request << zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 226) //association group 1
request << zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1) //association group 2
//3. no-motion report x seconds after motion stops (default 20 secs)
request << zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: timeOptionValueMap[motionDelayTime] ?: 20)
//4. motionSensitivity 3 levels: 64-normal (default), 127-maximum, 0-minimum
request << zwave.configurationV1.configurationSet(parameterNumber: 6, size: 1,
scaledConfigurationValue:
motionSensitivity == "normal" ? 64 :
motionSensitivity == "maximum" ? 127 :
motionSensitivity == "minimum" ? 0 : 64)
//5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: 8*60) //association group 1
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2
//6. report automatically on threshold change
request << zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: 1)
//7. query sensor data
request << zwave.batteryV1.batteryGet()
request << zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 0x0C) //motion
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01) //temperature
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x03) //illuminance
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x05) //humidity
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x1B) //ultravioletIndex
setConfigured("true")
zwave.batteryV1.batteryGet(),
zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 0x0C),
]
commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
}
private setConfigured() {
updateDataValue("configured", "true")
private def getTimeOptionValueMap() { [
"20 seconds" : 20,
"40 seconds" : 40,
"1 minute" : 60,
"2 minutes" : 2*60,
"3 minutes" : 3*60,
"4 minutes" : 4*60,
"5 minutes" : 5*60,
"8 minutes" : 8*60,
"15 minutes" : 15*60,
"30 minutes" : 30*60,
"1 hours" : 1*60*60,
"6 hours" : 6*60*60,
"12 hours" : 12*60*60,
"18 hours" : 6*60*60,
"24 hours" : 24*60*60,
]}
private setConfigured(configure) {
updateDataValue("configured", configure)
}
private isConfigured() {
@@ -281,5 +407,6 @@ private command(physicalgraph.zwave.Command cmd) {
}
private commands(commands, delay=200) {
log.info "sending commands: ${commands}"
delayBetween(commands.collect{ command(it) }, delay)
}

View File

@@ -38,168 +38,63 @@ metadata {
}
// UI tile definitions
tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
main(["switch"])
details(["switch", "level", "levelSliderControl", "refresh"])
}
tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch"
details(["switch", "refresh"])
}
}
// Parse incoming device messages to generate events
def parse(String description) {
log.trace description
if (description?.startsWith("catchall:")) {
def msg = zigbee.parse(description)
log.trace msg
log.trace "data: $msg.data"
if(description?.endsWith("0100") ||description?.endsWith("1001"))
{
def result = createEvent(name: "switch", value: "on")
log.debug "Parse returned ${result?.descriptionText}"
return result
log.debug "description is $description"
def resultMap = zigbee.getKnownDescription(description)
if (resultMap) {
log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
if(description?.endsWith("0000") || description?.endsWith("1000"))
{
def result = createEvent(name: "switch", value: "off")
log.debug "Parse returned ${result?.descriptionText}"
return result
else {
sendEvent(name: resultMap.type, value: resultMap.value)
}
}
if (description?.startsWith("read attr")) {
log.debug description[-2..-1]
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
sendEvent( name: "level", value: i )
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
}
}
def off() {
zigbee.off()
}
def on() {
log.debug "on()"
sendEvent(name: "switch", value: "on")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
}
def off() {
log.debug "off()"
sendEvent(name: "switch", value: "off")
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}
def refresh() {
// Schedule poll every 1 min
//schedule("0 */1 * * * ?", poll)
//poll()
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0"
]
zigbee.on()
}
def setLevel(value) {
log.trace "setLevel($value)"
def cmds = []
zigbee.setLevel(value)
}
if (value == 0) {
sendEvent(name: "switch", value: "off")
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {0000 0000}"
}
else if (device.latestValue("switch") == "off") {
sendEvent(name: "switch", value: "on")
}
sendEvent(name: "level", value: value)
def level = hex(value * 255/100)
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
//log.debug cmds
cmds
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
def configCmds = [
//Switch Reporting
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",
//Level Control Reporting
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
]
return configCmds + refresh() // send refresh cmds as part of config
}
def uninstalled() {
log.debug "uninstalled()"
response("zcl rftd")
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = "0" + s
}
s
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
log.debug "Configuring Reporting and Bindings."
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
}

View File

@@ -50,7 +50,7 @@ metadata {
capability "Switch Level"
capability "Polling"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
}
// UI tile definitions

View File

@@ -26,8 +26,6 @@ metadata {
capability "Actuator"
capability "Sensor"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45852"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45857"
}
// simulator metadata

View File

@@ -26,8 +26,6 @@ metadata {
capability "Actuator"
capability "Sensor"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "0003, 000A,0019", manufacturer: "Jasco Products", model: "45853"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45856"
}
// simulator metadata

View File

@@ -0,0 +1,81 @@
/**
* Logitech Harmony Activity
*
* Copyright 2015 Juan Risso
*
* 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.
*
*/
metadata {
definition (name: "Logitech Harmony Activity", namespace: "smartthings", author: "Juan Risso") {
capability "Switch"
capability "Actuator"
capability "Refresh"
command "huboff"
command "alloff"
command "refresh"
}
// simulator metadata
simulator {
}
// UI tile definitions
tiles {
standardTile("button", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "off", label: 'Off', action: "switch.on", icon: "st.harmony.harmony-hub-icon", backgroundColor: "#ffffff", nextState: "on"
state "on", label: 'On', action: "switch.off", icon: "st.harmony.harmony-hub-icon", backgroundColor: "#79b821", nextState: "off"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("forceoff", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:'Force End', action:"switch.off", icon:"st.secondary.off"
}
standardTile("huboff", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:'End Hub Action', action:"huboff", icon:"st.harmony.harmony-hub-icon"
}
standardTile("alloff", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:'All Actions', action:"alloff", icon:"st.secondary.off"
}
main "button"
details(["button", "refresh", "forceoff", "huboff", "alloff"])
}
}
def parse(String description) {
}
def on() {
sendEvent(name: "switch", value: "on")
log.trace parent.activity(device.deviceNetworkId,"start")
}
def off() {
sendEvent(name: "switch", value: "off")
log.trace parent.activity(device.deviceNetworkId,"end")
}
def huboff() {
sendEvent(name: "switch", value: "off")
log.trace parent.activity(device.deviceNetworkId,"hub")
}
def alloff() {
sendEvent(name: "switch", value: "off")
log.trace parent.activity("all","end")
}
def refresh() {
log.debug "Executing 'refresh'"
log.trace parent.poll()
}

View File

@@ -5,6 +5,8 @@
that issue by using state variables
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "OSRAM LIGHTIFY LED Tunable White 60W", namespace: "smartthings", author: "SmartThings") {
@@ -20,10 +22,7 @@ metadata {
// indicates that device keeps track of heartbeat (in state.heartbeat)
attribute "heartbeat", "string"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White"
}

View File

@@ -11,6 +11,9 @@
* for the specific language governing permissions and limitations under the License.
*
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "Sylvania Ultra iQ", namespace:"smartthings", author: "SmartThings") {
capability "Switch Level"

View File

@@ -1,5 +1,5 @@
metadata {
definition (name: "Color Control Capability", namespace: "capabilities", author: "SmartThings") {
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
capability "Color Control"
}

View File

@@ -15,6 +15,8 @@
* Thanks to Chad Monroe @cmonroe and Patrick Stuart @pstuart
*
*/
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata {
definition (name: "WeMo Bulb", namespace: "smartthings", author: "SmartThings") {
@@ -25,7 +27,6 @@ metadata {
capability "Switch"
capability "Switch Level"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,FF00", outClusters: "0019"
}
// simulator metadata

View File

@@ -25,6 +25,8 @@ metadata {
capability "Refresh"
capability "Sensor"
attribute "currentIP", "string"
command "subscribe"
command "resubscribe"
command "unsubscribe"
@@ -34,21 +36,36 @@ metadata {
// simulator metadata
simulator {}
// UI tile definitions
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}', icon:"st.switches.switch.on", backgroundColor:"#79b821"
state "turningOff", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
// UI tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
}
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
attributeState "currentIP", label: ''
}
}
main "switch"
details (["switch", "refresh"])
}
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
state "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
}
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["rich-control", "refresh"])
}
}
// parse events into attributes
@@ -68,6 +85,7 @@ def parse(String description) {
def result = []
def bodyString = msg.body
if (bodyString) {
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) {
@@ -78,13 +96,14 @@ def parse(String description) {
} else if (body?.property?.BinaryState?.text()) {
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
log.trace "Notify: BinaryState = ${value}"
result << createEvent(name: "switch", value: value)
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}")
} else if (body?.property?.TimeZoneNotification?.text()) {
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
log.trace "GetBinaryResponse: BinaryState = ${value}"
result << createEvent(name: "switch", value: value)
def dispaux = device.currentValue("switch") != value
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux)
}
}
@@ -101,14 +120,6 @@ private getCallBackAddress() {
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress() {
def ip = getDataValue("ip")
def port = getDataValue("port")
@@ -195,6 +206,8 @@ def subscribe(ip, port) {
if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip)
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
}
if (port && port != existingPort) {
log.debug "Updating port from $existingPort to $port"
@@ -259,6 +272,8 @@ User-Agent: CyberGarage-HTTP/1.0
def poll() {
log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277
@@ -274,3 +289,15 @@ User-Agent: CyberGarage-HTTP/1.0
</s:Body>
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
}
def setOffline() {
sendEvent(name: "switch", value: "offline", descriptionText: "The device is offline")
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

View File

@@ -21,6 +21,8 @@
capability "Refresh"
capability "Sensor"
attribute "currentIP", "string"
command "subscribe"
command "resubscribe"
command "unsubscribe"
@@ -31,17 +33,30 @@
}
// UI tile definitions
tiles {
tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "motion", canChangeIcon: true){
tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
attributeState "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
attributeState "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
attributeState "offline", label:'${name}', icon:"st.motion.motion.active", backgroundColor:"#ff0000"
}
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
attributeState "currentIP", label: ''
}
}
standardTile("motion", "device.motion", width: 2, height: 2) {
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
}
standardTile("refresh", "device.motion", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
state("offline", label:'${name}', icon:"st.motion.motion.inactive", backgroundColor:"#ff0000")
}
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "motion"
details (["motion", "refresh"])
details (["rich-control", "refresh"])
}
}
@@ -62,6 +77,7 @@ def parse(String description) {
def result = []
def bodyString = msg.body
if (bodyString) {
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) {
@@ -72,7 +88,7 @@ def parse(String description) {
} else if (body?.property?.BinaryState?.text()) {
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "active" : "inactive"
log.debug "Notify - BinaryState = ${value}"
result << createEvent(name: "motion", value: value)
result << createEvent(name: "motion", value: value, descriptionText: "Motion is ${value}")
} else if (body?.property?.TimeZoneNotification?.text()) {
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
}
@@ -91,14 +107,6 @@ private getCallBackAddress() {
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress() {
def ip = getDataValue("ip")
def port = getDataValue("port")
@@ -125,6 +133,8 @@ def refresh() {
////////////////////////////
def getStatus() {
log.debug "Executing WeMo Motion 'getStatus'"
if (device.currentValue("currentIP") != "Offline")
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277
@@ -165,7 +175,9 @@ def subscribe(ip, port) {
def existingPort = getDataValue("port")
if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip)
updateDataValue("ip", ip)
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
}
if (port && port != existingPort) {
log.debug "Updating port from $existingPort to $port"
@@ -226,3 +238,15 @@ User-Agent: CyberGarage-HTTP/1.0
</s:Envelope>
""", physicalgraph.device.Protocol.LAN)
}
def setOffline() {
sendEvent(name: "motion", value: "offline", descriptionText: "The device is offline")
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

View File

@@ -10,120 +10,142 @@
* 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.
*
* Wemo Switch
* Wemo Switch
*
* Author: superuser
* Date: 2013-10-11
* Author: Juan Risso (SmartThings)
* Date: 2015-10-11
*/
metadata {
definition (name: "Wemo Switch", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
definition (name: "Wemo Switch", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Switch"
capability "Polling"
capability "Refresh"
capability "Sensor"
command "subscribe"
command "resubscribe"
command "unsubscribe"
}
attribute "currentIP", "string"
// simulator metadata
simulator {}
command "subscribe"
command "resubscribe"
command "unsubscribe"
}
// UI tile definitions
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"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
// simulator metadata
simulator {}
main "switch"
details (["switch", "refresh"])
}
// UI tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
}
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
attributeState "currentIP", label: ''
}
}
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
state "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
}
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["rich-control", "refresh"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
log.debug "Parsing '${description}'"
def msg = parseLanMessage(description)
def headerString = msg.header
def msg = parseLanMessage(description)
def headerString = msg.header
if (headerString?.contains("SID: uuid:")) {
def sid = (headerString =~ /SID: uuid:.*/) ? ( headerString =~ /SID: uuid:.*/)[0] : "0"
sid -= "SID: uuid:".trim()
if (headerString?.contains("SID: uuid:")) {
def sid = (headerString =~ /SID: uuid:.*/) ? ( headerString =~ /SID: uuid:.*/)[0] : "0"
sid -= "SID: uuid:".trim()
updateDataValue("subscriptionId", sid)
}
updateDataValue("subscriptionId", sid)
}
def result = []
def bodyString = msg.body
if (bodyString) {
def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) {
log.trace "Got TimeSyncRequest"
result << timeSyncResponse()
} else if (body?.Body?.SetBinaryStateResponse?.BinaryState?.text()) {
log.trace "Got SetBinaryStateResponse = ${body?.Body?.SetBinaryStateResponse?.BinaryState?.text()}"
} else if (body?.property?.BinaryState?.text()) {
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
log.trace "Notify: BinaryState = ${value}"
result << createEvent(name: "switch", value: value)
} else if (body?.property?.TimeZoneNotification?.text()) {
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
log.trace "GetBinaryResponse: BinaryState = ${value}"
result << createEvent(name: "switch", value: value)
}
}
result
def result = []
def bodyString = msg.body
if (bodyString) {
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) {
log.trace "Got TimeSyncRequest"
result << timeSyncResponse()
} else if (body?.Body?.SetBinaryStateResponse?.BinaryState?.text()) {
log.trace "Got SetBinaryStateResponse = ${body?.Body?.SetBinaryStateResponse?.BinaryState?.text()}"
} else if (body?.property?.BinaryState?.text()) {
def value = body?.property?.BinaryState?.text().substring(0, 1).toInteger() == 0 ? "off" : "on"
log.trace "Notify: BinaryState = ${value}, ${body.property.BinaryState}"
def dispaux = device.currentValue("switch") != value
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux)
} else if (body?.property?.TimeZoneNotification?.text()) {
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().substring(0, 1).toInteger() == 0 ? "off" : "on"
log.trace "GetBinaryResponse: BinaryState = ${value}, ${body.property.BinaryState}"
log.info "Connection: ${device.currentValue("connection")}"
if (device.currentValue("currentIP") == "Offline") {
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "IP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
}
def dispaux2 = device.currentValue("switch") != value
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux2)
}
}
result
}
private getTime() {
// This is essentially System.currentTimeMillis()/1000, but System is disallowed by the sandbox.
((new GregorianCalendar().time.time / 1000l).toInteger()).toString()
// This is essentially System.currentTimeMillis()/1000, but System is disallowed by the sandbox.
((new GregorianCalendar().time.time / 1000l).toInteger()).toString()
}
private getCallBackAddress() {
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress() {
def ip = getDataValue("ip")
def port = getDataValue("port")
if (!ip || !port) {
def parts = device.deviceNetworkId.split(":")
if (parts.length == 2) {
ip = parts[0]
port = parts[1]
} else {
log.warn "Can't figure out ip and port for device: ${device.id}"
}
}
log.debug "Using ip: ${ip} and port: ${port} for device: ${device.id}"
return convertHexToIP(ip) + ":" + convertHexToInt(port)
def ip = getDataValue("ip")
def port = getDataValue("port")
if (!ip || !port) {
def parts = device.deviceNetworkId.split(":")
if (parts.length == 2) {
ip = parts[0]
port = parts[1]
} else {
log.warn "Can't figure out ip and port for device: ${device.id}"
}
}
log.debug "Using ip: ${ip} and port: ${port} for device: ${device.id}"
return convertHexToIP(ip) + ":" + convertHexToInt(port)
}
def on() {
log.debug "Executing 'on'"
sendEvent(name: "switch", value: "on")
log.debug "Executing 'on'"
def turnOn = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPAction: "urn:Belkin:service:basicevent:1#SetBinaryState"
Host: ${getHostAddress()}
@@ -133,17 +155,16 @@ Content-Length: 333
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
<BinaryState>1</BinaryState>
</m:SetBinaryState>
</m:SetBinaryState>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
}
def off() {
log.debug "Executing 'off'"
sendEvent(name: "switch", value: "off")
def turnOff = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
log.debug "Executing 'off'"
def turnOff = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPAction: "urn:Belkin:service:basicevent:1#SetBinaryState"
Host: ${getHostAddress()}
Content-Type: text/xml
@@ -152,36 +173,13 @@ Content-Length: 333
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
<BinaryState>0</BinaryState>
</m:SetBinaryState>
</m:SetBinaryState>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
}
/*def refresh() {
log.debug "Executing 'refresh'"
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277
Content-Type: text/xml; charset="utf-8"
HOST: ${getHostAddress()}
User-Agent: CyberGarage-HTTP/1.0
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1">
</u:GetBinaryState>
</s:Body>
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
}*/
def refresh() {
log.debug "Executing WeMo Switch 'subscribe', then 'timeSyncResponse', then 'poll'"
[subscribe(), timeSyncResponse(), poll()]
}
def subscribe(hostAddress) {
log.debug "Executing 'subscribe()'"
def address = getCallBackAddress()
@@ -200,27 +198,30 @@ def subscribe() {
subscribe(getHostAddress())
}
def subscribe(ip, port) {
def existingIp = getDataValue("ip")
def existingPort = getDataValue("port")
if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip)
}
if (port && port != existingPort) {
log.debug "Updating port from $existingPort to $port"
updateDataValue("port", port)
}
def refresh() {
log.debug "Executing WeMo Switch 'subscribe', then 'timeSyncResponse', then 'poll'"
[subscribe(), timeSyncResponse(), poll()]
}
def subscribe(ip, port) {
def existingIp = getDataValue("ip")
def existingPort = getDataValue("port")
if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip)
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
}
if (port && port != existingPort) {
log.debug "Updating port from $existingPort to $port"
updateDataValue("port", port)
}
subscribe("${ip}:${port}")
}
////////////////////////////
def resubscribe() {
log.debug "Executing 'resubscribe()'"
def sid = getDeviceDataByName("subscriptionId")
log.debug "Executing 'resubscribe()'"
def sid = getDeviceDataByName("subscriptionId")
new physicalgraph.device.HubAction("""SUBSCRIBE /upnp/event/basicevent1 HTTP/1.1
HOST: ${getHostAddress()}
SID: uuid:${sid}
@@ -228,12 +229,11 @@ TIMEOUT: Second-5400
""", physicalgraph.device.Protocol.LAN)
}
////////////////////////////
def unsubscribe() {
def sid = getDeviceDataByName("subscriptionId")
def sid = getDeviceDataByName("subscriptionId")
new physicalgraph.device.HubAction("""UNSUBSCRIBE publisher path HTTP/1.1
HOST: ${getHostAddress()}
SID: uuid:${sid}
@@ -242,7 +242,7 @@ SID: uuid:${sid}
""", physicalgraph.device.Protocol.LAN)
}
////////////////////////////
//TODO: Use UTC Timezone
def timeSyncResponse() {
log.debug "Executing 'timeSyncResponse()'"
@@ -267,9 +267,15 @@ User-Agent: CyberGarage-HTTP/1.0
""", physicalgraph.device.Protocol.LAN)
}
def setOffline() {
//sendEvent(name: "currentIP", value: "Offline", displayed: false)
sendEvent(name: "switch", value: "offline", descriptionText: "The device is offline")
}
def poll() {
log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277

View File

@@ -24,6 +24,9 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-CIA19NAE26", deviceJoinName: "Sengled Element touch"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45852", deviceJoinName: "GE Zigbee Plug-In Dimmer"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45857", deviceJoinName: "GE Zigbee In-Wall Dimmer"
}
tiles(scale: 2) {

View File

@@ -23,6 +23,9 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
}
tiles(scale: 2) {

View File

@@ -32,7 +32,13 @@
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10T", deviceJoinName: "Kwikset 10-Button Touch Deadbolt"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale YRL220 Lock"
manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale Touch Screen Lever Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
}
tiles(scale: 2) {
@@ -85,11 +91,24 @@ def uninstalled() {
}
def configure() {
/*
def cmds =
zigbee.configSetup("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}",
"${TYPE_ENUM8}", 0, 3600, "{01}") +
zigbee.configSetup("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}",
"${TYPE_U8}", 3600, 3600, "{01}")
"${TYPE_U8}", 600, 21600, "{01}")
*/
def zigbeeId = device.zigbeeId
def cmds =
[
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_DOORLOCK} {$zigbeeId} {}", "delay 200",
"zcl global send-me-a-report ${CLUSTER_DOORLOCK} ${DOORLOCK_ATTR_LOCKSTATE} ${TYPE_ENUM8} 0 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_POWER} {$zigbeeId} {}", "delay 200",
"zcl global send-me-a-report ${CLUSTER_POWER} ${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING} ${TYPE_U8} 600 21600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
]
log.info "configure() --- cmds: $cmds"
return cmds + refresh() // send refresh cmds as part of config
}
@@ -119,13 +138,15 @@ def parse(String description) {
def lock() {
def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
log.info "lock() -- cmds: $cmds"
return cmds
//return cmds
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_LOCK_DOOR} {}"
}
def unlock() {
def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
log.info "unlock() -- cmds: $cmds"
return cmds
//return cmds
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_UNLOCK_DOOR} {}"
}
// Private methods
@@ -139,8 +160,10 @@ private Map parseReportAttributeMessage(String description) {
Map resultMap = [:]
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
resultMap.name = "battery"
// BatteryPercentageRemaining is specified in .5% increments
resultMap.value = Integer.parseInt(descMap.value, 16) / 2
resultMap.value = Math.round(Integer.parseInt(descMap.value, 16) / 2)
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
resultMap.value = Integer.parseInt(descMap.value, 16)
}
log.info "parseReportAttributeMessage() --- battery: ${resultMap.value}"
}
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {

View File

@@ -23,6 +23,8 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0B04"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "0003, 000A,0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch"
}
tiles(scale: 2) {

View File

@@ -31,6 +31,10 @@ metadata {
command "setGenericName"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
}
// UI tile definitions

View File

@@ -20,7 +20,8 @@ definition(
description: "Use this free SmartApp in conjunction with the ObyThing Music app for your Mac to control and automate music and more with iTunes and SmartThings.",
category: "SmartThings Labs",
iconUrl: "http://obycode.com/obything/ObyThingSTLogo.png",
iconX2Url: "http://obycode.com/obything/ObyThingSTLogo@2x.png")
iconX2Url: "http://obycode.com/obything/ObyThingSTLogo@2x.png",
singleInstance: true)
preferences {

View File

@@ -22,7 +22,8 @@ definition(
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
oauth: true
oauth: true,
singleInstance: true
){
appSetting "clientId"
appSetting "clientSecret"

View File

@@ -13,11 +13,11 @@ definition(
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/jawbone-up.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/jawbone-up@2x.png",
oauth: true,
usePreferencesForAuthorization: false
usePreferencesForAuthorization: false,
singleInstance: true
) {
appSetting "clientId"
appSetting "clientSecret"
appSetting "serverUrl"
}
preferences {
@@ -28,16 +28,13 @@ mappings {
path("/receivedToken") { action: [ POST: "receivedToken", GET: "receivedToken"] }
path("/receiveToken") { action: [ POST: "receiveToken", GET: "receiveToken"] }
path("/hookCallback") { action: [ POST: "hookEventHandler", GET: "hookEventHandler"] }
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
path("/oauth/callback") { action: [ GET: "callback" ] }
}
def getSmartThingsClientId() {
return appSettings.clientId
}
def getSmartThingsClientSecret() {
return appSettings.clientSecret
}
def getServerUrl() { return "https://graph.api.smartthings.com" }
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" }
def buildRedirectUrl(page) { return buildActionUrl(page) }
def callback() {
def redirectUrl = null
@@ -63,9 +60,8 @@ def callback() {
// SmartThings code, which we ignore, as we don't need to exchange for an access token.
// Instead, go initiate the Jawbone OAuth flow.
log.debug "Executing callback redirect to auth page"
def stcid = getSmartThingsClientId()
state.oauthInitState = UUID.randomUUID().toString()
def oauthParams = [response_type: "code", client_id: stcid, scope: "move_read sleep_read", redirect_uri: "${serverUrl}/oauth/callback"]
def oauthParams = [response_type: "code", client_id: appSettings.clientId, scope: "move_read sleep_read", redirect_uri: "${serverUrl}/oauth/callback"]
redirect(location: "https://jawbone.com/auth/oauth2/auth?${toQueryString(oauthParams)}")
}
} else {
@@ -84,10 +80,11 @@ def authPage() {
createAccessToken()
}
description = "Click to enter Jawbone Credentials"
def redirectUrl = oauthInitUrl()
// log.debug "RedirectURL = ${redirectUrl}"
return dynamicPage(name: "Credentials", title: "Jawbone UP", nextPage: null, uninstall: true, install:false) {
section { href url:redirectUrl, style:"embedded", required:true, title:"Jawbone UP", description:description }
def redirectUrl = buildRedirectUrl
log.debug "RedirectURL = ${redirectUrl}"
def donebutton= state.JawboneAccessToken != null
return dynamicPage(name: "Credentials", title: "Jawbone UP", nextPage: null, uninstall: true, install: donebutton) {
section { href url:redirectUrl, style:"embedded", required:true, title:"Jawbone UP", state: hast ,description:description }
}
} else {
description = "Jawbone Credentials Already Entered."
@@ -99,17 +96,14 @@ def authPage() {
def oauthInitUrl() {
log.debug "oauthInitUrl"
def stcid = getSmartThingsClientId()
state.oauthInitState = UUID.randomUUID().toString()
def oauthParams = [ response_type: "code", client_id: stcid, scope: "move_read sleep_read", redirect_uri: buildRedirectUrl("receiveToken") ]
return "https://jawbone.com/auth/oauth2/auth?${toQueryString(oauthParams)}"
def oauthParams = [ response_type: "code", client_id: appSettings.clientId, scope: "move_read sleep_read", redirect_uri: "${serverUrl}/oauth/callback" ]
redirect(location: "https://jawbone.com/auth/oauth2/auth?${toQueryString(oauthParams)}")
}
def receiveToken(redirectUrl = null) {
log.debug "receiveToken"
def stcid = getSmartThingsClientId()
def oauthClientSecret = getSmartThingsClientSecret()
def oauthParams = [ client_id: stcid, client_secret: oauthClientSecret, grant_type: "authorization_code", code: params.code ]
def oauthParams = [ client_id: appSettings.clientId, client_secret: appSettings.clientSecret, grant_type: "authorization_code", code: params.code ]
def params = [
uri: "https://jawbone.com/auth/oauth2/token?${toQueryString(oauthParams)}",
]
@@ -231,18 +225,10 @@ String toQueryString(Map m) {
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
}
def getServerUrl() { return appSettings.serverUrl ?: "https://graph.api.smartthings.com" }
def buildRedirectUrl(page) {
// log.debug "buildRedirectUrl"
// /api/token/:st_token/smartapps/installations/:id/something
return "${serverUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/${page}"
}
def validateCurrentToken() {
log.debug "validateCurrentToken"
def url = "https://jawbone.com/nudge/api/v.1.1/users/@me/refreshToken"
def requestBody = "secret=${getSmartThingsClientSecret()}"
def requestBody = "secret=${appSettings.clientSecret}"
try {
httpPost(uri: url, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ], body: requestBody) {response ->
@@ -256,9 +242,7 @@ def validateCurrentToken() {
if (e.statusCode == 401) { // token is expired
log.debug "Access token is expired"
if (state.refreshToken) { // if we have this we are okay
def stcid = getSmartThingsClientId()
def oauthClientSecret = getSmartThingsClientSecret()
def oauthParams = [client_id: stcid, client_secret: oauthClientSecret, grant_type: "refresh_token", refresh_token: state.refreshToken]
def oauthParams = [client_id: appSettings.clientId, client_secret: appSettings.clientSecret, grant_type: "refresh_token", refresh_token: state.refreshToken]
def tokenUrl = "https://jawbone.com/auth/oauth2/token?${toQueryString(oauthParams)}"
def params = [
uri: tokenUrl
@@ -287,9 +271,10 @@ def validateCurrentToken() {
}
def initialize() {
def hookUrl = "${serverUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/hookCallback"
def webhook = "https://jawbone.com/nudge/api/v.1.1/users/@me/pubsub?webhook=$hookUrl"
log.debug "Callback URL: $webhook"
log.debug "Callback URL - Webhook"
def localServerUrl = getApiServerUrl()
def hookUrl = "${localServerUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/hookCallback"
def webhook = "https://jawbone.com/nudge/api/v.1.1/users/@me/pubsub?webhook=$hookUrl"
httpPost(uri: webhook, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ])
}
@@ -327,7 +312,6 @@ def setup() {
}
def installed() {
enableCallback()
if (!state.accessToken) {
log.debug "About to create access token"
@@ -340,7 +324,6 @@ def installed() {
}
def updated() {
enableCallback()
if (!state.accessToken) {
log.debug "About to create access token"
@@ -499,4 +482,4 @@ def hookEventHandler() {
def html = """{"code":200,"message":"OK"}"""
render contentType: 'application/json', data: html
}
}

View File

@@ -1,53 +0,0 @@
/**
*
* Lights On When Door Open After Sundown
*
* Based on "Turn It On When It Opens" by SmartThings
*
* Author: Aaron Crocco
*/
preferences {
section("When the door opens..."){
input "contact1", "capability.contactSensor", title: "Where?"
}
section("Turn on these lights..."){
input "switches", "capability.switch", multiple: true
}
section("and change mode to...") {
input "HomeAfterDarkMode", "mode", title: "Mode?"
}
}
def installed()
{
subscribe(contact1, "contact.open", contactOpenHandler)
}
def updated()
{
unsubscribe()
subscribe(contact1, "contact.open", contactOpenHandler)
}
def contactOpenHandler(evt) {
log.debug "$evt.value: $evt, $settings"
//Check current time to see if it's after sundown.
def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
def now = new Date()
def setTime = s.sunset
log.debug "Sunset is at $setTime. Current time is $now"
if (setTime.before(now)) { //Executes only if it's after sundown.
log.trace "Turning on switches: $switches"
switches.on()
log.trace "Changing house mode to $HomeAfterDarkMode"
setLocationMode(HomeAfterDarkMode)
sendPush("Welcome home! Changing mode to $HomeAfterDarkMode.")
}
}

View File

@@ -26,7 +26,8 @@ definition(
iconUrl: "http://i.imgur.com/HU0ANBp.png",
iconX2Url: "http://i.imgur.com/HU0ANBp.png",
iconX3Url: "http://i.imgur.com/HU0ANBp.png",
oauth: true)
oauth: true,
singleInstance: true)
preferences {

View File

@@ -14,8 +14,8 @@
*
*/
definition(
name: "Smart Lock / Unlock",
namespace: "",
name: "Smart Auto Lock / Unlock",
namespace: "smart-auto-lock-unlock",
author: "Arnaud",
description: "Automatically locks door X minutes after being closed and keeps door unlocked if door is open.",
category: "Safety & Security",

View File

@@ -21,7 +21,8 @@
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
singleInstance: true
)
preferences {

View File

@@ -26,7 +26,8 @@ definition(
description: "Connect your Ecobee thermostat to SmartThings.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png"
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png",
singleInstance: true
) {
appSetting "clientId"
appSetting "serverUrl"

View File

@@ -23,7 +23,8 @@ definition(
description: "Connect and take pictures using your Foscam camera from inside the Smartthings app.",
category: "SmartThings Internal",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam@2x.png"
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam@2x.png",
singleInstance: true
)
preferences {

View File

@@ -23,7 +23,8 @@ definition(
description: "Allows you to connect your Philips Hue lights with SmartThings and control them from your Things area or Dashboard in the SmartThings Mobile app. Adjust colors by going to the Thing detail screen for your Hue lights (tap the gear on Hue tiles).\n\nPlease update your Hue Bridge first, outside of the SmartThings app, using the Philips Hue app.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png"
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
singleInstance: true
)
preferences {
@@ -143,7 +144,7 @@ def bulbDiscovery() {
if (numFound == 0)
app.updateSetting("selectedBulbs", "")
if((bulbRefreshCount % 3) == 0) {
if((bulbRefreshCount % 5) == 0) {
discoverHueBulbs()
}
@@ -318,11 +319,15 @@ def addBulbs() {
def newHueBulb
if (bulbs instanceof java.util.Map) {
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light")) {
d = addChildDevice("smartthings", "Hue Lux Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
} else {
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
}
if (newHueBulb != null) {
if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") ) {
d = addChildDevice("smartthings", "Hue Lux Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
} else {
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
}
} else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
}
} else {
//backwards compatable
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
@@ -604,23 +609,22 @@ def parse(childDevice, description) {
}
}
def on(childDevice, transition_deprecated = 0) {
def on(childDevice) {
log.debug "Executing 'on'"
def percent = childDevice.device?.currentValue("level") as Integer
def level = Math.min(Math.round(percent * 255 / 100), 255)
put("lights/${getId(childDevice)}/state", [bri: level, on: true])
return "level: $percent"
put("lights/${getId(childDevice)}/state", [on: true])
return "Bulb is On"
}
def off(childDevice, transition_deprecated = 0) {
def off(childDevice) {
log.debug "Executing 'off'"
put("lights/${getId(childDevice)}/state", [on: false])
return "level: 0"
return "Bulb is Off"
}
def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'"
def level = Math.min(Math.round(percent * 255 / 100), 255)
def level
if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255)
put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0])
}
@@ -636,7 +640,7 @@ def setHue(childDevice, percent) {
put("lights/${getId(childDevice)}/state", [hue: level])
}
def setColor(childDevice, huesettings, alert_deprecated = "", transition_deprecated = 0) {
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
def sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
@@ -645,7 +649,7 @@ def setColor(childDevice, huesettings, alert_deprecated = "", transition_depreca
def value = [sat: sat, hue: hue, alert: alert, transitiontime: transition]
if (huesettings.level != null) {
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
if (huesettings.level == 1) value.bri = 1 else value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
value.on = value.bri > 0
}
@@ -723,8 +727,6 @@ private getBridgeIP() {
def serialNumber = selectedHue
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
if (!bridge) {
//failed because mac address sent from hub is wrong and doesn't match the hue's real mac address and serial number
//in this case we will look up the bridge by comparing the incorrect mac addresses
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
}
if (bridge?.ip && bridge?.port) {

View File

@@ -98,6 +98,15 @@ def installed() {
}
def updated() {
def currentDeviceIds = settings.collect { k, devices -> devices }.flatten().collect { it.id }.unique()
def subscriptionDevicesToRemove = app.subscriptions*.device.findAll { device ->
!currentDeviceIds.contains(device.id)
}
subscriptionDevicesToRemove.each { device ->
log.debug "Removing $device.displayName subscription"
state.remove(device.id)
unsubscribe(device)
}
log.debug settings
}

View File

@@ -22,7 +22,8 @@ definition(
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/life360.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/life360@2x.png",
oauth: [displayName: "Life360", displayLink: "Life360"]
oauth: [displayName: "Life360", displayLink: "Life360"],
singleInstance: true
) {
appSetting "clientId"
appSetting "clientSecret"

View File

@@ -13,7 +13,8 @@ definition(
iconUrl: "https://cloud.lifx.com/images/lifx.png",
iconX2Url: "https://cloud.lifx.com/images/lifx.png",
iconX3Url: "https://cloud.lifx.com/images/lifx.png",
oauth: true) {
oauth: true,
singleInstance: true) {
appSetting "clientId"
appSetting "clientSecret"
}

View File

@@ -34,7 +34,7 @@
* locks | lock | lock, unlock | locked, unlocked
* ---------------------+-------------------+-----------------------------+------------------------------------
*/
definition(
name: "Logitech Harmony (Connect)",
namespace: "smartthings",
@@ -43,11 +43,11 @@ definition(
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/harmony.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/harmony%402x.png",
oauth: [displayName: "Logitech Harmony", displayLink: "http://www.logitech.com/en-us/harmony-remotes"]
oauth: [displayName: "Logitech Harmony", displayLink: "http://www.logitech.com/en-us/harmony-remotes"],
singleInstance: true
){
appSetting "clientId"
appSetting "clientSecret"
appSetting "callbackUrl"
}
preferences(oauthPage: "deviceAuthorization") {
@@ -89,16 +89,18 @@ mappings {
}
def getServerUrl() { return "https://graph.api.smartthings.com" }
def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" }
def authPage() {
def description = null
def description = null
if (!state.HarmonyAccessToken) {
if (!state.accessToken) {
log.debug "About to create access token"
createAccessToken()
}
description = "Click to enter Harmony Credentials"
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}"
def redirectUrl = buildRedirectUrl
return dynamicPage(name: "Credentials", title: "Harmony", nextPage: null, uninstall: true, install:false) {
section { href url:redirectUrl, style:"embedded", required:true, title:"Harmony", description:description }
}
@@ -110,7 +112,7 @@ def authPage() {
def huboptions = state.HarmonyHubs ?: []
def actoptions = state.HarmonyActivities ?: []
def numFoundHub = huboptions.size() ?: 0
def numFoundAct = actoptions.size() ?: 0
if((deviceRefreshCount % 5) == 0) {
@@ -120,13 +122,14 @@ def authPage() {
section("Please wait while we discover your Harmony Hubs and Activities. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, options:huboptions
}
if (numFoundHub > 0 && numFoundAct > 0 && false)
// Virtual activity flag
if (numFoundHub > 0 && numFoundAct > 0 && true)
section("You can also add activities as virtual switches for other convenient integrations") {
input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, options:actoptions
}
}
if (state.resethub)
section("Connection to the hub timed out. Please restart the hub and try again.") {}
}
section("Connection to the hub timed out. Please restart the hub and try again.") {}
}
}
}
@@ -138,7 +141,7 @@ def callback() {
} else {
log.warn "No authQueryString"
}
if (state.HarmonyAccessToken) {
log.debug "Access token already exists"
discovery()
@@ -163,8 +166,8 @@ def callback() {
def init() {
log.debug "Requesting Code"
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${appSettings.callbackUrl}" ]
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${callbackUrl}" ]
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
}
def receiveToken(redirectUrl = null) {
@@ -174,7 +177,7 @@ def receiveToken(redirectUrl = null) {
uri: "https://home.myharmony.com/oauth2/token?${toQueryString(oauthParams)}",
]
try {
httpPost(params) { response ->
httpPost(params) { response ->
state.HarmonyAccessToken = response.data.access_token
}
} catch (java.util.concurrent.TimeoutException e) {
@@ -221,7 +224,7 @@ def connectionStatus(message, redirectUrl = null) {
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
}
def html = """
<!DOCTYPE html>
<html>
@@ -302,30 +305,28 @@ def buildRedirectUrl(page) {
}
def installed() {
enableCallback()
if (!state.accessToken) {
log.debug "About to create access token"
createAccessToken()
} else {
} else {
initialize()
}
}
def updated() {
unsubscribe()
unschedule()
enableCallback()
unschedule()
if (!state.accessToken) {
log.debug "About to create access token"
createAccessToken()
} else {
} else {
initialize()
}
}
}
def uninstalled() {
if (state.HarmonyAccessToken) {
try {
try {
state.HarmonyAccessToken = ""
log.debug "Success disconnecting Harmony from SmartThings"
} catch (groovyx.net.http.HttpResponseException e) {
@@ -339,7 +340,7 @@ def initialize() {
if (selectedhubs || selectedactivities) {
addDevice()
runEvery5Minutes("discovery")
}
}
}
def getHarmonydevices() {
@@ -359,20 +360,20 @@ Map discoverDevices() {
def hubname = getHubName(it.key)
def hubvalue = "${hubname}"
hubs["harmony-${hubkey}"] = hubvalue
it.value.response.data.activities.each {
it.value.response.data.activities.each {
def value = "${it.value.name}"
def key = "harmony-${hubkey}-${it.key}"
activities["${key}"] = value
}
}
}
state.HarmonyHubs = hubs
state.HarmonyActivities = activities
}
state.HarmonyActivities = activities
}
}
//CHILD DEVICE METHODS
def discovery() {
def Params = [auth: state.HarmonyAccessToken]
def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
@@ -384,11 +385,11 @@ def discovery() {
poll()
} else {
log.debug "Error: $response.status"
}
}
}
} catch (groovyx.net.http.HttpResponseException e) {
if (e.statusCode == 401) { // token is expired
state.remove("HarmonyAccessToken")
state.remove("HarmonyAccessToken")
log.warn "Harmony Access token has expired"
}
} catch (java.net.SocketTimeoutException e) {
@@ -396,12 +397,12 @@ def discovery() {
state.resethub = true
} catch (e) {
log.warn "Hostname in certificate didn't match. Please try again later."
}
}
return null
}
def addDevice() {
log.trace "Adding Hubs"
log.trace "Adding Hubs"
selectedhubs.each { dni ->
def d = getChildDevice(dni)
if(!d) {
@@ -412,8 +413,8 @@ def addDevice() {
} else {
log.trace "found ${d.displayName} with id $dni already exists"
}
}
log.trace "Adding Activities"
}
log.trace "Adding Activities"
selectedactivities.each { dni ->
def d = getChildDevice(dni)
if(!d) {
@@ -424,7 +425,7 @@ def addDevice() {
} else {
log.trace "found ${d.displayName} with id $dni already exists"
}
}
}
}
def activity(dni,mode) {
@@ -432,26 +433,26 @@ def activity(dni,mode) {
def msg = "Command failed"
def url = ''
if (dni == "all") {
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(Params)}"
} else {
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(Params)}"
} else {
def aux = dni.split('-')
def hubId = aux[1]
if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(Params)}"
if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(Params)}"
} else {
def activityId = aux[2]
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(Params)}"
}
}
}
try {
httpPostJson(uri: url) { response ->
httpPostJson(uri: url) { response ->
if (response.data.code == 200 || dni == "all") {
msg = "Command sent succesfully"
state.aux = 0
state.aux = 0
} else {
msg = "Command failed. Error: $response.data.code"
}
}
}
} catch (groovyx.net.http.HttpResponseException ex) {
log.error ex
if (state.aux == 0) {
@@ -459,7 +460,7 @@ def activity(dni,mode) {
activity(dni,mode)
} else {
msg = ex
state.aux = 0
state.aux = 0
}
} catch(Exception ex) {
msg = ex
@@ -472,10 +473,10 @@ def poll() {
// GET THE LIST OF ACTIVITIES
if (state.HarmonyAccessToken) {
getActivityList()
def Params = [auth: state.HarmonyAccessToken]
def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
def map = [:]
response.data.hubs.each {
if (it.value.message == "OK") {
@@ -488,20 +489,20 @@ def poll() {
def currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
}
}
}
} else {
log.trace it.value.message
}
}
}
def activities = getChildDevices()
def activities = getChildDevices()
def activitynotrunning = true
activities.each { activity ->
def act = activity.deviceNetworkId.split('-')
def act = activity.deviceNetworkId.split('-')
if (act.size() > 2) {
def aux = map.find { it.key == act[1] }
if (aux) {
def aux2 = aux.value.split(',')
def childDevice = getChildDevice(activity.deviceNetworkId)
def childDevice = getChildDevice(activity.deviceNetworkId)
if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
childDevice?.sendEvent(name: "switch", value: "on")
if (aux2[1] == "1")
@@ -511,30 +512,30 @@ def poll() {
if (aux2[1] == "3")
runIn(5, "poll", [overwrite: true])
}
}
}
}
}
}
return "Poll completed $map - $state.hubs"
}
} catch (groovyx.net.http.HttpResponseException e) {
if (e.statusCode == 401) { // token is expired
state.remove("HarmonyAccessToken")
state.remove("HarmonyAccessToken")
return "Harmony Access token has expired"
}
}
} catch(Exception e) {
log.trace e
}
}
}
}
def getActivityList() {
// GET ACTIVITY'S NAME
if (state.HarmonyAccessToken) {
def Params = [auth: state.HarmonyAccessToken]
def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
response.data.hubs.each {
def hub = getChildDevice("harmony-${it.key}")
if (hub) {
@@ -547,10 +548,10 @@ def getActivityList() {
}
activities += [id: "off", name: "Activity OFF", type: "0"]
log.trace activities
}
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
}
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
}
}
}
}
} catch (groovyx.net.http.HttpResponseException e) {
log.trace e
@@ -559,7 +560,7 @@ def getActivityList() {
} catch(Exception e) {
log.trace e
}
}
}
return activity
}
@@ -567,16 +568,16 @@ def getActivityName(activity,hubId) {
// GET ACTIVITY'S NAME
def actname = activity
if (state.HarmonyAccessToken) {
def Params = [auth: state.HarmonyAccessToken]
def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/all?${toQueryString(Params)}"
try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
actname = response.data.data.activities[activity].name
}
} catch(Exception e) {
log.trace e
}
}
}
return actname
}
@@ -584,19 +585,19 @@ def getActivityId(activity,hubId) {
// GET ACTIVITY'S NAME
def actid = activity
if (state.HarmonyAccessToken) {
def Params = [auth: state.HarmonyAccessToken]
def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/all?${toQueryString(Params)}"
try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
response.data.data.activities.each {
if (it.value.name == activity)
actid = it.key
}
}
}
} catch(Exception e) {
log.trace e
}
}
}
return actid
}
@@ -604,16 +605,16 @@ def getHubName(hubId) {
// GET HUB'S NAME
def hubname = hubId
if (state.HarmonyAccessToken) {
def Params = [auth: state.HarmonyAccessToken]
def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/hub/${hubId}/discover?${toQueryString(Params)}"
try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
hubname = response.data.data.name
}
} catch(Exception e) {
log.trace e
}
}
}
}
return hubname
}
@@ -624,8 +625,8 @@ def sendNotification(msg) {
def hookEventHandler() {
// log.debug "In hookEventHandler method."
log.debug "request = ${request}"
def json = request.JSON
def json = request.JSON
def html = """{"code":200,"message":"OK"}"""
render contentType: 'application/json', data: html
@@ -659,12 +660,16 @@ def updateDevice() {
} else {
def device = allDevices.find { it.id == params.id }
if (device) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
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"}'
}
render status: 204, data: "{}"
} else {
render status: 404, data: '{"msg": "Device not found"}'
}

View File

@@ -22,7 +22,8 @@ definition(
description: "Allows you to control your Samsung TV from the SmartThings app. Perform basic functions like power Off, source, volume, channels and other remote control functions.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%402x.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%403x.png"
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%403x.png",
singleInstance: true
)
preferences {

View File

@@ -16,8 +16,8 @@
*
*/
definition(
name: "Send HAM Bridge Command When",
namespace: "soletc.com",
name: "Send HAM Bridge Command When",
namespace: "smartthings",
author: "Scottin Pollock",
description: "Sends a command to your HAM Bridge server when SmartThings are activated.",
category: "Convenience",
@@ -25,7 +25,6 @@ definition(
iconX2Url: "http://solutionsetcetera.com/stuff/STIcons/HB@2x.png"
)
preferences {
section("Choose one or more, when..."){
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true

View File

@@ -23,7 +23,8 @@ definition(
description: "Integrate your Tesla car with SmartThings.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/tesla-app%402x.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tesla-app%403x.png"
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tesla-app%403x.png",
singleInstance: true
)
preferences {

View File

@@ -22,7 +22,8 @@ definition(
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png"
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
singleInstance: true
)
preferences {
@@ -61,10 +62,7 @@ def firstPage()
log.debug "REFRESH COUNT :: ${refreshCount}"
if(!state.subscribe) {
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
subscribe(location, null, locationHandler, [filterEvents:false])
//ssdp request every 25 seconds
if((refreshCount % 5) == 0) {
@@ -168,21 +166,30 @@ def getWemoLightSwitches()
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
runIn(900, "doDeviceSync" , [overwrite: false]) //setup ip:port syncing every 15 minutes
// SUBSCRIBE responses come back with TIMEOUT-1801 (30 minutes), so we refresh things a bit before they expire (29 minutes)
runIn(1740, "refresh", [overwrite: false])
}
def updated() {
log.debug "Updated with settings: ${settings}"
initialize()
}
runIn(5, "subscribeToDevices") //subscribe again to new/old devices wait 5 seconds
runIn(10, "refreshDevices") //refresh devices again, delayed by 10 seconds
def initialize() {
unsubscribe()
unschedule()
subscribe(location, null, locationHandler, [filterEvents:false])
if (selectedSwitches)
addSwitches()
if (selectedMotions)
addMotions()
if (selectedLightSwitches)
addLightSwitches()
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
runEvery5Minutes("refresh")
}
def resubscribe() {
@@ -192,8 +199,7 @@ def resubscribe() {
def refresh() {
log.debug "refresh() called"
//reschedule the refreshes
runIn(1740, "refresh", [overwrite: false])
doDeviceSync()
refreshDevices()
}
@@ -236,7 +242,8 @@ def addSwitches() {
"port": selectedSwitch.value.port
]
])
def ipvalue = convertHexToIP(selectedSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
@@ -266,8 +273,9 @@ def addMotions() {
"port": selectedMotion.value.port
]
])
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
def ipvalue = convertHexToIP(selectedMotion.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
}
@@ -296,7 +304,8 @@ def addLightSwitches() {
"port": selectedLightSwitch.value.port
]
])
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "created ${d.displayName} with id $dni"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
@@ -304,27 +313,6 @@ def addLightSwitches() {
}
}
def initialize() {
// remove location subscription afterwards
unsubscribe()
state.subscribe = false
if (selectedSwitches)
{
addSwitches()
}
if (selectedMotions)
{
addMotions()
}
if (selectedLightSwitches)
{
addLightSwitches()
}
}
def locationHandler(evt) {
def description = evt.description
def hub = evt?.hubId
@@ -333,53 +321,32 @@ def locationHandler(evt) {
log.debug parsedEvent
if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
def switches = getWemoSwitches()
if (!(switches."${parsedEvent.ssdpUSN.toString()}"))
{ //if it doesn't already exist
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
}
else
{ // just update the values
} else {
log.debug "Device was already found in state..."
def d = switches."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
log.debug "$d.ip <==> $parsedEvent.ip"
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
}
if (deviceChangedValues) {
def children = getChildDevices()
log.debug "Found children ${children}"
children.each {
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
it.subscribe(parsedEvent.ip, parsedEvent.port)
}
}
}
}
}
else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
def motions = getWemoMotions()
if (!(motions."${parsedEvent.ssdpUSN.toString()}"))
{ //if it doesn't already exist
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
}
else
{ // just update the values
} else { // just update the values
log.debug "Device was already found in state..."
def d = motions."${parsedEvent.ssdpUSN.toString()}"
@@ -412,10 +379,7 @@ def locationHandler(evt) {
if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}"))
{ //if it doesn't already exist
lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
}
else
{ // just update the values
} else {
log.debug "Device was already found in state..."
def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}"
@@ -426,21 +390,11 @@ def locationHandler(evt) {
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
child.subscribe(parsedEvent.ip, parsedEvent.port)
}
if (deviceChangedValues) {
def children = getChildDevices()
log.debug "Found children ${children}"
children.each {
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
it.subscribe(parsedEvent.ip, parsedEvent.port)
}
}
}
}
}
else if (parsedEvent.headers && parsedEvent.body) {
String headerString = new String(parsedEvent.headers.decodeBase64())?.toLowerCase()
@@ -580,73 +534,30 @@ private def parseDiscoveryMessage(String description) {
}
}
}
device
}
def doDeviceSync(){
log.debug "Doing Device Sync!"
runIn(900, "doDeviceSync" , [overwrite: false]) //schedule to run again in 15 minutes
if(!state.subscribe) {
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
discoverAllWemoTypes()
}
def pollChildren() {
def devices = getAllChildDevices()
devices.each { d ->
//only poll switches?
d.poll()
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
def delayPoll() {
log.debug "Executing 'delayPoll'"
runIn(5, "pollChildren")
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
/*def poll() {
log.debug "Executing 'poll'"
runIn(600, "poll", [overwrite: false]) //schedule to run again in 10 minutes
def lastPoll = getLastPollTime()
def currentTime = now()
def lastPollDiff = currentTime - lastPoll
log.debug "lastPoll: $lastPoll, currentTime: $currentTime, lastPollDiff: $lastPollDiff"
setLastPollTime(currentTime)
doDeviceSync()
}
def setLastPollTime(currentTime) {
state.lastpoll = currentTime
}
def getLastPollTime() {
state.lastpoll ?: now()
}
def now() {
new Date().getTime()
}*/
private Boolean canInstallLabs()
{
private Boolean canInstallLabs() {
return hasAllHubsOver("000.011.00603")
}
private Boolean hasAllHubsOver(String desiredFirmware)
{
private Boolean hasAllHubsOver(String desiredFirmware) {
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
private List getRealHubFirmwareVersions()
{
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}

View File

@@ -24,7 +24,8 @@ definition(
category: "Connections",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/withings.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/withings%402x.png",
oauth: true
oauth: true,
singleInstance: true
) {
appSetting "clientId"
appSetting "clientSecret"

View File

@@ -24,7 +24,8 @@ definition(
category: "SmartThings Internal",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png",
oauth: true
oauth: true,
singleInstance: true
) {
appSetting "serverUrl"
}

View File

@@ -15,7 +15,7 @@
*/
definition(
name: "Sprayer Controller 2",
namespace: "",
namespace: "sprayercontroller",
author: "Cooper Lee",
description: "Control Sprayers for a period of time a number of times per hour",
category: "My Apps",

View File

@@ -50,7 +50,7 @@ preferences {
}
section("Send Notifications?") {
input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Send an SMS to this number?"
input "phone", "phone", title: "Send an SMS to this number?", required:false
}
}
@@ -266,7 +266,9 @@ def sendAway(msg) {
}
else {
sendPush(msg)
sendSms(phone, msg)
if(phone){
sendSms(phone, msg)
}
}
}
@@ -280,7 +282,9 @@ def sendHome(msg) {
}
else {
sendPush(msg)
sendSms(phone, msg)
if(phone){
sendSms(phone, msg)
}
}
}
@@ -339,4 +343,4 @@ private getTimeIntervalLabel() {
private hideOptionsSection() {
(starting || ending || days || modes) ? false: true
}
}

View File

@@ -58,7 +58,8 @@ definition(
description: "Connect your Quirky to SmartThings.",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/quirky.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/quirky@2x.png"
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/quirky@2x.png",
singleInstance: true
) {
appSetting "clientId"
appSetting "clientSecret"

View File

@@ -25,7 +25,8 @@ definition(
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"
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp@2x.png",
singleInstance: true
)