Compare commits

..

2 Commits

25 changed files with 696 additions and 311 deletions

View File

@@ -14,6 +14,7 @@
* *
*/ */
//@Deprecated: Moved to ZLL Dimmer Bulb
metadata { metadata {
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") { definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
@@ -24,7 +25,7 @@ metadata {
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019" //fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
} }
// simulator metadata // simulator metadata

View File

@@ -138,7 +138,6 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
log.debug "productTypeId: ${cmd.productTypeId}" log.debug "productTypeId: ${cmd.productTypeId}"
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId) def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
updateDataValue("MSR", msr) updateDataValue("MSR", msr)
updateDataValue("manufacturer", cmd.manufacturerName)
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false]) createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
} }

View File

@@ -19,6 +19,11 @@ metadata {
capability "Sensor" capability "Sensor"
attribute "colorName", "string" attribute "colorName", "string"
// indicates that device keeps track of heartbeat (in state.heartbeat)
attribute "heartbeat", "string"
} }
// simulator metadata // simulator metadata
@@ -70,6 +75,9 @@ metadata {
def parse(String description) { def parse(String description) {
//log.trace description //log.trace description
// save heartbeat (i.e. last time we got a message from device)
state.heartbeat = Calendar.getInstance().getTimeInMillis()
if (description?.startsWith("catchall:")) { if (description?.startsWith("catchall:")) {
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1")) if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
{ {
@@ -124,6 +132,7 @@ def off() {
} }
def refresh() { def refresh() {
sendEvent(name: "heartbeat", value: "alive", displayed:false)
[ [
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500", "st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500", "st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",

View File

@@ -16,7 +16,7 @@
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings", category: "C1") { definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Switch" capability "Switch"
capability "Power Meter" capability "Power Meter"
@@ -25,6 +25,9 @@ metadata {
capability "Sensor" capability "Sensor"
capability "Health Check" capability "Health Check"
// indicates that device keeps track of heartbeat (in state.heartbeat)
attribute "heartbeat", "string"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet"
@@ -78,6 +81,9 @@ metadata {
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
// save heartbeat (i.e. last time we got a message from device)
state.heartbeat = Calendar.getInstance().getTimeInMillis()
def finalResult = zigbee.getKnownDescription(description) def finalResult = zigbee.getKnownDescription(description)
//TODO: Remove this after getKnownDescription can parse it automatically //TODO: Remove this after getKnownDescription can parse it automatically
@@ -118,6 +124,7 @@ def on() {
} }
def refresh() { def refresh() {
sendEvent(name: "heartbeat", value: "alive", displayed:false)
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B") zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
} }

View File

@@ -15,7 +15,7 @@
*/ */
metadata { metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
capability "Refresh" capability "Refresh"

View File

@@ -15,7 +15,7 @@
*/ */
metadata { metadata {
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor" capability "Motion Sensor"
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"

View File

@@ -15,7 +15,7 @@
*/ */
metadata { metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Three Axis" capability "Three Axis"
capability "Battery" capability "Battery"

View File

@@ -16,7 +16,7 @@
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH //DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
metadata { metadata {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Battery" capability "Battery"
capability "Configuration" capability "Configuration"
capability "Contact Sensor" capability "Contact Sensor"

View File

@@ -14,7 +14,7 @@
* *
*/ */
metadata { metadata {
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") { definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings") {
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
capability "Refresh" capability "Refresh"

View File

@@ -22,7 +22,7 @@ metadata {
tiles(scale: 2) { tiles(scale: 2) {
valueTile("currentColor", "device.color") { valueTile("currentColor", "device.color") {
state "color", label: '${currentValue}', defaultState: true state "default", label: '${currentValue}'
} }
controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) { controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) {
@@ -41,13 +41,6 @@ def parse(String description) {
log.debug "Parsing '${description}'" log.debug "Parsing '${description}'"
} }
def setColor(value) {
log.debug "setting color: $value"
if (value.hex) { sendEvent(name: "color", value: value.hex) }
if (value.hue) { sendEvent(name: "hue", value: value.hue) }
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation) }
}
def setSaturation(percent) { def setSaturation(percent) {
log.debug "Executing 'setSaturation'" log.debug "Executing 'setSaturation'"
sendEvent(name: "saturation", value: percent) sendEvent(name: "saturation", value: percent)

View File

@@ -39,7 +39,7 @@ metadata {
} }
valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) { valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) {
state "range", label:'${currentValue}', defaultState: true state "default", label:'${currentValue}'
} }
controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") { controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") {

View File

@@ -41,17 +41,17 @@ metadata {
// standard flat tile with only a label // standard flat tile with only a label
standardTile("flatLabel", "device.switch", width: 2, height: 2, decoration: "flat") { standardTile("flatLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
state "label", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff", defaultState: true state "default", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff"
} }
// standard flat tile with icon and label // standard flat tile with icon and label
standardTile("flatIconLabel", "device.switch", width: 2, height: 2, decoration: "flat") { standardTile("flatIconLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
state "iconLabel", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff", defaultState: true state "default", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff"
} }
// standard flat tile with only icon (Refreh text is IN the icon file) // standard flat tile with only icon (Refreh text is IN the icon file)
standardTile("flatIcon", "device.switch", width: 2, height: 2, decoration: "flat") { standardTile("flatIcon", "device.switch", width: 2, height: 2, decoration: "flat") {
state "icon", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
// standard with defaultState = true // standard with defaultState = true
@@ -74,19 +74,19 @@ metadata {
// utility tiles to fill the spaces // utility tiles to fill the spaces
standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") { standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") {
state "emptySmall", label:'', defaultState: true state "default", label:''
} }
standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") { standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") {
state "emptyBigger", label:'', defaultState: true state "default", label:''
} }
// multi-line text (explicit newlines) // multi-line text (explicit newlines)
standardTile("multiLine", "device.multiLine", width: 2, height: 2) { standardTile("multiLine", "device.multiLine", width: 2, height: 2) {
state "multiLine", label: '${currentValue}', defaultState: true state "default", label: '${currentValue}'
} }
standardTile("multiLineWithIcon", "device.multiLine", width: 2, height: 2) { standardTile("multiLineWithIcon", "device.multiLine", width: 2, height: 2) {
state "multiLineIcon", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true state "default", label: '${currentValue}', icon: "st.switches.switch.off"
} }
main("actionRings") main("actionRings")

View File

@@ -22,68 +22,68 @@ metadata {
tiles(scale: 2) { tiles(scale: 2) {
valueTile("text", "device.text", width: 2, height: 2) { valueTile("text", "device.text", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true state "default", label:'${currentValue}'
} }
valueTile("longText", "device.longText", width: 2, height: 2) { valueTile("longText", "device.longText", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true state "default", label:'${currentValue}'
} }
valueTile("integer", "device.integer", width: 2, height: 2) { valueTile("integer", "device.integer", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true state "default", label:'${currentValue}'
} }
valueTile("integerFloat", "device.integerFloat", width: 2, height: 2) { valueTile("integerFloat", "device.integerFloat", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true state "default", label:'${currentValue}'
} }
valueTile("pi", "device.pi", width: 2, height: 2) { valueTile("pi", "device.pi", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true state "default", label:'${currentValue}'
} }
valueTile("floatAsText", "device.floatAsText", width: 2, height: 2) { valueTile("floatAsText", "device.floatAsText", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true state "default", label:'${currentValue}'
} }
valueTile("bgColor", "device.integer", width: 2, height: 2) { valueTile("bgColor", "device.integer", width: 2, height: 2) {
state "val", label:'${currentValue}', backgroundColor: "#e86d13", defaultState: true state "default", label:'${currentValue}', backgroundColor: "#e86d13"
} }
valueTile("bgColorRange", "device.integer", width: 2, height: 2) { valueTile("bgColorRange", "device.integer", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [ state "default", label:'${currentValue}', backgroundColors: [
[value: 10, color: "#ff0000"], [value: 10, color: "#ff0000"],
[value: 90, color: "#0000ff"] [value: 90, color: "#0000ff"]
] ]
} }
valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) { valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) {
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [ state "default", label:'${currentValue}', backgroundColors: [
[value: 10, color: "#333333"] [value: 10, color: "#333333"]
] ]
} }
valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) { valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) {
state "valWithConflict", label:'${currentValue}', defaultState: true, backgroundColors: [ state "default", label:'${currentValue}', backgroundColors: [
[value: 10, color: "#990000"], [value: 10, color: "#990000"],
[value: 10, color: "#000099"] [value: 10, color: "#000099"]
] ]
} }
valueTile("noValue", "device.nada", width: 4, height: 2) { valueTile("noValue", "device.nada", width: 4, height: 2) {
state "noval", label:'${currentValue}', defaultState: true state "default", label:'${currentValue}'
} }
valueTile("multiLine", "device.multiLine", width: 3, height: 2) { valueTile("multiLine", "device.multiLine", width: 3, height: 2) {
state "val", label: '${currentValue}', defaultState: true state "default", label: '${currentValue}'
} }
valueTile("multiLineWithIcon", "device.multiLine", width: 3, height: 2) { valueTile("multiLineWithIcon", "device.multiLine", width: 3, height: 2) {
state "val", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true state "default", label: '${currentValue}', icon: "st.switches.switch.off"
} }
main("text") main("text")
details([ details([
"text", "longText", "integer", "text", "longText", "integer",
"integerFloat", "pi", "floatAsText", "integerFloat", "pi", "floatAsText",
"bgColor", "bgColorRange", "bgColorRangeSingleItem", "bgColor", "bgColorRange", "bgColorRangeSingleItem",
"bgColorRangeConflict", "noValue", "bgColorRangeConflict", "noValue",

View File

@@ -39,15 +39,15 @@ metadata {
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
} }
tileAttribute("device.level", key: "SECONDARY_CONTROL") { tileAttribute("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", icon: 'st.Weather.weather1', action:"randomizeLevel", defaultState: true attributeState "default", icon: 'st.Weather.weather1', action:"randomizeLevel"
} }
tileAttribute("device.level", key: "SLIDER_CONTROL") { tileAttribute("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", defaultState: true attributeState "default", action:"switch level.setLevel"
} }
} }
multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) { multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) {
tileAttribute("device.level", key: "PRIMARY_CONTROL") { tileAttribute("device.level", key: "PRIMARY_CONTROL") {
attributeState "level", label:'${currentValue}', defaultState: true, backgroundColors:[ attributeState "default", label:'${currentValue}', backgroundColors:[
[value: 0, color: "#ff0000"], [value: 0, color: "#ff0000"],
[value: 20, color: "#ffff00"], [value: 20, color: "#ffff00"],
[value: 40, color: "#00ff00"], [value: 40, color: "#00ff00"],
@@ -69,34 +69,34 @@ metadata {
} }
multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) { multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) {
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") { tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821"
} }
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") { tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821"
} }
} }
multiAttributeTile(name:"multilineTile", type:"generic", width:6, height:4) { multiAttributeTile(name:"multilineTile", type:"generic", width:6, height:4) {
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") { tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821"
} }
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") { tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821"
} }
} }
multiAttributeTile(name:"lengthyTileWithIcon", type:"generic", width:6, height:4) { multiAttributeTile(name:"lengthyTileWithIcon", type:"generic", width:6, height:4) {
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") { tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on"
} }
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") { tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on"
} }
} }
multiAttributeTile(name:"multilineTileWithIcon", type:"generic", width:6, height:4) { multiAttributeTile(name:"multilineTileWithIcon", type:"generic", width:6, height:4) {
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") { tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on"
} }
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") { tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on"
} }
} }

View File

@@ -96,10 +96,10 @@ metadata {
} }
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "reset", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single", defaultState: true state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
} }
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "refresh", label:"", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main(["switch"]) main(["switch"])
@@ -173,6 +173,7 @@ def setColor(value) {
def reset() { def reset() {
log.debug "Executing 'reset'" log.debug "Executing 'reset'"
setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23]) setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23])
//parent.poll()
} }
def setAdjustedColor(value) { def setAdjustedColor(value) {
@@ -188,6 +189,7 @@ def setAdjustedColor(value) {
def refresh() { def refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
//parent.manualRefresh()
} }
def adjustOutgoingHue(percent) { def adjustOutgoingHue(percent) {
@@ -206,3 +208,4 @@ def adjustOutgoingHue(percent) {
log.info "percent: $percent, adjusted: $adjusted" log.info "percent: $percent, adjusted: $adjusted"
adjusted adjusted
} }

View File

@@ -37,10 +37,10 @@ metadata {
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing") attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
} }
tileAttribute("device.status", key: "PREVIOUS_TRACK") { tileAttribute("device.status", key: "PREVIOUS_TRACK") {
attributeState("status", action:"music Player.previousTrack", defaultState: true) attributeState("default", action:"music Player.previousTrack")
} }
tileAttribute("device.status", key: "NEXT_TRACK") { tileAttribute("device.status", key: "NEXT_TRACK") {
attributeState("status", action:"music Player.nextTrack", defaultState: true) attributeState("default", action:"music Player.nextTrack")
} }
tileAttribute ("device.level", key: "SLIDER_CONTROL") { tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState("level", action:"music Player.setLevel") attributeState("level", action:"music Player.setLevel")
@@ -50,7 +50,7 @@ metadata {
attributeState("muted", action:"music Player.unmute", nextState: "unmuted") attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
} }
tileAttribute("device.trackDescription", key: "MARQUEE") { tileAttribute("device.trackDescription", key: "MARQUEE") {
attributeState("trackDescription", label:"${currentValue}", defaultState: true) attributeState("default", label:"${currentValue}")
} }
} }

View File

@@ -32,14 +32,14 @@ metadata {
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) { multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true) attributeState("default", label:'${currentValue}', unit:"dF")
} }
tileAttribute("device.temperature", key: "VALUE_CONTROL") { tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "tempUp") attributeState("VALUE_UP", action: "tempUp")
attributeState("VALUE_DOWN", action: "tempDown") attributeState("VALUE_DOWN", action: "tempDown")
} }
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") { tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
attributeState("humidity", label:'${currentValue}%', unit:"%", defaultState: true) attributeState("default", label:'${currentValue}%', unit:"%")
} }
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621") attributeState("idle", backgroundColor:"#44b621")
@@ -53,16 +53,15 @@ metadata {
attributeState("auto", label:'${name}') attributeState("auto", label:'${name}')
} }
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) attributeState("default", label:'${currentValue}', unit:"dF")
} }
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") { tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) attributeState("default", label:'${currentValue}', unit:"dF")
} }
} }
multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) { multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) attributeState("default", label:'${currentValue}', unit:"dF")
attributeState("temp", label:'${currentValue}', unit:"dF")
} }
tileAttribute("device.temperature", key: "VALUE_CONTROL") { tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "tempUp") attributeState("VALUE_UP", action: "tempUp")
@@ -80,16 +79,15 @@ metadata {
attributeState("auto", label:'${name}') attributeState("auto", label:'${name}')
} }
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") { tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) attributeState("default", label:'${currentValue}', unit:"dF")
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF")
} }
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") { tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true) attributeState("default", label:'${currentValue}', unit:"dF")
} }
} }
multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) { multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true, attributeState("default", label:'${currentValue}', unit:"dF",
backgroundColors:[ backgroundColors:[
[value: 31, color: "#153591"], [value: 31, color: "#153591"],
[value: 44, color: "#1e9cbb"], [value: 44, color: "#1e9cbb"],
@@ -120,30 +118,30 @@ metadata {
) )
} }
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "tempDown", label:'down', action:"tempDown", defaultState: true state "default", label:'down', action:"tempDown"
} }
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "tempUp", label:'up', action:"tempUp", defaultState: true state "default", label:'up', action:"tempUp"
} }
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff" state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
} }
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "heatDown", label:'down', action:"heatDown", defaultState: true state "default", label:'down', action:"heatDown"
} }
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "heatUp", label:'up', action:"heatUp", defaultState: true state "default", label:'up', action:"heatUp"
} }
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff" state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
} }
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "coolDown", label:'down', action:"coolDown", defaultState: true state "default", label:'down', action:"coolDown"
} }
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "coolUp", label:'up', action:"coolUp", defaultState: true state "default", label:'up', action:"coolUp"
} }
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {

View File

@@ -22,9 +22,8 @@ metadata {
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
//fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019" fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019" fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019", manufacturer: "CREE", model: "Connected A-19 60W Equivalent", deviceJoinName: "Cree Connected Bulb"
//fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019", manufacturer: "CREE", model: "Connected A-19 60W Equivalent", deviceJoinName: "Cree Connected Bulb"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 W clear", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light" fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 W clear", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 W clear - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light" fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 W clear - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019", manufacturer: "Philips", model: "LWB006", deviceJoinName: "Philips Hue White" fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019", manufacturer: "Philips", model: "LWB006", deviceJoinName: "Philips Hue White"

View File

@@ -95,17 +95,11 @@ def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
} }
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) { def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
log.debug "manufacturerId: ${cmd.manufacturerId}" if (state.manufacturer != cmd.manufacturerName) {
log.debug "manufacturerName: ${cmd.manufacturerName}" updateDataValue("manufacturer", 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)
updateDataValue("manufacturer", cmd.manufacturerName)
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
} }
def zwaveEvent(physicalgraph.zwave.Command cmd) { def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in // Handles all Z-Wave commands we aren't interested in
[:] [:]

View File

@@ -4,33 +4,29 @@
import java.text.DecimalFormat import java.text.DecimalFormat
import groovy.json.JsonSlurper import groovy.json.JsonSlurper
private getApiUrl() { "https://api.netatmo.com" } private apiUrl() { "https://api.netatmo.com" }
private getVendorName() { "netatmo" } private getVendorName() { "netatmo" }
private getVendorAuthPath() { "${apiUrl}/oauth2/authorize?" } private getVendorAuthPath() { "https://api.netatmo.com/oauth2/authorize?" }
private getVendorTokenPath(){ "${apiUrl}/oauth2/token" } private getVendorTokenPath(){ "https://api.netatmo.com/oauth2/token" }
private getVendorIcon() { "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png" } private getVendorIcon() { "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png" }
private getClientId() { appSettings.clientId } private getClientId() { appSettings.clientId }
private getClientSecret() { appSettings.clientSecret } private getClientSecret() { appSettings.clientSecret }
private getServerUrl() { appSettings.serverUrl } private getServerUrl() { "https://graph.api.smartthings.com" }
private getShardUrl() { return getApiServerUrl() }
private getCallbackUrl() { "${serverUrl}/oauth/callback" }
private getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${shardUrl}" }
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition( definition(
name: "Netatmo (Connect)", name: "Netatmo (Connect)",
namespace: "dianoga", namespace: "dianoga",
author: "Brian Steere", author: "Brian Steere",
description: "Netatmo Integration", description: "Netatmo Integration",
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png", 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", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
oauth: true, oauth: true,
singleInstance: true singleInstance: true
){ ){
appSetting "clientId" appSetting "clientId"
appSetting "clientSecret" appSetting "clientSecret"
appSetting "serverUrl"
} }
preferences { preferences {
@@ -39,52 +35,35 @@ preferences {
} }
mappings { mappings {
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]} path("/receivedToken"){action: [POST: "receivedToken", GET: "receivedToken"]}
path("/oauth/callback") {action: [GET: "callback"]} path("/receiveToken"){action: [POST: "receiveToken", GET: "receiveToken"]}
path("/auth"){action: [GET: "auth"]}
} }
def authPage() { def authPage() {
log.debug "In authPage" log.debug "In authPage"
if(canInstallLabs()) {
def description = null
def description if (state.vendorAccessToken == null) {
def uninstallAllowed = false log.debug "About to create access token."
def oauthTokenProvided = false
if (!state.accessToken) { createAccessToken()
log.debug "About to create access token." description = "Tap to enter Credentials."
state.accessToken = createAccessToken()
}
if (canInstallLabs()) { return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: true, install:false) {
section { href url:buildRedirectUrl("auth"), style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description }
def redirectUrl = getBuildRedirectUrl() }
log.debug "Redirect url = ${redirectUrl}" } else {
if (state.authToken) {
description = "Tap 'Next' to proceed" description = "Tap 'Next' to proceed"
uninstallAllowed = true
oauthTokenProvided = true
} else {
description = "Click to enter Credentials."
}
if (!oauthTokenProvided) { return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: true, install:false) {
log.debug "Show the login page" section { href url: buildRedirectUrl("receivedToken"), style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description }
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
section() {
paragraph "Tap below to log in to the netatmo and authorize SmartThings access."
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description
}
}
} else {
log.debug "Show the devices page"
return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
section() {
input(name:"Devices", style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description)
}
} }
} }
} else { }
else
{
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date. def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub".""" To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
@@ -99,184 +78,44 @@ To update your Hub, access Location Settings in the Main Menu (tap the gear next
} }
} }
def auth() {
redirect location: oauthInitUrl()
}
def oauthInitUrl() { def oauthInitUrl() {
log.debug "In oauthInitUrl" log.debug "In oauthInitUrl"
/* OAuth Step 1: Request access code with our client ID */
state.oauthInitState = UUID.randomUUID().toString() state.oauthInitState = UUID.randomUUID().toString()
def oauthParams = [ def oauthParams = [ response_type: "code",
response_type: "code",
client_id: getClientId(), client_id: getClientId(),
client_secret: getClientSecret(),
state: state.oauthInitState, state: state.oauthInitState,
redirect_uri: getCallbackUrl(), redirect_uri: buildRedirectUrl("receiveToken") ,
scope: "read_station" scope: "read_station"
]
log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
}
def callback() {
log.debug "callback()>> params: $params, params.code ${params.code}"
def code = params.code
def oauthState = params.state
if (oauthState == state.oauthInitState) {
def tokenParams = [
client_secret: getClientSecret(),
client_id : getClientId(),
grant_type: "authorization_code",
redirect_uri: getCallbackUrl(),
code: code,
scope: "read_station"
] ]
log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}" return getVendorAuthPath() + toQueryString(oauthParams)
def tokenUrl = getVendorTokenPath()
def params = [
uri: tokenUrl,
contentType: 'application/x-www-form-urlencoded',
body: tokenParams
]
log.debug "PARAMS: ${params}"
httpPost(params) { resp ->
def slurper = new JsonSlurper()
resp.data.each { key, value ->
def data = slurper.parseText(key)
state.refreshToken = data.refresh_token
state.authToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000)
log.debug "swapped token: $resp.data"
}
}
// Handle success and failure here, and render stuff accordingly
if (state.authToken) {
success()
} else {
fail()
}
} else {
log.error "callback() failed oauthState != state.oauthInitState"
}
} }
def success() { def buildRedirectUrl(endPoint) {
log.debug "in success" log.debug "In buildRedirectUrl"
def message = """
<p>We have located your """ + getVendorName() + """ account.</p> return getServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/${endPoint}"
<p>Tap 'Done' to continue to Devices.</p>
"""
connectionStatus(message)
} }
def fail() { def receiveToken() {
log.debug "in fail" log.debug "In receiveToken"
def message = """
<p>The connection could not be established!</p>
<p>Click 'Done' to return to the menu.</p>
"""
connectionStatus(message)
}
def connectionStatus(message, redirectUrl = null) {
def redirectHtml = ""
if (redirectUrl) {
redirectHtml = """
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
"""
}
def html = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${getVendorName()} Connection</title>
<style type="text/css">
* { box-sizing: border-box; }
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 100%;
padding: 40px;
/*background: #eee;*/
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 30px;
}
p {
font-size: 2.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
margin-bottom: 0;
}
/*
p:last-child {
margin-top: 0px;
}
*/
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
</head>
<body>
<div class="container">
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
${message}
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
def refreshToken() {
log.debug "In refreshToken"
def oauthParams = [ def oauthParams = [
client_secret: getClientSecret(), client_secret: getClientSecret(),
client_id: getClientId(), client_id: getClientId(),
grant_type: "refresh_token", grant_type: "authorization_code",
refresh_token: state.refreshToken redirect_uri: buildRedirectUrl('receiveToken'),
] code: params.code,
scope: "read_station"
]
def tokenUrl = getVendorTokenPath() def tokenUrl = getVendorTokenPath()
def params = [ def params = [
@@ -285,7 +124,201 @@ def refreshToken() {
body: oauthParams, body: oauthParams,
] ]
// OAuth Step 2: Request access token with our client Secret and OAuth "Code" log.debug params
/* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */
try {
httpPost(params) { response ->
log.debug response.data
def slurper = new JsonSlurper();
response.data.each {key, value ->
def data = slurper.parseText(key);
log.debug "Data: $data"
state.vendorRefreshToken = data.refresh_token
state.vendorAccessToken = data.access_token
state.vendorTokenExpires = now() + (data.expires_in * 1000)
return
}
}
} catch (Exception e) {
log.debug "Error: $e"
}
log.debug "State: $state"
if ( !state.vendorAccessToken ) { //We didn't get an access token, bail on install
return
}
/* OAuth Step 3: Use the access token to call into the vendor API throughout your code using state.vendorAccessToken. */
def html = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${getVendorName()} Connection</title>
<style type="text/css">
* { box-sizing: border-box; }
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 100%;
padding: 40px;
/*background: #eee;*/
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 30px;
}
p {
font-size: 2.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
margin-bottom: 0;
}
/*
p:last-child {
margin-top: 0px;
}
*/
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
</head>
<body>
<div class="container">
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
<p>We have located your """ + getVendorName() + """ account.</p>
<p>Tap 'Done' to process your credentials.</p>
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
def receivedToken() {
log.debug "In receivedToken"
def html = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Withings Connection</title>
<style type="text/css">
* { box-sizing: border-box; }
@font-face {
font-family: 'Swiss 721 W01 Thin';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Swiss 721 W01 Light';
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
font-weight: normal;
font-style: normal;
}
.container {
width: 560px;
padding: 40px;
/*background: #eee;*/
text-align: center;
}
img {
vertical-align: middle;
}
img:nth-child(2) {
margin: 0 30px;
}
p {
font-size: 2.2em;
font-family: 'Swiss 721 W01 Thin';
text-align: center;
color: #666666;
margin-bottom: 0;
}
/*
p:last-child {
margin-top: 0px;
}
*/
span {
font-family: 'Swiss 721 W01 Light';
}
</style>
</head>
<body>
<div class="container">
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
<p>Tap 'Done' to continue to Devices.</p>
</div>
</body>
</html>
"""
render contentType: 'text/html', data: html
}
// "
def refreshToken() {
log.debug "In refreshToken"
def oauthParams = [
client_secret: getClientSecret(),
client_id: getClientId(),
grant_type: "refresh_token",
refresh_token: state.vendorRefreshToken
]
def tokenUrl = getVendorTokenPath()
def params = [
uri: tokenUrl,
contentType: 'application/x-www-form-urlencoded',
body: oauthParams,
]
/* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */
try { try {
httpPost(params) { response -> httpPost(params) { response ->
def slurper = new JsonSlurper(); def slurper = new JsonSlurper();
@@ -294,9 +327,9 @@ def refreshToken() {
def data = slurper.parseText(key); def data = slurper.parseText(key);
log.debug "Data: $data" log.debug "Data: $data"
state.refreshToken = data.refresh_token state.vendorRefreshToken = data.refresh_token
state.accessToken = data.access_token state.vendorAccessToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000) state.vendorTokenExpires = now() + (data.expires_in * 1000)
return true return true
} }
@@ -305,8 +338,9 @@ def refreshToken() {
log.debug "Error: $e" log.debug "Error: $e"
} }
// We didn't get an access token log.debug "State: $state"
if ( !state.accessToken ) {
if ( !state.vendorAccessToken ) { //We didn't get an access token
return false return false
} }
} }
@@ -448,13 +482,13 @@ def listDevices() {
} }
def apiGet(String path, Map query, Closure callback) { def apiGet(String path, Map query, Closure callback) {
if(now() >= state.tokenExpires) { if(now() >= state.vendorTokenExpires) {
refreshToken(); refreshToken();
} }
query['access_token'] = state.accessToken query['access_token'] = state.vendorAccessToken
def params = [ def params = [
uri: getApiUrl(), uri: apiUrl(),
path: path, path: path,
'query': query 'query': query
] ]

View File

@@ -370,7 +370,9 @@ def parse_api_response(resp, message) {
} }
} }
def getServerUrl() { return getApiServerUrl() } def getServerUrl() {
return "https://graph.api.smartthings.com"
}
def debugEvent(message, displayEvent) { def debugEvent(message, displayEvent) {
def results = [ def results = [

View File

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

View File

@@ -297,8 +297,8 @@ private getTimeOk() {
def result = true def result = true
if (starting && ending) { if (starting && ending) {
def currTime = now() def currTime = now()
def start = timeToday(starting, location.timeZone).time def start = timeToday(starting).time
def stop = timeToday(ending, location.timeZone).time def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
} }
log.trace "timeOk = $result" log.trace "timeOk = $result"

View File

@@ -27,6 +27,7 @@ definition(
) { ) {
appSetting "clientId" appSetting "clientId"
appSetting "clientSecret" appSetting "clientSecret"
appSetting "serverUrl"
} }
preferences { preferences {
@@ -191,7 +192,7 @@ def getSmartThingsClientId() {
return "pREqugabRetre4EstetherufrePumamExucrEHuc" return "pREqugabRetre4EstetherufrePumamExucrEHuc"
} }
def getServerUrl() { getApiServerUrl() } def getServerUrl() { appSettings.serverUrl }
def buildRedirectUrl() def buildRedirectUrl()
{ {

View File

@@ -273,7 +273,7 @@ def scheduleCheck(evt) {
else { else {
if(people){ if(people){
//don't turn off lights if anyone is home //don't turn off lights if anyone is home
if(someoneIsHome){ if(someoneIsHome()){
log.debug("Stopping Check for Light") log.debug("Stopping Check for Light")
unschedule() unschedule()
} }