mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-15 21:03:23 +00:00
Compare commits
1 Commits
DEVTOOLS-1
...
MSA-971-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fafa4cb01f |
79
build.gradle
79
build.gradle
@@ -1,18 +1,15 @@
|
|||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import com.smartthings.deployment.slack.FileUpload
|
|
||||||
import com.smartthings.deployment.slack.Message
|
|
||||||
|
|
||||||
apply plugin: 'groovy'
|
apply plugin: 'groovy'
|
||||||
apply plugin: 'smartthings-executable-deployment'
|
apply plugin: 'smartthings-executable-deployment'
|
||||||
apply plugin: 'smartthings-slack'
|
apply plugin: 'smartthings-hipchat'
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.8"
|
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.3"
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
|
||||||
jcenter()
|
jcenter()
|
||||||
maven {
|
maven {
|
||||||
credentials {
|
credentials {
|
||||||
@@ -32,43 +29,7 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
slackSendMessage {
|
hipchatShareFile {
|
||||||
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
|
|
||||||
String token = project.hasProperty('slackToken') ? project.property('slackToken') : null
|
|
||||||
String webhookUrl = project.hasProperty('slackWebhookUrl') ? project.property('slackWebhookUrl') : null
|
|
||||||
String channel = project.hasProperty('slackChannel') ? project.property('slackChannel') : null
|
|
||||||
String drinks = 'https://dl.dropboxusercontent.com/s/m1z5mpd3c83lwev/minion_beer.jpeg?dl=0'
|
|
||||||
String wolverine = 'https://dl.dropboxusercontent.com/s/4lbjqzvm2v033u9/minion_wolverine.jpg?dl=0'
|
|
||||||
String beach = 'https://dl.dropboxusercontent.com/s/rqrfgxk53gfng69/minion_beach.png?dl=0'
|
|
||||||
String iconUrl
|
|
||||||
String color
|
|
||||||
String messageText
|
|
||||||
String username
|
|
||||||
switch (branch) {
|
|
||||||
case 'master':
|
|
||||||
username = 'Hickory'
|
|
||||||
iconUrl = wolverine
|
|
||||||
color = '#35D0F2'
|
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
|
|
||||||
break
|
|
||||||
case 'staging':
|
|
||||||
username = 'Dickory'
|
|
||||||
iconUrl = beach
|
|
||||||
color = '#FFDE20'
|
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
|
|
||||||
break
|
|
||||||
case 'production':
|
|
||||||
username = 'Dock'
|
|
||||||
iconUrl = drinks
|
|
||||||
color = '#FF1D23'
|
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
username = 'Hickory'
|
|
||||||
iconUrl = wolverine
|
|
||||||
color = '#35D0F2'
|
|
||||||
messageText = "Began deployment of an _SmartThingsPublic[${branch}]_ branch. Have no idea what's going on."
|
|
||||||
}
|
|
||||||
List<String> archives = []
|
List<String> archives = []
|
||||||
File rootDir = new File("${project.buildDir}/archives")
|
File rootDir = new File("${project.buildDir}/archives")
|
||||||
if (rootDir.exists()) {
|
if (rootDir.exists()) {
|
||||||
@@ -81,25 +42,19 @@ slackSendMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Date date = new Date()
|
|
||||||
String fileDate = date.format('yyyy-MM-dd_HH-mm-ss', TimeZone.getTimeZone('GMT'))
|
|
||||||
|
|
||||||
// Required Task Arguments.
|
// Set task properties
|
||||||
file = new FileUpload(
|
data = archives.join('\n').getBytes(StandardCharsets.UTF_8)
|
||||||
data: archives.join('\n').getBytes(StandardCharsets.UTF_8),
|
fileName = 'deployment-notes.txt'
|
||||||
filename: "deployment-notes-${fileDate}.txt",
|
contentType = 'text/html'
|
||||||
title: 'Deployment Notes',
|
}
|
||||||
channels: channel,
|
|
||||||
token: token,
|
hipchatSendNotification {
|
||||||
color: color
|
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
|
||||||
)
|
message = "Began executable deploy of SmartThingsPublic(${branch})."
|
||||||
message = new Message(
|
if (branch == 'master') {
|
||||||
webhookUrl: webhookUrl,
|
message += ' (dev shards)'
|
||||||
username: username,
|
}
|
||||||
asUser: true,
|
color = branch == 'master' ? 'yellow' : 'red'
|
||||||
iconUrl: iconUrl,
|
notify = true
|
||||||
channel: channel,
|
|
||||||
fallback: 'Deployment Notification',
|
|
||||||
text: messageText
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
circle.yml
10
circle.yml
@@ -15,11 +15,13 @@ deployment:
|
|||||||
develop:
|
develop:
|
||||||
branch: master
|
branch: master
|
||||||
commands:
|
commands:
|
||||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_DEV"
|
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_DEV
|
||||||
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
|
||||||
|
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD
|
||||||
|
|
||||||
stage:
|
stage:
|
||||||
branch: staging
|
branch: staging
|
||||||
commands:
|
commands:
|
||||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_STAGE"
|
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_STAGING
|
||||||
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
|
||||||
|
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 SmartThings
|
|
||||||
*
|
|
||||||
* 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: "Beacon Capability", namespace: "capabilities", author: "SmartThings") {
|
|
||||||
capability "Beacon"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
status "present": "beacon: present"
|
|
||||||
status "not present": "beacon: not present"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles {
|
|
||||||
standardTile("beacon", "device.beacon", width: 2, height: 2) {
|
|
||||||
state("not present", label:'not present', icon:"st.presence.tile.not-present", backgroundColor:"#ffffff")
|
|
||||||
state("present", label:'present', icon:"st.presence.tile.present", backgroundColor:"#53a7c0")
|
|
||||||
}
|
|
||||||
main "beacon"
|
|
||||||
details "beacon"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
def pair = description.split(":")
|
|
||||||
createEvent(name: pair[0].trim(), value: pair[1].trim())
|
|
||||||
}
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
/**
|
|
||||||
* Fibaro Door/Window Sensor ZW5
|
|
||||||
*
|
|
||||||
* Copyright 2016 Fibar Group S.A.
|
|
||||||
*
|
|
||||||
* 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: "Fibaro Door/Window Sensor ZW5 with Temperature", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
|
||||||
capability "Battery"
|
|
||||||
capability "Contact Sensor"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Tamper Alert"
|
|
||||||
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x31, 0x86", outClusters: ""
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x31, 0x86, 0x84", outClusters: ""//actual NIF
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"FGK", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
|
||||||
tileAttribute("device.contact", key:"PRIMARY_CONTROL") {
|
|
||||||
attributeState("open", icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
|
||||||
attributeState("closed", icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
|
||||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
|
||||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, , width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "temperature", label:'${currentValue}°',
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 31, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
main "FGK"
|
|
||||||
details(["FGK","battery", "temperature"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
def result = []
|
|
||||||
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
if (state.sec) {
|
|
||||||
result = createEvent(descriptionText:description, displayed:false)
|
|
||||||
} else {
|
|
||||||
result = createEvent(
|
|
||||||
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
|
||||||
eventType: "ALERT",
|
|
||||||
name: "secureInclusion",
|
|
||||||
value: "failed",
|
|
||||||
displayed: true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (description == "updated") {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
|
|
||||||
if (cmd) {
|
|
||||||
log.debug "Parsed '${cmd}'"
|
|
||||||
zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//security
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x98: 1])
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
return zwaveEvent(encapsulatedCommand)
|
|
||||||
} else {
|
|
||||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
|
||||||
createEvent(descriptionText: cmd.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//crc16
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
|
||||||
{
|
|
||||||
def versions = [0x31: 5, 0x72: 2, 0x80: 1, 0x86: 1]
|
|
||||||
def version = versions[cmd.commandClass as Integer]
|
|
||||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
|
||||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
|
||||||
if (!encapsulatedCommand) {
|
|
||||||
log.debug "Could not extract command from $cmd"
|
|
||||||
} else {
|
|
||||||
zwaveEvent(encapsulatedCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
|
||||||
//it is assumed that default notification events are used
|
|
||||||
//(parameter 20 was not changed before device's re-inclusion)
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.notificationType == 6) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 22:
|
|
||||||
map.name = "contact"
|
|
||||||
map.value = "open"
|
|
||||||
map.descriptionText = "${device.displayName}: is open"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 23:
|
|
||||||
map.name = "contact"
|
|
||||||
map.value = "closed"
|
|
||||||
map.descriptionText = "${device.displayName}: is closed"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (cmd.notificationType == 7) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 0:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
map.name = "battery"
|
|
||||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
|
||||||
map.unit = "%"
|
|
||||||
map.displayed = true
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
|
||||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
|
||||||
def cmds = []
|
|
||||||
cmds << encap(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 500"
|
|
||||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
|
|
||||||
cmds << "delay 1200"
|
|
||||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
[event, response(cmds)]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
|
||||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
|
||||||
log.debug "productId: ${cmd.productId}"
|
|
||||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
|
||||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
|
||||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
|
||||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
|
||||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
|
||||||
|
|
||||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
|
||||||
String serialNumber = "h'"
|
|
||||||
|
|
||||||
cmd.deviceIdData.each{ data ->
|
|
||||||
serialNumber += "${String.format("%02X", data)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDataValue("serialNumber", serialNumber)
|
|
||||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
|
||||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
|
||||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
|
||||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
|
||||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
|
||||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
|
||||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.sensorType == 1) {
|
|
||||||
// temperature
|
|
||||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
|
||||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
|
||||||
map.unit = getTemperatureScale()
|
|
||||||
map.name = "temperature"
|
|
||||||
map.displayed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
|
||||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Executing 'configure'"
|
|
||||||
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGK's default wake up interval
|
|
||||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
|
||||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
|
||||||
cmds += zwave.versionV1.versionGet()
|
|
||||||
cmds += zwave.batteryV1.batteryGet()
|
|
||||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
|
|
||||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
|
||||||
|
|
||||||
encapSequence(cmds, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
|
|
||||||
private crc16(physicalgraph.zwave.Command cmd) {
|
|
||||||
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
|
|
||||||
"5601${cmd.format()}0000"
|
|
||||||
}
|
|
||||||
|
|
||||||
private encapSequence(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ encap(it) }, delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private encap(physicalgraph.zwave.Command cmd) {
|
|
||||||
def secureClasses = [0x20, 0x2B, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
|
||||||
|
|
||||||
//todo: check if secure inclusion was successful
|
|
||||||
//if not do not send security-encapsulated command
|
|
||||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
|
||||||
secure(cmd)
|
|
||||||
} else {
|
|
||||||
crc16(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
/**
|
|
||||||
* Fibaro Flood Sensor ZW5
|
|
||||||
*
|
|
||||||
* Copyright 2016 Fibar Group S.A.
|
|
||||||
*
|
|
||||||
* 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: "Fibaro Flood Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
|
||||||
capability "Battery"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Tamper Alert"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
capability "Water Sensor"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x22, 0x85, 0x59, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x9C, 0x31, 0x86", outClusters: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"FGFS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
|
||||||
tileAttribute("device.water", key:"PRIMARY_CONTROL") {
|
|
||||||
attributeState("dry", icon:"st.alarm.water.dry", backgroundColor:"#79b821")
|
|
||||||
attributeState("wet", icon:"st.alarm.water.wet", backgroundColor:"#ffa81e")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
|
||||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
|
||||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "temperature", label:'${currentValue}°',
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 31, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
main "FGFS"
|
|
||||||
details(["FGFS","battery", "temperature"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
def result = []
|
|
||||||
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
if (state.sec) {
|
|
||||||
result = createEvent(descriptionText:description, displayed:false)
|
|
||||||
} else {
|
|
||||||
result = createEvent(
|
|
||||||
descriptionText: "FGFS failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
|
||||||
eventType: "ALERT",
|
|
||||||
name: "secureInclusion",
|
|
||||||
value: "failed",
|
|
||||||
displayed: true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (description == "updated") {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72:2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
|
|
||||||
if (cmd) {
|
|
||||||
log.debug "Parsed '${cmd}'"
|
|
||||||
zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//security
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
return zwaveEvent(encapsulatedCommand)
|
|
||||||
} else {
|
|
||||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
|
||||||
createEvent(descriptionText: cmd.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//crc16
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
|
||||||
{
|
|
||||||
def versions = [0x31: 5, 0x72: 2, 0x80: 1]
|
|
||||||
def version = versions[cmd.commandClass as Integer]
|
|
||||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
|
||||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
|
||||||
if (!encapsulatedCommand) {
|
|
||||||
log.debug "Could not extract command from $cmd"
|
|
||||||
} else {
|
|
||||||
zwaveEvent(encapsulatedCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
|
|
||||||
{
|
|
||||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
|
||||||
def cmds = []
|
|
||||||
cmds << encap(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 500"
|
|
||||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
|
|
||||||
cmds << "delay 1200"
|
|
||||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
[event, response(cmds)]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
|
||||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
|
||||||
log.debug "productId: ${cmd.productId}"
|
|
||||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
|
||||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
|
||||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
|
||||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
|
||||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
|
||||||
|
|
||||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
|
||||||
String serialNumber = "h'"
|
|
||||||
|
|
||||||
cmd.deviceIdData.each{ data ->
|
|
||||||
serialNumber += "${String.format("%02X", data)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDataValue("serialNumber", serialNumber)
|
|
||||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
|
||||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
|
||||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
|
||||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
|
||||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
|
||||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
|
||||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
map.name = "battery"
|
|
||||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
|
||||||
map.unit = "%"
|
|
||||||
map.displayed = true
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.notificationType == 5) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 2:
|
|
||||||
map.name = "water"
|
|
||||||
map.value = "wet"
|
|
||||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0:
|
|
||||||
map.name = "water"
|
|
||||||
map.value = "dry"
|
|
||||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (cmd.notificationType == 7) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 0:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.sensorType == 1) {
|
|
||||||
// temperature
|
|
||||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
|
||||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
|
||||||
map.unit = getTemperatureScale()
|
|
||||||
map.name = "temperature"
|
|
||||||
map.displayed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
|
||||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Executing 'configure'"
|
|
||||||
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGFS' default wake up interval
|
|
||||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
|
||||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
|
||||||
cmds += zwave.versionV1.versionGet()
|
|
||||||
cmds += zwave.batteryV1.batteryGet()
|
|
||||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
|
|
||||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
|
||||||
|
|
||||||
encapSequence(cmds, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
|
|
||||||
private crc16(physicalgraph.zwave.Command cmd) {
|
|
||||||
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
|
|
||||||
"5601${cmd.format()}0000"
|
|
||||||
}
|
|
||||||
|
|
||||||
private encapSequence(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ encap(it) }, delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private encap(physicalgraph.zwave.Command cmd) {
|
|
||||||
def secureClasses = [0x20, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
|
||||||
|
|
||||||
//todo: check if secure inclusion was successful
|
|
||||||
//if not do not send security-encapsulated command
|
|
||||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
|
||||||
secure(cmd)
|
|
||||||
} else {
|
|
||||||
crc16(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,281 +0,0 @@
|
|||||||
/**
|
|
||||||
* Fibaro Motion Sensor ZW5
|
|
||||||
*
|
|
||||||
* Copyright 2016 Fibar Group S.A.
|
|
||||||
*
|
|
||||||
* 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: "Fibaro Motion Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
|
||||||
capability "Battery"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Illuminance Measurement"
|
|
||||||
capability "Motion Sensor"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Tamper Alert"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x20, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x30, 0x9C, 0x98, 0x7A", outClusters: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
|
||||||
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
|
||||||
attributeState("inactive", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
|
||||||
attributeState("active", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
|
||||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
|
||||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "temperature", label:'${currentValue}°',
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 31, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
main "FGMS"
|
|
||||||
details(["FGMS","battery","temperature","illuminance"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
def result = []
|
|
||||||
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
if (state.sec) {
|
|
||||||
result = createEvent(descriptionText:description, displayed:false)
|
|
||||||
} else {
|
|
||||||
result = createEvent(
|
|
||||||
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
|
||||||
eventType: "ALERT",
|
|
||||||
name: "secureInclusion",
|
|
||||||
value: "failed",
|
|
||||||
displayed: true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (description == "updated") {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
|
|
||||||
if (cmd) {
|
|
||||||
log.debug "Parsed '${cmd}'"
|
|
||||||
zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//security
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
return zwaveEvent(encapsulatedCommand)
|
|
||||||
} else {
|
|
||||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
|
||||||
createEvent(descriptionText: cmd.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//crc16
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
|
||||||
{
|
|
||||||
def versions = [0x31: 5, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1]
|
|
||||||
def version = versions[cmd.commandClass as Integer]
|
|
||||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
|
||||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
|
||||||
if (!encapsulatedCommand) {
|
|
||||||
log.debug "Could not extract command from $cmd"
|
|
||||||
} else {
|
|
||||||
zwaveEvent(encapsulatedCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
|
||||||
def map = [ displayed: true ]
|
|
||||||
switch (cmd.sensorType) {
|
|
||||||
case 1:
|
|
||||||
map.name = "temperature"
|
|
||||||
map.unit = cmd.scale == 1 ? "F" : "C"
|
|
||||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, map.unit, cmd.precision)
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
map.name = "illuminance"
|
|
||||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
|
||||||
map.unit = "lux"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.notificationType == 7) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 0:
|
|
||||||
if (cmd.eventParameter[0] == 3) {
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
|
||||||
}
|
|
||||||
if (cmd.eventParameter[0] == 8) {
|
|
||||||
map.name = "motion"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: motion has stopped"
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 8:
|
|
||||||
map.name = "motion"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: motion detected"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
map.name = "battery"
|
|
||||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
|
||||||
map.unit = "%"
|
|
||||||
map.displayed = true
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
|
|
||||||
{
|
|
||||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
|
||||||
def cmds = []
|
|
||||||
cmds << encap(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 500"
|
|
||||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
|
|
||||||
cmds << "delay 500"
|
|
||||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3, scale: 1))
|
|
||||||
cmds << "delay 1200"
|
|
||||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
[event, response(cmds)]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
|
||||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
|
||||||
log.debug "productId: ${cmd.productId}"
|
|
||||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
|
||||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
|
||||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
|
||||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
|
||||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
|
||||||
|
|
||||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
|
||||||
String serialNumber = "h'"
|
|
||||||
|
|
||||||
cmd.deviceIdData.each{ data ->
|
|
||||||
serialNumber += "${String.format("%02X", data)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDataValue("serialNumber", serialNumber)
|
|
||||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
|
||||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
|
||||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
|
||||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
|
||||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
|
||||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
|
||||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
|
||||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Executing 'configure'"
|
|
||||||
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds: 7200, nodeid: zwaveHubNodeId)//FGMS' default wake up interval
|
|
||||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
|
||||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
|
||||||
cmds += zwave.versionV1.versionGet()
|
|
||||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId])
|
|
||||||
cmds += zwave.batteryV1.batteryGet()
|
|
||||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
|
|
||||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3, scale: 1)
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
|
||||||
|
|
||||||
encapSequence(cmds, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
|
|
||||||
private crc16(physicalgraph.zwave.Command cmd) {
|
|
||||||
//zwave.crc16encapV1.crc16Encap().encapsulate(cmd).format()
|
|
||||||
"5601${cmd.format()}0000"
|
|
||||||
}
|
|
||||||
|
|
||||||
private encapSequence(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ encap(it) }, delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private encap(physicalgraph.zwave.Command cmd) {
|
|
||||||
def secureClasses = [0x20, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
|
||||||
|
|
||||||
//todo: check if secure inclusion was successful
|
|
||||||
//if not do not send security-encapsulated command
|
|
||||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
|
||||||
secure(cmd)
|
|
||||||
} else {
|
|
||||||
crc16(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Fibaro Door/Window Sensor ZW5
|
* Fibaro Wall Plug ZW5
|
||||||
*
|
*
|
||||||
* Copyright 2016 Fibar Group S.A.
|
* Copyright 2016 Fibar Group S.A.
|
||||||
*
|
*
|
||||||
@@ -14,48 +14,56 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Fibaro Door/Window Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
definition (name: "Fibaro Wall Plug ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
||||||
capability "Battery"
|
capability "Actuator"
|
||||||
capability "Contact Sensor"
|
capability "Configuration"
|
||||||
|
capability "Energy Meter"
|
||||||
|
capability "Power Meter"
|
||||||
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Configuration"
|
capability "Switch"
|
||||||
capability "Tamper Alert"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x86, 0x84", outClusters: ""
|
command "reset"
|
||||||
|
|
||||||
|
fingerprint deviceId: "0x1001", inClusters: "0x5E, 0x22, 0x85, 0x59, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x32, 0x8E, 0x71, 0x73, 0x98, 0x31, 0x25, 0x86", outClusters: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"FGK", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
multiAttributeTile(name:"FGWP", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||||
tileAttribute("device.contact", key:"PRIMARY_CONTROL") {
|
tileAttribute("device.switch", key:"PRIMARY_CONTROL") {
|
||||||
attributeState("open", icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
attributeState("on", label: '${name}', action: "switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821")
|
||||||
attributeState("closed", icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
attributeState("off", label: '${name}', action: "switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff")
|
||||||
}
|
}
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
tileAttribute("device.power", key:"SECONDARY_CONTROL") {
|
||||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
attributeState("default", label:'${currentValue} W', backgroundColor:"#ffffff")
|
||||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, , width: 2, height: 2, decoration: "flat") {
|
valueTile("energy", "device.energy", width: 2, height: 2) {
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
state "default", label:'${currentValue} kWh'
|
||||||
}
|
}
|
||||||
|
standardTile("reset", "device.energy", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "default", label:'reset kWh', action:"reset"
|
||||||
|
}
|
||||||
|
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
|
}
|
||||||
|
|
||||||
main "FGK"
|
main "FGWP"
|
||||||
details(["FGK","battery"])
|
details(["FGWP", "energy", "reset", "refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "Parsing '${description}'"
|
log.debug "Parsing '${description}'"
|
||||||
def result = []
|
|
||||||
|
|
||||||
if (description.startsWith("Err 106")) {
|
if (description.startsWith("Err 106")) {
|
||||||
if (state.sec) {
|
if (state.sec) {
|
||||||
result = createEvent(descriptionText:description, displayed:false)
|
result = createEvent(descriptionText:description, displayed:false)
|
||||||
} else {
|
} else {
|
||||||
@@ -70,7 +78,7 @@ def parse(String description) {
|
|||||||
} else if (description == "updated") {
|
} else if (description == "updated") {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
def cmd = zwave.parse(description, [0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
def cmd = zwave.parse(description, [0x25: 1, 0x31: 5, 0x32: 1, 0x5A: 1, 0x71: 3, 0x72: 2, 0x86: 1])
|
||||||
|
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
log.debug "Parsed '${cmd}'"
|
log.debug "Parsed '${cmd}'"
|
||||||
@@ -81,7 +89,7 @@ def parse(String description) {
|
|||||||
|
|
||||||
//security
|
//security
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x98: 1])
|
def encapsulatedCommand = cmd.encapsulatedCommand([0x25: 1, 0x5A: 1])
|
||||||
if (encapsulatedCommand) {
|
if (encapsulatedCommand) {
|
||||||
return zwaveEvent(encapsulatedCommand)
|
return zwaveEvent(encapsulatedCommand)
|
||||||
} else {
|
} else {
|
||||||
@@ -93,7 +101,7 @@ def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulat
|
|||||||
//crc16
|
//crc16
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
||||||
{
|
{
|
||||||
def versions = [0x72: 2, 0x80: 1, 0x86: 1]
|
def versions = [0x31: 5, 0x32: 1, 0x71: 3, 0x72: 2, 0x86: 1]
|
||||||
def version = versions[cmd.commandClass as Integer]
|
def version = versions[cmd.commandClass as Integer]
|
||||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||||
@@ -104,59 +112,12 @@ def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.meterv1.MeterReport cmd) {
|
||||||
//it is assumed that default notification events are used
|
if (cmd.scale == 0) {
|
||||||
//(parameter 20 was not changed before device's re-inclusion)
|
createEvent(name: "energy", value: cmd.scaledMeterValue, unit: "kWh")
|
||||||
def map = [:]
|
} else if (cmd.scale == 2) {
|
||||||
if (cmd.notificationType == 6) {
|
createEvent(name: "power", value: Math.round(cmd.scaledMeterValue), unit: "W")
|
||||||
switch (cmd.event) {
|
}
|
||||||
case 22:
|
|
||||||
map.name = "contact"
|
|
||||||
map.value = "open"
|
|
||||||
map.descriptionText = "${device.displayName}: is open"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 23:
|
|
||||||
map.name = "contact"
|
|
||||||
map.value = "closed"
|
|
||||||
map.descriptionText = "${device.displayName}: is closed"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (cmd.notificationType == 7) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 0:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
map.name = "battery"
|
|
||||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
|
||||||
map.unit = "%"
|
|
||||||
map.displayed = true
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
|
||||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
|
||||||
def cmds = []
|
|
||||||
cmds << encap(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 1200"
|
|
||||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
[event, response(cmds)]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||||
@@ -193,32 +154,86 @@ def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
|||||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
||||||
|
def map = [ displayed: true ]
|
||||||
|
if (cmd.sensorType == 4) {
|
||||||
|
createEvent(name: "power", value: Math.round(cmd.scaledSensorValue), unit: "W")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
|
||||||
|
createEvent(name: "switch", value: cmd.value ? "on" : "off", type: "digital")
|
||||||
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
||||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd)
|
||||||
|
{
|
||||||
|
if (cmd.notificationType == 0x08) {
|
||||||
|
if (cmd.event == 0x06) {
|
||||||
|
createEvent(descriptionText: "Warning: $device.displayName detected over-current", isStateChange: true)
|
||||||
|
} else if (cmd.event == 0x08) {
|
||||||
|
createEvent(descriptionText: "Warning: $device.displayName detected over-load", isStateChange: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||||
|
log.debug "$device.displayName: Unhandled: $cmd"
|
||||||
|
[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle commands
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Executing 'configure'"
|
log.debug "Executing 'configure'"
|
||||||
|
|
||||||
def cmds = []
|
def cmds = []
|
||||||
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGK's default wake up interval
|
cmds += zwave.manufacturerSpecificV1.manufacturerSpecificGet()
|
||||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
|
||||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
||||||
cmds += zwave.versionV1.versionGet()
|
cmds += zwave.versionV1.versionGet()
|
||||||
cmds += zwave.batteryV1.batteryGet()
|
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId])
|
||||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
|
cmds += zwave.meterV2.meterGet(scale:0)
|
||||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
cmds += zwave.meterV2.meterGet(scale:2)
|
||||||
|
cmds += zwave.switchBinaryV1.switchBinaryGet()
|
||||||
|
|
||||||
encapSequence(cmds, 500)
|
encapSequence(cmds, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def refresh() {
|
||||||
|
log.debug "Executing 'refresh'"
|
||||||
|
|
||||||
|
def cmds = []
|
||||||
|
cmds += zwave.meterV2.meterGet(scale:0)
|
||||||
|
cmds += zwave.meterV2.meterGet(scale:2)
|
||||||
|
cmds += zwave.switchBinaryV1.switchBinaryGet()
|
||||||
|
|
||||||
|
encapSequence(cmds, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
def on() {
|
||||||
|
log.debug "Executing 'on'"
|
||||||
|
encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF))
|
||||||
|
}
|
||||||
|
|
||||||
|
def off() {
|
||||||
|
log.debug "Executing 'off'"
|
||||||
|
encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00))
|
||||||
|
}
|
||||||
|
|
||||||
|
def reset() {
|
||||||
|
log.debug "Executing 'reset'"
|
||||||
|
encap(zwave.meterV2.meterReset())
|
||||||
|
}
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
private secure(physicalgraph.zwave.Command cmd) {
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
private crc16(physicalgraph.zwave.Command cmd) {
|
private crc16(physicalgraph.zwave.Command cmd) {
|
||||||
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
|
//zwave.crc16encapV1.crc16Encap().encapsulate(cmd).format()
|
||||||
"5601${cmd.format()}0000"
|
"5601${cmd.format()}0000"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,9 +242,9 @@ private encapSequence(commands, delay=200) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private encap(physicalgraph.zwave.Command cmd) {
|
private encap(physicalgraph.zwave.Command cmd) {
|
||||||
def secureClasses = [0x20, 0x2B, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
def secureClasses = [0x25, 0x5A, 0x70, 0x85, 0x8E]
|
||||||
|
|
||||||
//todo: check if secure inclusion was successful
|
//todo: check if secure inclusion was successful
|
||||||
//if not do not send security-encapsulated command
|
//if not do not send security-encapsulated command
|
||||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
if (secureClasses.find{ it == cmd.commandClassId }) {
|
||||||
secure(cmd)
|
secure(cmd)
|
||||||
@@ -1,795 +0,0 @@
|
|||||||
/**
|
|
||||||
* CoopBoss H3Vx
|
|
||||||
* 02/29/16 Fixed app crash with Android by changing the syntax of default state in tile definition.
|
|
||||||
* Fixed null value errors during join process. Added 3 new commands to refresh data.
|
|
||||||
*
|
|
||||||
* 01/18/16 Masked invalid temperature reporting when TempProbe1 is below 0C
|
|
||||||
* Added setBaseCurrentNE, readBaseCurrentNE, commands as well as baseCurrentNE attribute.
|
|
||||||
*
|
|
||||||
* Copyright 2016 John Rucker
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* Icon location = http://scripts.3dgo.net/smartthings/icons/
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "CoopBoss H3Vx", namespace: "JohnRucker", author: "John.Rucker@Solar-Current.com") {
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
capability "Door Control"
|
|
||||||
capability "Switch"
|
|
||||||
|
|
||||||
command "closeDoor"
|
|
||||||
command "closeDoorHiI"
|
|
||||||
command "openDoor"
|
|
||||||
command "autoCloseOn"
|
|
||||||
command "autoCloseOff"
|
|
||||||
command "autoOpenOn"
|
|
||||||
command "autoOpenOff"
|
|
||||||
command "setCloseLevelTo"
|
|
||||||
command "setOpenLevelTo"
|
|
||||||
command "setSensitivityLevel"
|
|
||||||
command "Aux1On"
|
|
||||||
command "Aux1Off"
|
|
||||||
command "Aux2On"
|
|
||||||
command "Aux2Off"
|
|
||||||
command "updateTemp1"
|
|
||||||
command "updateTemp2"
|
|
||||||
command "updateSun"
|
|
||||||
command "setNewBaseCurrent"
|
|
||||||
command "setNewPhotoCalibration"
|
|
||||||
command "readNewPhotoCalibration"
|
|
||||||
command "readBaseCurrentNE"
|
|
||||||
command "setBaseCurrentNE"
|
|
||||||
command "updateSensitivity"
|
|
||||||
command "updateCloseLightLevel"
|
|
||||||
command "updateOpenLightLevel"
|
|
||||||
|
|
||||||
attribute "doorState","string"
|
|
||||||
attribute "currentLightLevel","number"
|
|
||||||
attribute "closeLightLevel","number"
|
|
||||||
attribute "openLightLevel","number"
|
|
||||||
attribute "autoCloseEnable","string"
|
|
||||||
attribute "autoOpenEnable","string"
|
|
||||||
attribute "TempProb1","number"
|
|
||||||
attribute "TempProb2","number"
|
|
||||||
attribute "dayOrNight","string"
|
|
||||||
attribute "doorSensitivity","number"
|
|
||||||
attribute "doorCurrent","number"
|
|
||||||
attribute "doorVoltage","number"
|
|
||||||
attribute "Aux1","string"
|
|
||||||
attribute "Aux2","string"
|
|
||||||
attribute "coopStatus","string"
|
|
||||||
attribute "baseDoorCurrent","number"
|
|
||||||
attribute "photoCalibration","number"
|
|
||||||
attribute "baseCurrentNE","string"
|
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0101,0402", manufacturer: "Solar-Current", model: "Coop Boss"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
input description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
|
||||||
input "tempOffsetCoop", "number", title: "Coop Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
|
||||||
input "tempOffsetOutside", "number", title: "Outside Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
tiles(scale: 2){
|
|
||||||
multiAttributeTile(name:"doorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
|
||||||
{
|
|
||||||
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", nextState:"Sent"
|
|
||||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#0000ff" , nextState:"Sent"
|
|
||||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
|
||||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#79b821", nextState:"Sent"
|
|
||||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
|
||||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
|
||||||
attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent"
|
|
||||||
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
|
||||||
attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.coopStatus", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "device.coopStatus", label:'${currentValue}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
multiAttributeTile(name:"dtlsDoorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
|
||||||
{
|
|
||||||
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.secondary.tools", nextState:"Sent"
|
|
||||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.doors.garage.garage-open", backgroundColor: "#0000ff", nextState:"Sent"
|
|
||||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.doors.garage.garage-opening", backgroundColor: "#ffa81e"
|
|
||||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821", nextState:"Sent"
|
|
||||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.doors.garage.garage-closing", backgroundColor: "#ffa81e"
|
|
||||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.doors.garage.garage-open", backgroundColor: "#ff0000", nextState:"Sent"
|
|
||||||
attributeState "forced close", label: "forced", action:"openDoor", icon: "st.doors.garage.garage-closed", backgroundColor: "#ff8000", nextState:"Sent"
|
|
||||||
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.secondary.tools", backgroundColor: "#ff0000", nextState:"Sent"
|
|
||||||
attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.doorState", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "unknown", label: 'Door is in unknown state. Push to open.'
|
|
||||||
attributeState "open", label: 'Coop door is open. Push to close.'
|
|
||||||
attributeState "opening", label: 'Caution, door is opening!'
|
|
||||||
attributeState "closed", label: 'Coop door is closed. Push to open.'
|
|
||||||
attributeState "closing", label: 'Caution, door is closing!'
|
|
||||||
attributeState "jammed", label: 'Door open! Push for high-force close'
|
|
||||||
attributeState "forced close", label: "Door is closed. Push to open."
|
|
||||||
attributeState "fault", label: 'Door fault check electrical connection.'
|
|
||||||
attributeState "Sent", label: 'Command sent to CoopBoss...'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("autoClose", "device.autoCloseEnable", width: 2, height: 2){
|
|
||||||
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#79b821", nextState:"Sent"
|
|
||||||
state "off", label: 'Auto', action:"autoCloseOn", icon: "st.doors.garage.garage-closing", nextState:"Sent"
|
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("autoOpen", "device.autoOpenEnable", width: 2, height: 2){
|
|
||||||
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#79b821", nextState:"Sent"
|
|
||||||
state "off", label: 'Auto', action:"autoOpenOn", icon: "st.doors.garage.garage-opening", nextState:"Sent"
|
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("TempProb1", "device.TempProb1", width: 2, height: 2, decoration: "flat"){
|
|
||||||
state "default", label:'Coop\r${currentValue}°', unit:"F", action:"updateTemp1"}
|
|
||||||
|
|
||||||
valueTile("TempProb2", "device.TempProb2", width: 2, height: 2, decoration: "flat"){
|
|
||||||
state "default", label:'Outside\r${currentValue}°', unit:"F", action:"updateTemp2"}
|
|
||||||
|
|
||||||
valueTile("currentLevel", "device.currentLightLevel", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "default", label:'Sun\r${currentValue}', action:"updateSun"}
|
|
||||||
|
|
||||||
valueTile("dayOrNight", "device.dayOrNight", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "default", label:'${currentValue}.'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("SetClSlider", "device.closeLightLevel", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") {
|
|
||||||
state "closeLightLevel", action:"setCloseLevelTo", backgroundColor:"#d04e00"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("SetClValue", "device.closeLightLevel", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "default", label:'Close\nSunlight\n${currentValue}', action:'updateCloseLightLevel'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("SetOpSlider", "device.openLightLevel", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") {
|
|
||||||
state "openLightLevel", action:"setOpenLevelTo", backgroundColor:"#d04e00"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("SetOpValue", "device.openLightLevel", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "default", label:'Open\nSunlight\n${currentValue}', action:'updateOpenLightLevel'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("SetSensitivitySlider", "device.doorSensitivity", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") {
|
|
||||||
state "openLightLevel", action:"setSensitivityLevel", backgroundColor:"#d04e00"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("SetSensitivityValue", "device.doorSensitivity", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "default", label:'Door\nSensitivity\n${currentValue}', action:'updateSensitivity'
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", width: 2, height: 2, decoration: "flat", inactiveLabel: false) {
|
|
||||||
state "default", label:'All', action:"refresh.refresh", icon:"st.secondary.refresh-icon"
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("aux1", "device.Aux1", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "off", label:'Aux 1', action:"Aux1On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
|
||||||
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("aux2", "device.Aux2", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "off", label:'Aux 2', action:"Aux2On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
|
||||||
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "doorCtrl"
|
|
||||||
details (["dtlsDoorCtrl", "TempProb1", "TempProb2", "currentLevel", "autoClose", "autoOpen", "dayOrNight",
|
|
||||||
"SetClSlider", "SetClValue", "SetOpSlider", "SetOpValue", "SetSensitivitySlider", "SetSensitivityValue",
|
|
||||||
"aux1", "aux2", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events def parse(String description) {
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "description: $description"
|
|
||||||
Map map = [:]
|
|
||||||
if (description?.startsWith('catchall:')) {
|
|
||||||
map = parseCatchAllMessage(description)
|
|
||||||
}
|
|
||||||
else if (description?.startsWith('read attr -')) {
|
|
||||||
map = parseReportAttributeMessage(description)
|
|
||||||
}
|
|
||||||
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
|
|
||||||
map = parseCustomMessage(description)
|
|
||||||
}
|
|
||||||
log.debug map
|
|
||||||
//return map ? createEvent(map) : null
|
|
||||||
sendEvent(map)
|
|
||||||
callUpdateStatusTxt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseCatchAllMessage(String description) {
|
|
||||||
Map resultMap = [:]
|
|
||||||
def cluster = zigbee.parse(description)
|
|
||||||
log.debug cluster
|
|
||||||
if (cluster.clusterId == 0x0402) {
|
|
||||||
switch(cluster.sourceEndpoint) {
|
|
||||||
|
|
||||||
case 0x39: // Endpoint 0x39 is the temperature of probe 1
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
resultMap.name = "TempProb1"
|
|
||||||
def celsius = Integer.valueOf(temp,16).shortValue()
|
|
||||||
if (celsius == -32768){ // This number is used to indicate an error in the temperature reading
|
|
||||||
resultMap.value = "---"
|
|
||||||
}else{
|
|
||||||
celsius = celsius / 100 // Temperature value is sent X 100.
|
|
||||||
resultMap.value = celsiusToFahrenheit(celsius)
|
|
||||||
if (tempOffsetOutside) {
|
|
||||||
def offset = tempOffsetOutside as int
|
|
||||||
resultMap.value = resultMap.value + offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendEvent(name: "temperature", value: resultMap.value, displayed: false) // set the temperatureMeasurment capability to temperature
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0x40: // Endpoint 0x40 is the temperature of probe 2
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
resultMap.name = "TempProb2"
|
|
||||||
def celsius = Integer.valueOf(temp,16).shortValue()
|
|
||||||
//resultMap.descriptionText = "Prob2 celsius value = ${celsius}"
|
|
||||||
if (celsius == -32768){ // This number is used to indicate an error in the temperature reading
|
|
||||||
resultMap.value = "---"
|
|
||||||
}else{
|
|
||||||
celsius = celsius / 100 // Temperature value is sent X 100.
|
|
||||||
resultMap.value = celsiusToFahrenheit(celsius)
|
|
||||||
if (tempOffsetCoop) {
|
|
||||||
def offset = tempOffsetCoop as int
|
|
||||||
resultMap.value = resultMap.value + offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cluster.clusterId == 0x0101 && cluster.command == 0x0b) { // This is a default response to a command sent to cluster 0x0101 door control
|
|
||||||
//log.debug "Default Response Data = $cluster.data"
|
|
||||||
switch(cluster.data) {
|
|
||||||
|
|
||||||
case "[10, 0]": // 0x0a turn auto close on command verified
|
|
||||||
resultMap.name = "autoCloseEnable"
|
|
||||||
resultMap.value = "on"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[11, 0]": // 0x0b turn auto close off command verified
|
|
||||||
resultMap.name = "autoCloseEnable"
|
|
||||||
resultMap.value = "off"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[12, 0]": // 0x0C turn auto open on command verified
|
|
||||||
resultMap.name = "autoOpenEnable"
|
|
||||||
resultMap.value = "on"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[13, 0]": // 0x0d turn auto open off command verified
|
|
||||||
resultMap.name = "autoOpenEnable"
|
|
||||||
resultMap.value = "off"
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
case "[20, 0]": // 0x14 Aux1 On command verified
|
|
||||||
log.info "verified Aux1 On"
|
|
||||||
sendEvent(name: "switch", value: "on", displayed: false)
|
|
||||||
resultMap.name = "Aux1"
|
|
||||||
resultMap.value = "on"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[21, 0]": // 0x15 Aux1 Off command verified
|
|
||||||
log.info "verified Aux1 Off"
|
|
||||||
sendEvent(name: "switch", value: "off", displayed: false)
|
|
||||||
resultMap.name = "Aux1"
|
|
||||||
resultMap.value = "off"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[22, 0]": // 0x16 Aux2 On command verified
|
|
||||||
log.info "verified Aux2 On"
|
|
||||||
resultMap.name = "Aux2"
|
|
||||||
resultMap.value = "on"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[23, 0]": // 0x17 Aux2 Off command verified
|
|
||||||
log.info "verified Aux2 Off"
|
|
||||||
resultMap.name = "Aux2"
|
|
||||||
resultMap.value = "off"
|
|
||||||
break
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseReportAttributeMessage(String description) {
|
|
||||||
Map resultMap = [:]
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
//log.debug "read attr descMap --> $descMap"
|
|
||||||
if (descMap.cluster == "0101" && descMap.attrId == "0003") {
|
|
||||||
resultMap.name = "doorState"
|
|
||||||
if (descMap.value == "00"){
|
|
||||||
resultMap.value = "unknown"
|
|
||||||
sendEvent(name: "door", value: "unknown", displayed: false)
|
|
||||||
}else if(descMap.value == "01"){
|
|
||||||
resultMap.value = "closed"
|
|
||||||
sendEvent(name: "door", value: "closed", displayed: false)
|
|
||||||
}else if(descMap.value == "02"){
|
|
||||||
resultMap.value = "open"
|
|
||||||
sendEvent(name: "door", value: "open", displayed: false)
|
|
||||||
}else if(descMap.value == "03"){
|
|
||||||
resultMap.value = "jammed"
|
|
||||||
}else if(descMap.value == "04"){
|
|
||||||
resultMap.value = "forced close"
|
|
||||||
}else if(descMap.value == "05"){
|
|
||||||
resultMap.value = "forced close"
|
|
||||||
}else if(descMap.value == "06"){
|
|
||||||
resultMap.value = "closing"
|
|
||||||
sendEvent(name: "door", value: "closing", displayed: false)
|
|
||||||
}else if(descMap.value == "07"){
|
|
||||||
resultMap.value = "opening"
|
|
||||||
sendEvent(name: "door", value: "opening", displayed: false)
|
|
||||||
}else if(descMap.value == "08"){
|
|
||||||
resultMap.value = "fault"
|
|
||||||
}else {
|
|
||||||
resultMap.value = "unknown"
|
|
||||||
}
|
|
||||||
resultMap.descriptionText = "Door State Changed to ${resultMap.value}"
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0400") {
|
|
||||||
resultMap.name = "currentLightLevel"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
resultMap.displayed = false
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0401") {
|
|
||||||
resultMap.name = "closeLightLevel"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0402") {
|
|
||||||
resultMap.name = "openLightLevel"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0403") {
|
|
||||||
resultMap.name = "autoCloseEnable"
|
|
||||||
if (descMap.value == "01"){resultMap.value = "on"}
|
|
||||||
else{resultMap.value = "off"}
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0404") {
|
|
||||||
resultMap.name = "autoOpenEnable"
|
|
||||||
if (descMap.value == "01"){resultMap.value = "on"}
|
|
||||||
else{resultMap.value = "off"}
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0405") {
|
|
||||||
resultMap.name = "doorCurrent"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
resultMap.value = resultMap.value * 0.001
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0408") {
|
|
||||||
resultMap.name = "doorSensitivity"
|
|
||||||
resultMap.value = (100 - Integer.parseInt(descMap.value, 16))
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0409") {
|
|
||||||
resultMap.name = "baseDoorCurrent"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
resultMap.value = resultMap.value * 0.001
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040a") {
|
|
||||||
resultMap.name = "doorVoltage"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
resultMap.value = resultMap.value * 0.001
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040b") {
|
|
||||||
resultMap.name = "Aux1"
|
|
||||||
if(descMap.value == "01"){
|
|
||||||
resultMap.value = "on"
|
|
||||||
sendEvent(name: "switch", value: "on", displayed: false)
|
|
||||||
}else{
|
|
||||||
resultMap.value = "off"
|
|
||||||
sendEvent(name: "switch", value: "off", displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040c") {
|
|
||||||
resultMap.name = "Aux2"
|
|
||||||
if(descMap.value == "01"){
|
|
||||||
resultMap.value = "on"
|
|
||||||
}else{
|
|
||||||
resultMap.value = "off"
|
|
||||||
}
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040d") {
|
|
||||||
resultMap.name = "photoCalibration"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040e") {
|
|
||||||
resultMap.name = "baseCurrentNE"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
}
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseCustomMessage(String description) {
|
|
||||||
//log.info "ParseCustomMessage called with ${description}"
|
|
||||||
Map resultMap = [:]
|
|
||||||
if (description?.startsWith('temperature: ')) {
|
|
||||||
resultMap.name = "temperature"
|
|
||||||
def rawT = (description - "temperature: ").trim()
|
|
||||||
resultMap.descriptionText = "Temperature celsius value = ${rawT}"
|
|
||||||
def rawTint = Float.parseFloat(rawT)
|
|
||||||
if (rawTint > 65){
|
|
||||||
resultMap.name = null
|
|
||||||
resultMap.value = null
|
|
||||||
resultMap.descriptionText = "Temperature celsius value = ${rawT} is invalid not updating"
|
|
||||||
log.warn "Invalid temperature value detected! rawT = ${rawT}, description = ${description}"
|
|
||||||
}else if (rawT == -32768){ // This number is used to indicate an error in the temperature reading
|
|
||||||
resultMap.value = "ERR"
|
|
||||||
}else{
|
|
||||||
resultMap.value = celsiusToFahrenheit(rawT.toFloat()) as Float
|
|
||||||
sendEvent(name: "TempProb1", value: resultMap.value, displayed: false) // Workaround for lack of access to endpoint information for Temperature report
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resultMap.displayed = false
|
|
||||||
log.info "Temperature reported = ${resultMap.value}"
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Added for Temeperature parse
|
|
||||||
def getFahrenheit(value) {
|
|
||||||
def celsius = Integer.parseInt(value, 16)
|
|
||||||
return celsiusToFahrenheit(celsius) as Integer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private methods
|
|
||||||
def callUpdateStatusTxt(){
|
|
||||||
def cTemp = device.currentState("TempProb1")?.value
|
|
||||||
def cLight = 0
|
|
||||||
def testNull = device.currentState("currentLightLevel")?.value
|
|
||||||
if (testNull != null){
|
|
||||||
cLight = device.currentState("currentLightLevel")?.value as int
|
|
||||||
}
|
|
||||||
updateStatusTxt(cTemp, cLight)
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateStatusTxt(currentTemp, currentLight){
|
|
||||||
//log.info "called updateStatusTxt with ${currentTemp}, ${currentLight}"
|
|
||||||
def cTmp = currentTemp
|
|
||||||
def cLL = 10
|
|
||||||
def oLL = 10
|
|
||||||
|
|
||||||
def testNull = device.currentState("closeLightLevel")?.value
|
|
||||||
if (testNull != null){
|
|
||||||
cLL = device.currentState("closeLightLevel")?.value as int
|
|
||||||
}
|
|
||||||
|
|
||||||
testNull = device.currentState("openLightLevel")?.value
|
|
||||||
if (testNull != null){
|
|
||||||
oLL = device.currentState("openLightLevel")?.value as int
|
|
||||||
}
|
|
||||||
|
|
||||||
def aOpnEn = device.currentState("autoOpenEnable")?.value
|
|
||||||
def aClsEn = device.currentState("autoCloseEnable")?.value
|
|
||||||
|
|
||||||
if (currentLight < cLL){
|
|
||||||
if (aOpnEn == "on"){
|
|
||||||
sendEvent(name: "dayOrNight", value: "Sun must be > ${oLL} to auto open", displayed: false)
|
|
||||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} open at ${oLL}. Coop ${cTmp}°", displayed: false)
|
|
||||||
}else{
|
|
||||||
sendEvent(name: "dayOrNight", value: "Auto Open is turned off.", displayed: false)
|
|
||||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} auto open off. Coop ${cTmp}°", displayed: false)
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
if (aClsEn == "on"){
|
|
||||||
sendEvent(name: "dayOrNight", value: "Sun must be < ${cLL} to auto close", displayed: false)
|
|
||||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} close at ${cLL}. Coop ${cTmp}°", displayed: false)
|
|
||||||
}else{
|
|
||||||
sendEvent(name: "dayOrNight", value: "Auto Close is turned off.", displayed: false)
|
|
||||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} auto close off. Coop ${cTmp}°", displayed: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands to device
|
|
||||||
def on() {
|
|
||||||
log.debug "on calling Aux1On"
|
|
||||||
Aux1On()
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "off calling Aux1Off"
|
|
||||||
Aux1Off()
|
|
||||||
}
|
|
||||||
|
|
||||||
def close() {
|
|
||||||
log.debug "close calling closeDoor"
|
|
||||||
closeDoor()
|
|
||||||
}
|
|
||||||
|
|
||||||
def open() {
|
|
||||||
log.debug "open calling openDoor"
|
|
||||||
openDoor()
|
|
||||||
}
|
|
||||||
|
|
||||||
def Aux1On(){
|
|
||||||
log.debug "Sending Aux1 = on command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x14 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def Aux1Off(){
|
|
||||||
log.debug "Sending Aux1 = off command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x15 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def Aux2On(){
|
|
||||||
log.debug "Sending Aux2 = on command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x16 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def Aux2Off(){
|
|
||||||
log.debug "Sending Aux2 = off command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x17 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def openDoor() {
|
|
||||||
log.debug "Sending Open command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x1 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def closeDoor() {
|
|
||||||
log.debug "Sending Close command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def closeDoorHiI() {
|
|
||||||
log.debug "Sending High Current Close command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x4 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def autoOpenOn() {
|
|
||||||
log.debug "Setting Auto Open On"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0C {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def autoOpenOff() {
|
|
||||||
log.debug "Setting Auto Open Off"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0D {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def autoCloseOn() {
|
|
||||||
log.debug "Setting Auto Close On"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0A {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def autoCloseOff() {
|
|
||||||
log.debug "Setting Auto Close Off"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0B {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def setOpenLevelTo(cValue) {
|
|
||||||
def cX = cValue
|
|
||||||
log.debug "Setting Open Light Level to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x402 0x23 {${Integer.toHexString(cX)}}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x402" // Read light value
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setCloseLevelTo(cValue) {
|
|
||||||
def cX = cValue
|
|
||||||
log.debug "Setting Close Light Level to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x401 0x23 {${Integer.toHexString(cX)}}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x401" // Read light value
|
|
||||||
cmd
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def setSensitivityLevel(cValue) {
|
|
||||||
def cX = 100 - cValue
|
|
||||||
log.debug "Setting Door sensitivity level to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x408 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit integer value.
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x408" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setNewBaseCurrent(cValue) {
|
|
||||||
def cX = cValue as int
|
|
||||||
log.info "Setting new BaseCurrent to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit integer value.
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setNewPhotoCalibration(cValue) {
|
|
||||||
def cX = cValue as int
|
|
||||||
log.info "Setting new Photoresister calibration to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D 0x2B {${Integer.toHexString(cX)}}" // Write attribute. 0x2B is a 32 bit signed integer value.
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def readNewPhotoCalibration() {
|
|
||||||
log.info "Requesting current Photoresister calibration "
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def readBaseCurrentNE() {
|
|
||||||
log.info "Requesting base current never exceed setting "
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setBaseCurrentNE(cValue) {
|
|
||||||
def cX = cValue as int
|
|
||||||
log.info "Setting new base Current Never Exceed to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit unsigned integer value.
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll(){
|
|
||||||
log.debug "Polling Device"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0003" // Read Door State
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read probe 1 Temperature
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read probe 2 Temperature
|
|
||||||
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateTemp1() {
|
|
||||||
log.debug "Sending attribute read request for Temperature Probe1"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read Current Temperature from Coop Probe 1
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateTemp2() {
|
|
||||||
log.debug "Sending attribute read request for Temperature Probe2"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read Current Temperature from Coop Probe 2
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def updateSun() {
|
|
||||||
log.debug "Sending attribute read request for Sun Light Level"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateSensitivity() {
|
|
||||||
log.debug "Sending attribute read request for door sensitivity"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0408" // Read Door sensitivity
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateCloseLightLevel() {
|
|
||||||
log.debug "Sending attribute read close light level"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0401"
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateOpenLightLevel() {
|
|
||||||
log.debug "Sending attribute read open light level"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0402"
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "sending refresh command"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0003" // Read Door State
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0401" // Read Door Close Light Level
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0402" // Read Door Open Light Level
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0403" // Read Auto Door Close Settings
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0404" // Read Auto Door Open Settings
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read Current Temperature from Coop Probe 1
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0408" // Object detection sensitivity
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read Current Temperature from Coop Probe 2
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0405" // Current required to close door
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x040B" // Aux1 Status
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x040C" // Aux2 Status
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409" // Read Base current
|
|
||||||
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Binding SEP 0x38 DEP 0x01 Cluster 0x0101 Lock cluster to hub"
|
|
||||||
log.debug "Binding SEP 0x39 DEP 0x01 Cluster 0x0402 Temperature cluster to hub"
|
|
||||||
log.debug "Binding SEP 0x40 DEP 0x01 Cluster 0x0402 Temperature cluster to hub"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "zdo bind 0x${device.deviceNetworkId} 0x38 0x01 0x0101 {${device.zigbeeId}} {}" // Bind to end point 0x38 and the lock cluster
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "zdo bind 0x${device.deviceNetworkId} 0x39 0x01 0x0402 {${device.zigbeeId}} {}" // Bind to end point 0x39 and the temperature cluster
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "zdo bind 0x${device.deviceNetworkId} 0x40 0x01 0x0402 {${device.zigbeeId}} {}" // Bind to end point 0x40 and the temperature cluster
|
|
||||||
cmd << "delay 1500"
|
|
||||||
|
|
||||||
log.info "Sending ZigBee Configuration Commands to Coop Control"
|
|
||||||
return cmd + refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -22,14 +22,12 @@ metadata {
|
|||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
attribute "tamper", "enum", ["detected", "clear"]
|
attribute "tamper", "enum", ["detected", "clear"]
|
||||||
attribute "batteryStatus", "string"
|
attribute "batteryStatus", "string"
|
||||||
attribute "powerSupply", "enum", ["USB Cable", "Battery"]
|
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"
|
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
|
||||||
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A,0x5A"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -328,9 +326,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// allow device user configured or default 16 min to check in; double the periodic reporting interval
|
|
||||||
sendEvent(name: "checkInterval", value: 2* (timeOptionValueMap[reportInterval] ?: (2*8*60)), displayed: false)
|
|
||||||
|
|
||||||
// This sensor joins as a secure device if you double-click the button to include it
|
// This sensor joins as a secure device if you double-click the button to include it
|
||||||
log.debug "${device.displayName} is configuring its settings"
|
log.debug "${device.displayName} is configuring its settings"
|
||||||
def request = []
|
def request = []
|
||||||
@@ -357,7 +352,7 @@ def configure() {
|
|||||||
motionSensitivity == "minimum" ? 0 : 64)
|
motionSensitivity == "minimum" ? 0 : 64)
|
||||||
|
|
||||||
//5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
|
//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: 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
|
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ metadata {
|
|||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "configureAfterSecure"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
|
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
|
||||||
}
|
}
|
||||||
@@ -248,8 +245,6 @@ def configureAfterSecure() {
|
|||||||
def configure() {
|
def configure() {
|
||||||
// log.debug "configure()"
|
// log.debug "configure()"
|
||||||
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
|
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
|
||||||
// allow device 16 min to check in; double the periodic reporting interval
|
|
||||||
sendEvent(name: "checkInterval", value: 2*8*60, displayed: false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setConfigured() {
|
private setConfigured() {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ metadata {
|
|||||||
capability "Illuminance Measurement"
|
capability "Illuminance Measurement"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86"
|
fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86"
|
||||||
}
|
}
|
||||||
@@ -181,9 +180,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// allow device 10 min to check in; double the periodic reporting interval
|
|
||||||
sendEvent(name: "checkInterval", value: 2*5*60, displayed: false)
|
|
||||||
|
|
||||||
delayBetween([
|
delayBetween([
|
||||||
// send binary sensor report instead of basic set for motion
|
// send binary sensor report instead of basic set for motion
|
||||||
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(),
|
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(),
|
||||||
|
|||||||
@@ -12,6 +12,16 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Purpose: Arrival Sensor HA DTH File
|
||||||
|
*
|
||||||
|
* Filename: Arrival-Sensor-HA.src/Arrival-Sensor-HA.groovy
|
||||||
|
*
|
||||||
|
* Change History:
|
||||||
|
* 1. 20160115 TW - Update/Edit to support i18n translations
|
||||||
|
* 2. 20160121 TW - Update to V4 battery calcs, added pref's page title translations
|
||||||
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Tone"
|
capability "Tone"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#==============================================================================
|
||||||
# Copyright 2016 SmartThings
|
# Copyright 2016 SmartThings
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -11,16 +12,24 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
#==============================================================================
|
||||||
|
# Purpose: Arrival Sensor HA i18n Translation File
|
||||||
|
#
|
||||||
|
# Filename: Arrival-Sensor-HA.src/i18n/messages.properties
|
||||||
|
#
|
||||||
|
# Change History:
|
||||||
|
# 1. 20160115 TW Initial release with informal Korean translation.
|
||||||
|
# 2. 20160121 TW Added def preference section titles.
|
||||||
|
#==============================================================================
|
||||||
# Korean (ko)
|
# Korean (ko)
|
||||||
# Device Preferences
|
# Device Preferences
|
||||||
'''Give your device a name'''.ko=기기 이름 설정
|
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||||
'''Set Device Image'''.ko=기기 이미지 설정
|
'''Set Device Image'''.ko=기기 이미지 설정
|
||||||
'''Presence timeout (minutes)'''.ko=알람 유예 시간 설정 (분)
|
'''Presence timeout (minutes)'''.ko=시간 초과. 스마트폰 위치 정보
|
||||||
'''Tap to set'''.ko=눌러서 설정
|
'''Tap to set'''.ko=눌러서 설정
|
||||||
'''Arrival Sensor'''.ko=도착알림 센서
|
'''Arrival Sensor'''.ko=도착알림 센서
|
||||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
|
||||||
# Events / Notifications
|
# Events / Notifications
|
||||||
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}의 남은 배터리 {{ value }}
|
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}남아있는 배터리는 {{ value }}입니다.
|
||||||
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
|
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
|
||||||
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
|
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다.
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -64,10 +64,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
||||||
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
|
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff"
|
||||||
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
|
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821"
|
||||||
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
|
|
||||||
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
|
|
||||||
}
|
}
|
||||||
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
|
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
|
||||||
state "station1", label:'${currentValue}', action:"preset1"
|
state "station1", label:'${currentValue}', action:"preset1"
|
||||||
@@ -749,16 +747,8 @@ def cb_boseSetInput(xml, input) {
|
|||||||
*/
|
*/
|
||||||
def boseSetPowerState(boolean enable) {
|
def boseSetPowerState(boolean enable) {
|
||||||
log.info "boseSetPowerState(${enable})"
|
log.info "boseSetPowerState(${enable})"
|
||||||
// Fix to get faster update of power status back from speaker after sending on/off
|
queueCallback('nowPlaying', "cb_boseSetPowerState", enable ? "POWERON" : "POWEROFF")
|
||||||
// Instead of queuing the command to be sent after the refresh send it directly via sendHubCommand
|
return boseRefreshNowPlaying()
|
||||||
// Note: This is a temporary hack that should be replaced by a re-design of the
|
|
||||||
// DTH to use sendHubCommand for all commands
|
|
||||||
sendHubCommand(bosePOST("/key", "<key state=\"press\" sender=\"Gabbo\">POWER</key>"))
|
|
||||||
sendHubCommand(bosePOST("/key", "<key state=\"release\" sender=\"Gabbo\">POWER</key>"))
|
|
||||||
sendHubCommand(boseGET("/now_playing"))
|
|
||||||
if (enable) {
|
|
||||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", 5)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -797,11 +787,10 @@ def cb_boseSetPowerState(xml, state) {
|
|||||||
*/
|
*/
|
||||||
def cb_boseConfirmPowerOn(xml, tries) {
|
def cb_boseConfirmPowerOn(xml, tries) {
|
||||||
def result = []
|
def result = []
|
||||||
def attempt = tries as Integer
|
log.warn "boseConfirmPowerOn() attempt #" + tries
|
||||||
log.warn "boseConfirmPowerOn() attempt #$attempt"
|
if (xml.attributes()['source'] == "STANDBY" && tries > 0) {
|
||||||
if (xml.attributes()['source'] == "STANDBY" && attempt > 0) {
|
|
||||||
result << boseRefreshNowPlaying()
|
result << boseRefreshNowPlaying()
|
||||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", attempt-1)
|
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", tries-1)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ metadata {
|
|||||||
|
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
@@ -89,10 +88,6 @@ def refresh() {
|
|||||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
def poll() {
|
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "Z-Wave Wall Dimmer"
|
fingerprint inClusters: "0x26"
|
||||||
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "Z-Wave Wall Dimmer"
|
|
||||||
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "Z-Wave Plug-In Dimmer"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -44,10 +42,6 @@ metadata {
|
|||||||
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
|
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
|
||||||
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
@@ -76,28 +70,11 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main(["switch"])
|
main(["switch"])
|
||||||
details(["switch", "level", "refresh"])
|
details(["switch", "level", "indicator", "refresh"])
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated(){
|
|
||||||
switch (ledIndicator) {
|
|
||||||
case "on":
|
|
||||||
indicatorWhenOn()
|
|
||||||
break
|
|
||||||
case "off":
|
|
||||||
indicatorWhenOff()
|
|
||||||
break
|
|
||||||
case "never":
|
|
||||||
indicatorNever()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
indicatorWhenOn()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
if (description != "updated") {
|
if (description != "updated") {
|
||||||
@@ -161,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])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,19 +201,19 @@ def refresh() {
|
|||||||
delayBetween(commands,100)
|
delayBetween(commands,100)
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOn() {
|
def indicatorWhenOn() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
sendEvent(name: "indicatorStatus", value: "when on")
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOff() {
|
def indicatorWhenOff() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
sendEvent(name: "indicatorStatus", value: "when off")
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorNever() {
|
def indicatorNever() {
|
||||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
sendEvent(name: "indicatorStatus", value: "never")
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
def invertSwitch(invert=true) {
|
def invertSwitch(invert=true) {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ metadata {
|
|||||||
attribute "tamper", "enum", ["detected", "clear"]
|
attribute "tamper", "enum", ["detected", "clear"]
|
||||||
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
|
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
|
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
|
||||||
fingerprint mfr:"010F", prod:"0C02", model:"1002"
|
|
||||||
}
|
}
|
||||||
simulator {
|
simulator {
|
||||||
//battery
|
//battery
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* GE Link Bulb
|
* GE Link Bulb
|
||||||
*
|
*
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2014 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* 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:
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
@@ -53,8 +53,6 @@ metadata {
|
|||||||
capability "Polling"
|
capability "Polling"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE", model: "SoftWhite", deviceJoinName: "GE Link Soft White Bulb"
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE", model: "Daylight", deviceJoinName: "GE Link Daylight Bulb"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
/**
|
|
||||||
* Hue Bloom
|
|
||||||
*
|
|
||||||
* Philips Hue Type "Color Light"
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
|
||||||
*/
|
|
||||||
|
|
||||||
// for the UI
|
|
||||||
metadata {
|
|
||||||
// Automatically generated. Make future change here.
|
|
||||||
definition (name: "Hue Bloom", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Color Control"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "setAdjustedColor"
|
|
||||||
command "reset"
|
|
||||||
command "refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles (scale: 2){
|
|
||||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
|
||||||
attributeState "color", action:"setAdjustedColor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["rich-control"])
|
|
||||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(description) {
|
|
||||||
log.debug "parse() - $description"
|
|
||||||
def results = []
|
|
||||||
|
|
||||||
def map = description
|
|
||||||
if (description instanceof String) {
|
|
||||||
log.debug "Hue Bulb stringToMap - ${map}"
|
|
||||||
map = stringToMap(description)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map?.name && map?.value) {
|
|
||||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle commands
|
|
||||||
void on() {
|
|
||||||
log.trace parent.on(this)
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
void off() {
|
|
||||||
log.trace parent.off(this)
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
}
|
|
||||||
|
|
||||||
void nextLevel() {
|
|
||||||
def level = device.latestValue("level") as Integer ?: 0
|
|
||||||
if (level <= 100) {
|
|
||||||
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
level = 25
|
|
||||||
}
|
|
||||||
setLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLevel(percent) {
|
|
||||||
log.debug "Executing 'setLevel'"
|
|
||||||
if (verifyPercent(percent)) {
|
|
||||||
parent.setLevel(this, percent)
|
|
||||||
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSaturation(percent) {
|
|
||||||
log.debug "Executing 'setSaturation'"
|
|
||||||
if (verifyPercent(percent)) {
|
|
||||||
parent.setSaturation(this, percent)
|
|
||||||
sendEvent(name: "saturation", value: percent, displayed: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setHue(percent) {
|
|
||||||
log.debug "Executing 'setHue'"
|
|
||||||
if (verifyPercent(percent)) {
|
|
||||||
parent.setHue(this, percent)
|
|
||||||
sendEvent(name: "hue", value: percent, displayed: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setColor(value) {
|
|
||||||
log.debug "setColor: ${value}, $this"
|
|
||||||
def events = []
|
|
||||||
def validValues = [:]
|
|
||||||
|
|
||||||
if (verifyPercent(value.hue)) {
|
|
||||||
events << createEvent(name: "hue", value: value.hue, displayed: false)
|
|
||||||
validValues.hue = value.hue
|
|
||||||
}
|
|
||||||
if (verifyPercent(value.saturation)) {
|
|
||||||
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
|
|
||||||
validValues.saturation = value.saturation
|
|
||||||
}
|
|
||||||
if (value.hex != null) {
|
|
||||||
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
|
|
||||||
events << createEvent(name: "color", value: value.hex)
|
|
||||||
validValues.hex = value.hex
|
|
||||||
} else {
|
|
||||||
log.warn "$value.hex is not a valid color"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (verifyPercent(value.level)) {
|
|
||||||
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
|
|
||||||
validValues.level = value.level
|
|
||||||
}
|
|
||||||
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
|
|
||||||
events << createEvent(name: "switch", value: "off")
|
|
||||||
validValues.switch = "off"
|
|
||||||
} else {
|
|
||||||
events << createEvent(name: "switch", value: "on")
|
|
||||||
validValues.switch = "on"
|
|
||||||
}
|
|
||||||
if (!events.isEmpty()) {
|
|
||||||
parent.setColor(this, validValues)
|
|
||||||
}
|
|
||||||
events.each {
|
|
||||||
sendEvent(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
log.debug "Executing 'reset'"
|
|
||||||
def value = [level:100, saturation:56, hue:23]
|
|
||||||
setAdjustedColor(value)
|
|
||||||
parent.poll()
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAdjustedColor(value) {
|
|
||||||
if (value) {
|
|
||||||
log.trace "setAdjustedColor: ${value}"
|
|
||||||
def adjusted = value + [:]
|
|
||||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
|
||||||
// Needed because color picker always sends 100
|
|
||||||
adjusted.level = null
|
|
||||||
setColor(adjusted)
|
|
||||||
} else {
|
|
||||||
log.warn "Invalid color input"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setColorTemperature(value) {
|
|
||||||
if (value) {
|
|
||||||
log.trace "setColorTemperature: ${value}k"
|
|
||||||
parent.setColorTemperature(this, value)
|
|
||||||
sendEvent(name: "colorTemperature", value: value)
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
} else {
|
|
||||||
log.warn "Invalid color temperature"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void refresh() {
|
|
||||||
log.debug "Executing 'refresh'"
|
|
||||||
parent.manualRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def adjustOutgoingHue(percent) {
|
|
||||||
def adjusted = percent
|
|
||||||
if (percent > 31) {
|
|
||||||
if (percent < 63.0) {
|
|
||||||
adjusted = percent + (7 * (percent -30 ) / 32)
|
|
||||||
}
|
|
||||||
else if (percent < 73.0) {
|
|
||||||
adjusted = 69 + (5 * (percent - 62) / 10)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
adjusted = percent + (2 * (100 - percent) / 28)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info "percent: $percent, adjusted: $adjusted"
|
|
||||||
adjusted
|
|
||||||
}
|
|
||||||
|
|
||||||
def verifyPercent(percent) {
|
|
||||||
if (percent == null)
|
|
||||||
return false
|
|
||||||
else if (percent >= 0 && percent <= 100) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
log.warn "$percent is not 0-100"
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,13 +17,16 @@ metadata {
|
|||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"rich-control"){
|
multiAttributeTile(name:"rich-control"){
|
||||||
tileAttribute ("", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
|
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
|
||||||
}
|
}
|
||||||
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
|
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
|
||||||
attributeState "default", label:'SN: ${currentValue}'
|
attributeState "default", label:'SN: ${currentValue}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
standardTile("icon", "icon", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
|
||||||
|
state "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#FFFFFF"
|
||||||
|
}
|
||||||
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
|
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
|
||||||
state "default", label:'SN: ${currentValue}'
|
state "default", label:'SN: ${currentValue}'
|
||||||
}
|
}
|
||||||
@@ -31,7 +34,7 @@ metadata {
|
|||||||
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
|
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
|
||||||
}
|
}
|
||||||
|
|
||||||
main (["rich-control"])
|
main (["icon"])
|
||||||
details(["rich-control", "networkAddress"])
|
details(["rich-control", "networkAddress"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +75,6 @@ def parse(description) {
|
|||||||
}
|
}
|
||||||
else if (contentType?.contains("xml")) {
|
else if (contentType?.contains("xml")) {
|
||||||
log.debug "HUE BRIDGE ALREADY PRESENT"
|
log.debug "HUE BRIDGE ALREADY PRESENT"
|
||||||
parent.hubVerification(device.hub.id, msg.body)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Hue Bulb
|
* Hue Bulb
|
||||||
*
|
*
|
||||||
* Philips Hue Type "Extended Color Light"
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -30,14 +28,17 @@ metadata {
|
|||||||
tiles (scale: 2){
|
tiles (scale: 2){
|
||||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||||
}
|
}
|
||||||
|
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||||
|
attributeState "level", label: 'Level ${currentValue}%'
|
||||||
|
}
|
||||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||||
attributeState "color", action:"setAdjustedColor"
|
attributeState "color", action:"setAdjustedColor"
|
||||||
}
|
}
|
||||||
@@ -68,13 +69,11 @@ metadata {
|
|||||||
def parse(description) {
|
def parse(description) {
|
||||||
log.debug "parse() - $description"
|
log.debug "parse() - $description"
|
||||||
def results = []
|
def results = []
|
||||||
|
|
||||||
def map = description
|
def map = description
|
||||||
if (description instanceof String) {
|
if (description instanceof String) {
|
||||||
log.debug "Hue Bulb stringToMap - ${map}"
|
log.debug "Hue Bulb stringToMap - ${map}"
|
||||||
map = stringToMap(description)
|
map = stringToMap(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map?.name && map?.value) {
|
if (map?.name && map?.value) {
|
||||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
||||||
}
|
}
|
||||||
@@ -104,104 +103,62 @@ void nextLevel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setLevel(percent) {
|
void setLevel(percent) {
|
||||||
log.debug "Executing 'setLevel'"
|
log.debug "Executing 'setLevel'"
|
||||||
if (verifyPercent(percent)) {
|
parent.setLevel(this, percent)
|
||||||
parent.setLevel(this, percent)
|
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
|
||||||
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaturation(percent) {
|
void setSaturation(percent) {
|
||||||
log.debug "Executing 'setSaturation'"
|
log.debug "Executing 'setSaturation'"
|
||||||
if (verifyPercent(percent)) {
|
parent.setSaturation(this, percent)
|
||||||
parent.setSaturation(this, percent)
|
sendEvent(name: "saturation", value: percent, displayed: false)
|
||||||
sendEvent(name: "saturation", value: percent, displayed: false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setHue(percent) {
|
void setHue(percent) {
|
||||||
log.debug "Executing 'setHue'"
|
log.debug "Executing 'setHue'"
|
||||||
if (verifyPercent(percent)) {
|
parent.setHue(this, percent)
|
||||||
parent.setHue(this, percent)
|
sendEvent(name: "hue", value: percent, displayed: false)
|
||||||
sendEvent(name: "hue", value: percent, displayed: false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setColor(value) {
|
void setColor(value) {
|
||||||
log.debug "setColor: ${value}, $this"
|
log.debug "setColor: ${value}, $this"
|
||||||
def events = []
|
parent.setColor(this, value)
|
||||||
def validValues = [:]
|
if (value.hue) { sendEvent(name: "hue", value: value.hue, displayed: false)}
|
||||||
|
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation, displayed: false)}
|
||||||
if (verifyPercent(value.hue)) {
|
if (value.hex) { sendEvent(name: "color", value: value.hex)}
|
||||||
events << createEvent(name: "hue", value: value.hue, displayed: false)
|
if (value.level) { sendEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")}
|
||||||
validValues.hue = value.hue
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
|
||||||
if (verifyPercent(value.saturation)) {
|
|
||||||
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
|
|
||||||
validValues.saturation = value.saturation
|
|
||||||
}
|
|
||||||
if (value.hex != null) {
|
|
||||||
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
|
|
||||||
events << createEvent(name: "color", value: value.hex)
|
|
||||||
validValues.hex = value.hex
|
|
||||||
} else {
|
|
||||||
log.warn "$value.hex is not a valid color"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (verifyPercent(value.level)) {
|
|
||||||
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
|
|
||||||
validValues.level = value.level
|
|
||||||
}
|
|
||||||
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
|
|
||||||
events << createEvent(name: "switch", value: "off")
|
|
||||||
validValues.switch = "off"
|
|
||||||
} else {
|
|
||||||
events << createEvent(name: "switch", value: "on")
|
|
||||||
validValues.switch = "on"
|
|
||||||
}
|
|
||||||
if (!events.isEmpty()) {
|
|
||||||
parent.setColor(this, validValues)
|
|
||||||
}
|
|
||||||
events.each {
|
|
||||||
sendEvent(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
log.debug "Executing 'reset'"
|
log.debug "Executing 'reset'"
|
||||||
def value = [level:100, saturation:56, hue:23]
|
def value = [level:100, saturation:56, hue:23]
|
||||||
setAdjustedColor(value)
|
setAdjustedColor(value)
|
||||||
parent.poll()
|
parent.poll()
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAdjustedColor(value) {
|
void setAdjustedColor(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
log.trace "setAdjustedColor: ${value}"
|
log.trace "setAdjustedColor: ${value}"
|
||||||
def adjusted = value + [:]
|
def adjusted = value + [:]
|
||||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
adjusted.hue = adjustOutgoingHue(value.hue)
|
||||||
// Needed because color picker always sends 100
|
// Needed because color picker always sends 100
|
||||||
adjusted.level = null
|
adjusted.level = null
|
||||||
setColor(adjusted)
|
setColor(adjusted)
|
||||||
} else {
|
|
||||||
log.warn "Invalid color input"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setColorTemperature(value) {
|
void setColorTemperature(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
log.trace "setColorTemperature: ${value}k"
|
log.trace "setColorTemperature: ${value}k"
|
||||||
parent.setColorTemperature(this, value)
|
parent.setColorTemperature(this, value)
|
||||||
sendEvent(name: "colorTemperature", value: value)
|
sendEvent(name: "colorTemperature", value: value)
|
||||||
sendEvent(name: "switch", value: "on")
|
}
|
||||||
} else {
|
|
||||||
log.warn "Invalid color temperature"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh() {
|
void refresh() {
|
||||||
log.debug "Executing 'refresh'"
|
log.debug "Executing 'refresh'"
|
||||||
parent.manualRefresh()
|
parent.manualRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
def adjustOutgoingHue(percent) {
|
def adjustOutgoingHue(percent) {
|
||||||
@@ -220,14 +177,3 @@ def adjustOutgoingHue(percent) {
|
|||||||
log.info "percent: $percent, adjusted: $adjusted"
|
log.info "percent: $percent, adjusted: $adjusted"
|
||||||
adjusted
|
adjusted
|
||||||
}
|
}
|
||||||
|
|
||||||
def verifyPercent(percent) {
|
|
||||||
if (percent == null)
|
|
||||||
return false
|
|
||||||
else if (percent >= 0 && percent <= 100) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
log.warn "$percent is not 0-100"
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Hue Lux Bulb
|
* Hue Lux Bulb
|
||||||
*
|
*
|
||||||
* Philips Hue Type "Dimmable Light"
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
*/
|
*/
|
||||||
// for the UI
|
// for the UI
|
||||||
@@ -33,6 +31,9 @@ metadata {
|
|||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||||
}
|
}
|
||||||
|
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||||
|
attributeState "level", label: 'Level ${currentValue}%'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||||
@@ -67,24 +68,19 @@ def parse(description) {
|
|||||||
|
|
||||||
// handle commands
|
// handle commands
|
||||||
void on() {
|
void on() {
|
||||||
log.trace parent.on(this)
|
parent.on(this)
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
}
|
||||||
|
|
||||||
void off() {
|
void off() {
|
||||||
log.trace parent.off(this)
|
parent.off(this)
|
||||||
sendEvent(name: "switch", value: "off")
|
sendEvent(name: "switch", value: "off")
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLevel(percent) {
|
void setLevel(percent) {
|
||||||
log.debug "Executing 'setLevel'"
|
log.debug "Executing 'setLevel'"
|
||||||
if (percent != null && percent >= 0 && percent <= 100) {
|
parent.setLevel(this, percent)
|
||||||
parent.setLevel(this, percent)
|
sendEvent(name: "level", value: percent)
|
||||||
sendEvent(name: "level", value: percent)
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
} else {
|
|
||||||
log.warn "$percent is not 0-100"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh() {
|
void refresh() {
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* Hue White Ambiance Bulb
|
|
||||||
*
|
|
||||||
* Philips Hue Type "Color Temperature Light"
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
|
||||||
*/
|
|
||||||
|
|
||||||
// for the UI
|
|
||||||
metadata {
|
|
||||||
// Automatically generated. Make future change here.
|
|
||||||
definition (name: "Hue White Ambiance Bulb", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Color Temperature"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Refresh"
|
|
||||||
|
|
||||||
command "refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles (scale: 2){
|
|
||||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
|
|
||||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "colorTemperature", label: '${currentValue} K'
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["rich-control"])
|
|
||||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(description) {
|
|
||||||
log.debug "parse() - $description"
|
|
||||||
def results = []
|
|
||||||
|
|
||||||
def map = description
|
|
||||||
if (description instanceof String) {
|
|
||||||
log.debug "Hue Ambience Bulb stringToMap - ${map}"
|
|
||||||
map = stringToMap(description)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map?.name && map?.value) {
|
|
||||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle commands
|
|
||||||
void on() {
|
|
||||||
log.trace parent.on(this)
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
void off() {
|
|
||||||
log.trace parent.off(this)
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLevel(percent) {
|
|
||||||
log.debug "Executing 'setLevel'"
|
|
||||||
if (percent != null && percent >= 0 && percent <= 100) {
|
|
||||||
parent.setLevel(this, percent)
|
|
||||||
sendEvent(name: "level", value: percent)
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
} else {
|
|
||||||
log.warn "$percent is not 0-100"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setColorTemperature(value) {
|
|
||||||
if (value) {
|
|
||||||
log.trace "setColorTemperature: ${value}k"
|
|
||||||
parent.setColorTemperature(this, value)
|
|
||||||
sendEvent(name: "colorTemperature", value: value)
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
} else {
|
|
||||||
log.warn "Invalid color temperature"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void refresh() {
|
|
||||||
log.debug "Executing 'refresh'"
|
|
||||||
parent.manualRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#==============================================================================
|
||||||
# Copyright 2016 SmartThings
|
# Copyright 2016 SmartThings
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -11,12 +12,21 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
#==============================================================================
|
||||||
|
# Purpose: Mobile Presence i18n Translation File
|
||||||
|
#
|
||||||
|
# Filename: mobile-presence.src/i18n/messages.properties
|
||||||
|
#
|
||||||
|
# Change History:
|
||||||
|
# 1. 20160205 TW Initial release with informal Korean translation.
|
||||||
|
# 2. 20160224 TW Updated with formal Korean translation.
|
||||||
|
#==============================================================================
|
||||||
# Korean (ko)
|
# Korean (ko)
|
||||||
# Device Preferences
|
# Device Preferences
|
||||||
'''Give your device a name'''.ko=기기 이름 설정
|
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||||
'''Set Device Image'''.ko=기기 이미지 설정
|
'''Set Device Image'''.ko=기기 이미지 설정
|
||||||
# Events / Notifications
|
# Events / Notifications
|
||||||
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
|
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다.
|
||||||
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
|
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
|
||||||
'''present'''.ko=집안
|
'''present'''.ko=집안
|
||||||
'''not present'''.ko=외출
|
'''not present'''.ko=외출
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
===============================================================================
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2016 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -12,6 +13,14 @@
|
|||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
|
===============================================================================
|
||||||
|
* Purpose: Mobile Presence DTH File
|
||||||
|
*
|
||||||
|
* Filename: mobile-presence.src/mobile-presence.groovy
|
||||||
|
*
|
||||||
|
* Change History:
|
||||||
|
* 1. 20160205 TW - Update/Edit to support i18n translations
|
||||||
|
===============================================================================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
@@ -144,14 +143,51 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
|
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
Map resultMap = [:]
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
result.name = 'motion'
|
Map resultMap = [:]
|
||||||
result.value = zs.isAlarm2Set() ? 'active' : 'inactive'
|
switch(msgCode) {
|
||||||
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
|
case '0x0030': // Closed/No Motion/Dry
|
||||||
|
log.debug 'no motion'
|
||||||
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'inactive'
|
||||||
|
break
|
||||||
|
|
||||||
return resultMap
|
case '0x0032': // Open/Motion/Wet
|
||||||
|
log.debug 'motion'
|
||||||
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'active'
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0032': // Tamper Alarm
|
||||||
|
log.debug 'motion with tamper alarm'
|
||||||
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'active'
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0033': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0034': // Supervision Report
|
||||||
|
log.debug 'no motion with tamper alarm'
|
||||||
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'inactive'
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0035': // Restore Report
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0036': // Trouble/Failure
|
||||||
|
log.debug 'motion with failure alarm'
|
||||||
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'active'
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0038': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh()
|
def refresh()
|
||||||
|
|||||||
@@ -14,9 +14,6 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
|
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
@@ -27,7 +24,6 @@ metadata {
|
|||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
|
|
||||||
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"
|
|
||||||
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
|
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
|
||||||
@@ -222,33 +218,40 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List parseIasMessage(String description) {
|
private List parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(" ")
|
||||||
log.debug "parseIasMessage: $description"
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
List resultListMap = []
|
List resultListMap = []
|
||||||
Map resultMap_battery = [:]
|
Map resultMap_battery = [:]
|
||||||
Map resultMap_battery_state = [:]
|
Map resultMap_battery_state = [:]
|
||||||
Map resultMap_sensor = [:]
|
Map resultMap_sensor = [:]
|
||||||
|
|
||||||
resultMap_sensor.name = "contact"
|
// Relevant bit field definitions from ZigBee spec
|
||||||
resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed"
|
def BATTERY_BIT = ( 1 << 3 )
|
||||||
|
def TROUBLE_BIT = ( 1 << 6 )
|
||||||
|
def SENSOR_BIT = ( 1 << 0 ) // it's ALARM1 bit from the ZCL spec
|
||||||
|
|
||||||
|
// Convert hex string to integer
|
||||||
|
def zoneStatus = Integer.parseInt(msgCode[-4..-1],16)
|
||||||
|
|
||||||
|
log.debug "parseIasMessage: zoneStatus: ${zoneStatus}"
|
||||||
|
|
||||||
// Check each relevant bit, create map for it, and add to list
|
// Check each relevant bit, create map for it, and add to list
|
||||||
log.debug "parseIasMessage: Battery Status ${zs.battery}"
|
log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}"
|
||||||
log.debug "parseIasMessage: Trouble Status ${zs.trouble}"
|
log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}"
|
||||||
log.debug "parseIasMessage: Sensor Status ${zs.alarm1}"
|
log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}"
|
||||||
|
|
||||||
/* Comment out this path to check the battery state to avoid overwriting the
|
/* Comment out this path to check the battery state to avoid overwriting the
|
||||||
battery value (Change log #2), but keep these conditions for later use
|
battery value (Change log #2), but keep these conditions for later use
|
||||||
resultMap_battery_state.name = "battery_state"
|
resultMap_battery_state.name = "battery_state"
|
||||||
if (zs.isTroubleSet()) {
|
if (zoneStatus & TROUBLE_BIT) {
|
||||||
resultMap_battery_state.value = "failed"
|
resultMap_battery_state.value = "failed"
|
||||||
|
|
||||||
resultMap_battery.name = "battery"
|
resultMap_battery.name = "battery"
|
||||||
resultMap_battery.value = 0
|
resultMap_battery.value = 0
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (zs.isBatterySet()) {
|
if (zoneStatus & BATTERY_BIT) {
|
||||||
resultMap_battery_state.value = "low"
|
resultMap_battery_state.value = "low"
|
||||||
|
|
||||||
// to generate low battery notification by the platform
|
// to generate low battery notification by the platform
|
||||||
@@ -266,6 +269,9 @@ private List parseIasMessage(String description) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
resultMap_sensor.name = "contact"
|
||||||
|
resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed"
|
||||||
|
|
||||||
resultListMap << resultMap_battery_state
|
resultListMap << resultMap_battery_state
|
||||||
resultListMap << resultMap_battery
|
resultListMap << resultMap_battery
|
||||||
resultListMap << resultMap_sensor
|
resultListMap << resultMap_sensor
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#==============================================================================
|
||||||
# Copyright 2016 SmartThings
|
# Copyright 2016 SmartThings
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -11,16 +12,20 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
#==============================================================================
|
||||||
|
# Purpose: SmartPower Outlet i18n Translation File
|
||||||
|
#
|
||||||
|
# Filename: SmartPower-Outlet.src/i18n/messages.properties
|
||||||
|
#
|
||||||
|
# Change History:
|
||||||
|
# 1. 20160116 TW Initial release with informal Korean translation.
|
||||||
|
#==============================================================================
|
||||||
# Korean (ko)
|
# Korean (ko)
|
||||||
# Device Preferences
|
# Device Preferences
|
||||||
'''Give your device a name'''.ko=기기 이름 설정
|
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||||
'''Outlet'''.ko= 스마트 플러그
|
'''Outlet'''.ko=플러그
|
||||||
# Events descriptionText
|
# Events descriptionText
|
||||||
'''{{ device.displayName }} is On'''.ko={{ device.displayName }} 켜짐
|
'''{{ device.displayName }} is On'''.ko={{ device.displayName }}켜졌습니다.
|
||||||
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }} 꺼짐
|
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }}꺼졌습니다.
|
||||||
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전력은 {{ value }}와트입니다.
|
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전원은 {{ value }}와트입니다
|
||||||
'''On'''.ko= 켜짐
|
|
||||||
'''Off'''.ko=꺼짐
|
|
||||||
'''Turning On'''.ko=켜는 중
|
|
||||||
'''Turning Off'''.ko=끄는 중
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
===============================================================================
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2016 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -12,18 +13,27 @@
|
|||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
|
===============================================================================
|
||||||
|
* Purpose: SmartPower Outlet DTH File
|
||||||
|
*
|
||||||
|
* Filename: SmartPower-Outlet.src/SmartPower-Outlet.groovy
|
||||||
|
*
|
||||||
|
* Change History:
|
||||||
|
* 1. 20160117 TW - Update/Edit to support i18n translations
|
||||||
|
===============================================================================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
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"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
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"
|
||||||
@@ -55,10 +65,10 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||||
attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||||
attributeState "turningOn", label: 'Turning On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||||
attributeState "turningOff", label: 'Turning Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
||||||
attributeState "power", label:'${currentValue} W'
|
attributeState "power", label:'${currentValue} W'
|
||||||
@@ -78,6 +88,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
|
||||||
@@ -101,12 +114,6 @@ def parse(String description) {
|
|||||||
else {
|
else {
|
||||||
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
|
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
|
||||||
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
|
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
|
||||||
// Temporary fix for the case when Device is OFFLINE and is connected again
|
|
||||||
if (state.lastOnOff == null){
|
|
||||||
state.lastOnOff = now()
|
|
||||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true)
|
|
||||||
}
|
|
||||||
state.lastOnOff = now()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -122,28 +129,13 @@ def off() {
|
|||||||
def on() {
|
def on() {
|
||||||
zigbee.on()
|
zigbee.on()
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Outlet
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
|
|
||||||
// send read attribute onOFF if the last time we heard from the outlet is outside of the checkInterval
|
|
||||||
if (state.lastOnOff < (now() - (1000 * device.currentValue("checkInterval"))) ){
|
|
||||||
log.info "ping, alive=no, lastOnOff=${new Date(state.lastOnOff)}"
|
|
||||||
state.lastOnOff = null
|
|
||||||
return zigbee.onOffRefresh()
|
|
||||||
} else { // if the last onOff activity is within the checkInterval we artificially create a Device-Watch event
|
|
||||||
log.info "ping, alive=yes, lastOnOff=${new Date(state.lastOnOff)}"
|
|
||||||
sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh()
|
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||||
|
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 1200, displayed: false)
|
|
||||||
zigbee.onOffConfig() + powerConfig() + refresh()
|
zigbee.onOffConfig() + powerConfig() + refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#==============================================================================
|
||||||
# Copyright 2016 SmartThings
|
# Copyright 2016 SmartThings
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -11,10 +12,16 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
#==============================================================================
|
||||||
|
# Purpose: SmartSense Moisture Sensor i18n Translation File
|
||||||
|
#
|
||||||
|
# Filename: SmartSense-Moisture-Sensor.src/i18n/messages.properties
|
||||||
|
#
|
||||||
|
# Change History:
|
||||||
|
# 1. 20160116 TW Initial release with formal Korean translation.
|
||||||
|
#==============================================================================
|
||||||
# Korean (ko)
|
# Korean (ko)
|
||||||
# Device Preferences
|
# Device Preferences
|
||||||
'''Dry'''.ko=건조
|
|
||||||
'''Wet'''.ko=누수
|
|
||||||
'''dry'''.ko=건조
|
'''dry'''.ko=건조
|
||||||
'''wet'''.ko=누수
|
'''wet'''.ko=누수
|
||||||
'''battery'''.ko=배터리
|
'''battery'''.ko=배터리
|
||||||
@@ -22,14 +29,13 @@
|
|||||||
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||||
'''Degrees'''.ko=온도
|
'''Degrees'''.ko=온도
|
||||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||||
'''Give your device a name'''.ko=기기 이름 설정
|
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||||
'''Water Leak Sensor'''.ko=누수감지 센서
|
'''Water Leak Sensor'''.ko=누수센서
|
||||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
|
||||||
# Events descriptionText
|
# Events descriptionText
|
||||||
'''{{ device.displayName }} is dry'''.ko={{ device.displayName }}가 건조
|
'''{{ device.displayName }} is dry'''.ko={{ device.displayName }}가 건조
|
||||||
'''{{ device.displayName }} is wet'''.ko={{ device.displayName }}누수
|
'''{{ device.displayName }} is wet'''.ko={{ device.displayName }}누수
|
||||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
|
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
|
||||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
||||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
|
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
===============================================================================
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2016 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -12,19 +13,24 @@
|
|||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
|
===============================================================================
|
||||||
|
* Purpose: SmartSense Moisture Sensor DTH File
|
||||||
|
*
|
||||||
|
* Filename: SmartSense-Moisture-Sensor.src/SmartSense-Moisture-Sensor.groovy
|
||||||
|
*
|
||||||
|
* Change History:
|
||||||
|
* 1. 20160116 TW - Update/Edit to support i18n translations
|
||||||
|
* 2. 20160125 TW = Incorporated new battery mapping from TM
|
||||||
|
===============================================================================
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
|
|
||||||
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"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Water Sensor"
|
capability "Water Sensor"
|
||||||
capability "Health Check"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
@@ -172,9 +178,42 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getMoistureResult('dry')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getMoistureResult('wet')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
log.debug 'dry with tamper alarm'
|
||||||
|
resultMap = getMoistureResult('dry')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
log.debug 'water with tamper alarm'
|
||||||
|
resultMap = getMoistureResult('wet')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
@@ -225,8 +264,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,8 +320,6 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
|
||||||
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
def configCmds = [
|
def configCmds = [
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Smartsense Motion Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartThings Motion Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-motion-sensor)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health]($health)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Motion Sensor** - can detect motion
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 motion sensor that has 120min check-in interval
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#==============================================================================
|
||||||
# Copyright 2016 SmartThings
|
# Copyright 2016 SmartThings
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -11,6 +12,15 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
#==============================================================================
|
||||||
|
# Purpose: SmartSense Motion Sensor i18n Translation File
|
||||||
|
#
|
||||||
|
# Filename: SmartSense-Motion-Sensor.src/i18n/messages.properties
|
||||||
|
#
|
||||||
|
# Change History:
|
||||||
|
# 1. 20160116 TW Initial release with formal Korean translation.
|
||||||
|
# 2. 20160224 TW Updated formal Korean translations from Mike Stoller.
|
||||||
|
#==============================================================================
|
||||||
# Korean (ko)
|
# Korean (ko)
|
||||||
# Device Preferences
|
# Device Preferences
|
||||||
'''battery'''.ko=배터리
|
'''battery'''.ko=배터리
|
||||||
@@ -18,16 +28,13 @@
|
|||||||
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||||
'''Degrees'''.ko=온도
|
'''Degrees'''.ko=온도
|
||||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||||
'''Give your device a name'''.ko=기기 이름 설정
|
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||||
'''Motion Sensor'''.ko=모션 센서
|
'''Motion Sensor'''.ko=모션 센서
|
||||||
'''motion'''.ko= 동작 감지
|
|
||||||
'''no motion'''.ko=동작 없음
|
|
||||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
|
||||||
# Events descriptionText
|
# Events descriptionText
|
||||||
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }}에서 움직임이 감지되었습니다.
|
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }} 가 움직임을 감지하였습니다.
|
||||||
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }}에서 움직임이 중단되었습니다.
|
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }}움직임이 중단되었습니다
|
||||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
|
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가){{ value }}°C였습니다.
|
||||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
||||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
|
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
===============================================================================
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2016 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -12,19 +13,24 @@
|
|||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
|
===============================================================================
|
||||||
|
* Purpose: SmartSense Motion Sensor DTH File
|
||||||
|
*
|
||||||
|
* Filename: SmartSense-Motion-Sensor.src/SmartSense-Motion-Sensor.groovy
|
||||||
|
*
|
||||||
|
* Change History:
|
||||||
|
* 1. 20160116 TW - Update/Edit to support i18n translations
|
||||||
|
* 2. 20160125 TW = Incorporated new battery mapping from TM
|
||||||
|
===============================================================================
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
|
|
||||||
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"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Health Check"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
@@ -185,10 +191,44 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
|
Map resultMap = [:]
|
||||||
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getMotionResult('inactive')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
log.debug 'motion with tamper alarm'
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
log.debug 'no motion with tamper alarm'
|
||||||
|
resultMap = getMotionResult('inactive')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
log.debug 'motion with failure alarm'
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
@@ -240,8 +280,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,8 +332,6 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
|
||||||
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
@@ -169,8 +168,44 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive')
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getMotionResult('inactive')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
log.debug 'motion with tamper alarm'
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
log.debug 'no motion with tamper alarm'
|
||||||
|
resultMap = getMotionResult('inactive')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
log.debug 'motion with failure alarm'
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
@@ -205,8 +240,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#==============================================================================
|
||||||
# Copyright 2016 SmartThings
|
# Copyright 2016 SmartThings
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -11,6 +12,14 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
#==============================================================================
|
||||||
|
# Purpose: SmartSense Multi Sensor i18n Translation File
|
||||||
|
#
|
||||||
|
# Filename: SmartSense-Multi-Sensor.src/i18n/messages.properties
|
||||||
|
#
|
||||||
|
# Change History:
|
||||||
|
# 1. 20160117 TW Initial release with informal Korean translation.
|
||||||
|
#==============================================================================
|
||||||
# Korean (ko)
|
# Korean (ko)
|
||||||
# Device Preferences
|
# Device Preferences
|
||||||
'''Yes'''.ko=예
|
'''Yes'''.ko=예
|
||||||
@@ -22,20 +31,15 @@
|
|||||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||||
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
|
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
|
||||||
'''Tap to set'''.ko=눌러서 설정
|
'''Tap to set'''.ko=눌러서 설정
|
||||||
'''Give your device a name'''.ko=기기 이름 설정
|
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||||
'''Multipurpose Sensor'''.ko=문 및 창 센서
|
'''Multipurpose Sensor'''.ko=멀티 센서
|
||||||
# Events descriptionText
|
# Events descriptionText
|
||||||
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}에서 열림이 감지되었습니다.
|
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}열림을 감지하였습니다.
|
||||||
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}에서 닫힘이 감지되었습니다.
|
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}닫혔습니다.
|
||||||
'''{{ device.displayName }} was active'''.ko={{ device.displayName }} 활성화
|
'''{{ device.displayName }} was active'''.ko={{ device.displayName }}활성화되었습니다.
|
||||||
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }} 비활성화
|
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }}비활성화되었습니다.
|
||||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
|
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가){{ value }}°C였습니다.
|
||||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
|
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
||||||
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
|
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
|
||||||
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
|
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
|
||||||
'''Inactive'''.ko=비활성 상태
|
|
||||||
'''Active'''.ko=활성 상태
|
|
||||||
'''Open'''.ko= 열림이 감지될 때
|
|
||||||
'''Closed'''.ko=닫힘
|
|
||||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
===============================================================================
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2016 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -12,11 +13,19 @@
|
|||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
* License for the specific language governing permissions and limitations
|
* License for the specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
|
===============================================================================
|
||||||
|
* Purpose: SmartSense Multi Sensor DTH File
|
||||||
|
*
|
||||||
|
* Filename: SmartSense-Multi-Sensor.src/SmartSense-Multi-Sensor.groovy
|
||||||
|
*
|
||||||
|
* Change History:
|
||||||
|
* 1. 20160117 TW - Update/Edit to support i18n translations
|
||||||
|
* 2. 20160125 TW = Incorporated new battery mapping from TM
|
||||||
|
===============================================================================
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
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"
|
||||||
@@ -26,7 +35,6 @@ metadata {
|
|||||||
capability "Acceleration Sensor"
|
capability "Acceleration Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||||
@@ -75,19 +83,19 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
||||||
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||||
attributeState "closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||||
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
|
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
|
||||||
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||||
state("open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||||
state("closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||||
}
|
}
|
||||||
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
||||||
state("active", label:'Active', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
||||||
state("inactive", label:'Inactive', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
state("inactive", label:'${name}', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
||||||
}
|
}
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||||
state("temperature", label:'${currentValue}°',
|
state("temperature", label:'${currentValue}°',
|
||||||
@@ -225,13 +233,47 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
if (garageSensor != "Yes"){
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
if(garageSensor != "Yes") {
|
case '0x0021': // Open/Motion/Wet
|
||||||
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
if (garageSensor != "Yes"){
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
if (garageSensor != "Yes"){
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
if (garageSensor != "Yes"){
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultMap
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,8 +347,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,37 +412,76 @@ def refresh() {
|
|||||||
|
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||||
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
|
refreshCmds = refreshCmds + [
|
||||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||||
as of now.
|
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||||
*/
|
as of now.
|
||||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
|
*/
|
||||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||||
|
]
|
||||||
} else {
|
} else {
|
||||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
refreshCmds = refreshCmds + [
|
||||||
|
/* sensitivity - default value (8) */
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
//Common refresh commands
|
//Common refresh commands
|
||||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
refreshCmds = refreshCmds + [
|
||||||
zigbee.readAttribute(0x0001, 0x0020) +
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global read 0xFC02 0x0010",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||||
|
]
|
||||||
|
|
||||||
return refreshCmds + enrollResponse()
|
return refreshCmds + enrollResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
|
|
||||||
log.debug "Configuring Reporting"
|
log.debug "Configuring Reporting"
|
||||||
|
|
||||||
def configCmds = enrollResponse() +
|
def configCmds = [
|
||||||
zigbee.batteryConfig() +
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
zigbee.temperatureConfig() +
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
|
||||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
||||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||||
|
]
|
||||||
|
|
||||||
return configCmds + refresh()
|
return configCmds + refresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ private List parseContactMessage(String description) {
|
|||||||
parts.each { part ->
|
parts.each { part ->
|
||||||
part = part.trim()
|
part = part.trim()
|
||||||
if (part.startsWith('contactState:')) {
|
if (part.startsWith('contactState:')) {
|
||||||
results.addAll(getContactResult(part, description))
|
results << getContactResult(part, description)
|
||||||
}
|
}
|
||||||
else if (part.startsWith('accelerationState:')) {
|
else if (part.startsWith('accelerationState:')) {
|
||||||
results << getAccelerationResult(part, description)
|
results << getAccelerationResult(part, description)
|
||||||
@@ -316,7 +316,7 @@ private List getContactResult(part, description) {
|
|||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getAccelerationResult(part, description) {
|
private getAccelerationResult(part, description) {
|
||||||
def name = "acceleration"
|
def name = "acceleration"
|
||||||
def value = part.endsWith("1") ? "active" : "inactive"
|
def value = part.endsWith("1") ? "active" : "inactive"
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
@@ -335,7 +335,7 @@ private Map getAccelerationResult(part, description) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getTempResult(part, description) {
|
private getTempResult(part, description) {
|
||||||
def name = "temperature"
|
def name = "temperature"
|
||||||
def temperatureScale = getTemperatureScale()
|
def temperatureScale = getTemperatureScale()
|
||||||
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
|
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
|
||||||
@@ -360,7 +360,7 @@ private Map getTempResult(part, description) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getXyzResult(results, description) {
|
private getXyzResult(results, description) {
|
||||||
def name = "threeAxis"
|
def name = "threeAxis"
|
||||||
def value = "${results.x},${results.y},${results.z}"
|
def value = "${results.x},${results.y},${results.z}"
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
@@ -379,7 +379,7 @@ private Map getXyzResult(results, description) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getBatteryResult(part, description) {
|
private getBatteryResult(part, description) {
|
||||||
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
|
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
|
||||||
def name = "battery"
|
def name = "battery"
|
||||||
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
|
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
|
||||||
@@ -400,7 +400,7 @@ private Map getBatteryResult(part, description) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getRssiResult(part, description, lastHop=false) {
|
private getRssiResult(part, description, lastHop=false) {
|
||||||
def name = lastHop ? "lastHopRssi" : "rssi"
|
def name = lastHop ? "lastHopRssi" : "rssi"
|
||||||
def valueString = part.split(":")[1].trim()
|
def valueString = part.split(":")[1].trim()
|
||||||
def value = (Integer.parseInt(valueString) - 128).toString()
|
def value = (Integer.parseInt(valueString) - 128).toString()
|
||||||
@@ -431,7 +431,7 @@ private Map getRssiResult(part, description, lastHop=false) {
|
|||||||
* Note: To make the signal strength indicator more accurate, we could combine
|
* Note: To make the signal strength indicator more accurate, we could combine
|
||||||
* LQI with RSSI.
|
* LQI with RSSI.
|
||||||
*/
|
*/
|
||||||
private Map getLqiResult(part, description, lastHop=false) {
|
private getLqiResult(part, description, lastHop=false) {
|
||||||
def name = lastHop ? "lastHopLqi" : "lqi"
|
def name = lastHop ? "lastHopLqi" : "lqi"
|
||||||
def valueString = part.split(":")[1].trim()
|
def valueString = part.split(":")[1].trim()
|
||||||
def percentageOf = 255
|
def percentageOf = 255
|
||||||
|
|||||||
@@ -14,20 +14,17 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
//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
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
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"
|
||||||
capability "Acceleration Sensor"
|
capability "Acceleration Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Health Check"
|
command "enrollResponse"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -173,9 +170,40 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
@@ -205,8 +233,7 @@ def getTemperature(value) {
|
|||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +299,6 @@ def getTemperature(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
|
||||||
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
|||||||
@@ -13,17 +13,14 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Contact Sensor"
|
capability "Contact Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Health Check"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
@@ -168,8 +165,40 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
@@ -199,8 +228,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +273,6 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
|
||||||
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
|||||||
@@ -14,14 +14,12 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
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"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Health Check"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
|
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
|
||||||
}
|
}
|
||||||
@@ -206,8 +204,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +251,6 @@ def refresh()
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
|
||||||
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
def configCmds = [
|
def configCmds = [
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
metadata {
|
metadata {
|
||||||
definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Alarm"
|
capability "Alarm"
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
metadata {
|
metadata {
|
||||||
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Color Control"
|
capability "Color Control"
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Contact Sensor"
|
capability "Contact Sensor"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "open"
|
command "open"
|
||||||
command "close"
|
command "close"
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Lock"
|
capability "Lock"
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulated lock
|
// Simulated lock
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Motion Sensor"
|
capability "Motion Sensor"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "active"
|
command "active"
|
||||||
command "inactive"
|
command "inactive"
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Presence Sensor"
|
capability "Presence Sensor"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "arrived"
|
command "arrived"
|
||||||
command "departed"
|
command "departed"
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ metadata {
|
|||||||
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Relay Switch"
|
capability "Relay Switch"
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
|
|
||||||
command "onPhysical"
|
command "onPhysical"
|
||||||
command "offPhysical"
|
command "offPhysical"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ metadata {
|
|||||||
definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "up"
|
command "up"
|
||||||
command "down"
|
command "down"
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ metadata {
|
|||||||
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Thermostat"
|
capability "Thermostat"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
|
|
||||||
command "tempUp"
|
command "tempUp"
|
||||||
command "tempDown"
|
command "tempDown"
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Water Sensor"
|
capability "Water Sensor"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "wet"
|
command "wet"
|
||||||
command "dry"
|
command "dry"
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
# Device Tiles Examples and Reference
|
|
||||||
|
|
||||||
This package contains examples of Device tiles, organized by tile type.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Each Device Handler shows example usages of a specific tile, and is meant to represent the variety of permutations that a tile can be configured.
|
|
||||||
|
|
||||||
The various tiles can be used by QA to test tiles on all supported mobile devices, and by developers as a reference implementation.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Self-publish the Device Handlers in this package.
|
|
||||||
2. Self-publish the Device Tile Controller SmartApp. The SmartApp can be found [here](https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/master/smartapps/smartthings/tile-ux/device-tile-controller.src/device-tile-controller.groovy).
|
|
||||||
3. Install the SmartApp from the Marketplace, under "My Apps".
|
|
||||||
4. Select the simulated devices you want to install and press "Done".
|
|
||||||
|
|
||||||
The simulated devices can then be found in the "Things" view of "My Home" in the mobile app.
|
|
||||||
You may wish to create a new room for these simulated devices for easy access.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Each simulated device can be interacted with like other devices.
|
|
||||||
You can use the mobile app to interact with the tiles to see how they look and behave.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If you get an error when installing the simulated devices using the controller SmartApp, ensure that you have published all the Device Handlers for yourself.
|
|
||||||
Also check live logging to see if there is a specific tile that is causing installation issues.
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
*Question: A tile isn't behaving as expected. What should I do?*
|
|
||||||
|
|
||||||
QA should create a JIRA ticket for any issues or inconsistencies of tiles across devices.
|
|
||||||
|
|
||||||
Developers may file a support ticket, and reference the specific tile and issue observed.
|
|
||||||
|
|
||||||
*Question: I'd like to contribute an example tile usage that would be helpful for testing and reference purposes. Can I do that?*
|
|
||||||
|
|
||||||
We recommend that you open an issue in the SmartThingsPublic repository describing the example tile and usage.
|
|
||||||
That way we can discuss with you the proposed change, and then if appropriate you can create a PR associated to the issue.
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "carouselDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Thermostat"
|
|
||||||
capability "Relative Humidity Measurement"
|
|
||||||
|
|
||||||
command "tempUp"
|
|
||||||
command "tempDown"
|
|
||||||
command "heatUp"
|
|
||||||
command "heatDown"
|
|
||||||
command "coolUp"
|
|
||||||
command "coolDown"
|
|
||||||
command "setTemperature", ["number"]
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
|
|
||||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
|
||||||
attributeState("default", action: "setTemperature")
|
|
||||||
}
|
|
||||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState("default", label:'${currentValue}%', unit:"%")
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
|
||||||
attributeState("idle", backgroundColor:"#44b621")
|
|
||||||
attributeState("heating", backgroundColor:"#ffa81e")
|
|
||||||
attributeState("cooling", backgroundColor:"#269bd2")
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
|
||||||
attributeState("off", label:'${name}')
|
|
||||||
attributeState("heat", label:'${name}')
|
|
||||||
attributeState("cool", label:'${name}')
|
|
||||||
attributeState("auto", label:'${name}')
|
|
||||||
}
|
|
||||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main("thermostatMulti")
|
|
||||||
details([
|
|
||||||
"thermostatMulti"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
sendEvent(name: "temperature", value: 72, unit: "F")
|
|
||||||
sendEvent(name: "heatingSetpoint", value: 70, unit: "F")
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: 70, unit: "F")
|
|
||||||
sendEvent(name: "coolingSetpoint", value: 76, unit: "F")
|
|
||||||
sendEvent(name: "thermostatMode", value: "off")
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
|
||||||
sendEvent(name: "humidity", value: 53, unit: "%")
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
}
|
|
||||||
|
|
||||||
def evaluate(temp, heatingSetpoint, coolingSetpoint) {
|
|
||||||
log.debug "evaluate($temp, $heatingSetpoint, $coolingSetpoint"
|
|
||||||
def threshold = 1.0
|
|
||||||
def current = device.currentValue("thermostatOperatingState")
|
|
||||||
def mode = device.currentValue("thermostatMode")
|
|
||||||
|
|
||||||
def heating = false
|
|
||||||
def cooling = false
|
|
||||||
def idle = false
|
|
||||||
if (mode in ["heat","emergency heat","auto"]) {
|
|
||||||
if (heatingSetpoint - temp >= threshold) {
|
|
||||||
heating = true
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "heating")
|
|
||||||
}
|
|
||||||
else if (temp - heatingSetpoint >= threshold) {
|
|
||||||
idle = true
|
|
||||||
}
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
|
||||||
}
|
|
||||||
if (mode in ["cool","auto"]) {
|
|
||||||
if (temp - coolingSetpoint >= threshold) {
|
|
||||||
cooling = true
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "cooling")
|
|
||||||
}
|
|
||||||
else if (coolingSetpoint - temp >= threshold && !heating) {
|
|
||||||
idle = true
|
|
||||||
}
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: coolingSetpoint)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
|
||||||
}
|
|
||||||
if (idle && !heating && !cooling) {
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setHeatingSetpoint(Double degreesF) {
|
|
||||||
log.debug "setHeatingSetpoint($degreesF)"
|
|
||||||
sendEvent(name: "heatingSetpoint", value: degreesF)
|
|
||||||
evaluate(device.currentValue("temperature"), degreesF, device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def setCoolingSetpoint(Double degreesF) {
|
|
||||||
log.debug "setCoolingSetpoint($degreesF)"
|
|
||||||
sendEvent(name: "coolingSetpoint", value: degreesF)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), degreesF)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setThermostatMode(String value) {
|
|
||||||
sendEvent(name: "thermostatMode", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def setThermostatFanMode(String value) {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "off")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def heat() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "heat")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def auto() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "auto")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def emergencyHeat() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "emergency heat")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def cool() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "cool")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def fanOn() {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanOn")
|
|
||||||
}
|
|
||||||
|
|
||||||
def fanAuto() {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
|
||||||
}
|
|
||||||
|
|
||||||
def fanCirculate() {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanCirculate")
|
|
||||||
}
|
|
||||||
|
|
||||||
def tempUp() {
|
|
||||||
def ts = device.currentState("temperature")
|
|
||||||
def value = ts ? ts.integerValue + 1 : 72
|
|
||||||
sendEvent(name:"temperature", value: value)
|
|
||||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def tempDown() {
|
|
||||||
def ts = device.currentState("temperature")
|
|
||||||
def value = ts ? ts.integerValue - 1 : 72
|
|
||||||
sendEvent(name:"temperature", value: value)
|
|
||||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def setTemperature(value) {
|
|
||||||
def ts = device.currentState("temperature")
|
|
||||||
sendEvent(name:"temperature", value: value)
|
|
||||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def heatUp() {
|
|
||||||
def ts = device.currentState("heatingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue + 1 : 68
|
|
||||||
sendEvent(name:"heatingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def heatDown() {
|
|
||||||
def ts = device.currentState("heatingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue - 1 : 68
|
|
||||||
sendEvent(name:"heatingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def coolUp() {
|
|
||||||
def ts = device.currentState("coolingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue + 1 : 76
|
|
||||||
sendEvent(name:"coolingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def coolDown() {
|
|
||||||
def ts = device.currentState("coolingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue - 1 : 76
|
|
||||||
sendEvent(name:"coolingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "colorWheelDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Color Control"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
valueTile("currentColor", "device.color") {
|
|
||||||
state "color", label: '${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) {
|
|
||||||
state "color", action: "color control.setColor"
|
|
||||||
}
|
|
||||||
|
|
||||||
main("currentColor")
|
|
||||||
details([
|
|
||||||
"rgbSelector"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String 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) {
|
|
||||||
log.debug "Executing 'setSaturation'"
|
|
||||||
sendEvent(name: "saturation", value: percent)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setHue(percent) {
|
|
||||||
log.debug "Executing 'setHue'"
|
|
||||||
sendEvent(name: "hue", value: percent)
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "presenceDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Presence Sensor"
|
|
||||||
|
|
||||||
command "arrived"
|
|
||||||
command "departed"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
// You only get a presence tile view when the size is 3x3 otherwise it's a value tile
|
|
||||||
standardTile("presence", "device.presence", width: 3, height: 3, canChangeBackground: true) {
|
|
||||||
state("present", labelIcon:"st.presence.tile.mobile-present", backgroundColor:"#53a7c0")
|
|
||||||
state("not present", labelIcon:"st.presence.tile.mobile-not-present", backgroundColor:"#ebeef2")
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("notPresentBtn", "device.fake", width: 3, height: 3, decoration: "flat") {
|
|
||||||
state("not present", label:'not present', backgroundColor:"#ffffff", action:"departed")
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("presentBtn", "device.fake", width: 3, height: 3, decoration: "flat") {
|
|
||||||
state("present", label:'present', backgroundColor:"#53a7c0", action:"arrived")
|
|
||||||
}
|
|
||||||
|
|
||||||
main("presence")
|
|
||||||
details([
|
|
||||||
"presence", "presenceControl", "notPresentBtn", "presentBtn"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
sendEvent(name: "presence", value: "present")
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
}
|
|
||||||
|
|
||||||
def arrived() {
|
|
||||||
log.trace "Executing 'arrived'"
|
|
||||||
sendEvent(name: "presence", value: "present")
|
|
||||||
}
|
|
||||||
|
|
||||||
def departed() {
|
|
||||||
log.trace "Executing 'arrived'"
|
|
||||||
sendEvent(name: "presence", value: "not present")
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "sliderDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Switch Level"
|
|
||||||
command "setRangedLevel", ["number"]
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
controlTile("tinySlider", "device.level", "slider", height: 2, width: 2, inactiveLabel: false) {
|
|
||||||
state "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("mediumSlider", "device.level", "slider", height: 2, width: 4, inactiveLabel: false) {
|
|
||||||
state "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("largeSlider", "device.level", "slider", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
|
||||||
state "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("rangeSlider", "device.rangedLevel", "slider", height: 2, width: 4, range: "(20..80)") {
|
|
||||||
state "level", action:"setRangedLevel"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) {
|
|
||||||
state "range", label:'${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") {
|
|
||||||
state "level", action:"setRangedLevel"
|
|
||||||
}
|
|
||||||
|
|
||||||
main("rangeValue")
|
|
||||||
details([
|
|
||||||
"tinySlider", "mediumSlider",
|
|
||||||
"largeSlider",
|
|
||||||
"rangeSlider", "rangeValue",
|
|
||||||
"rangeSliderConstrained"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
sendEvent(name: "level", value: 63)
|
|
||||||
sendEvent(name: "rangedLevel", value: 47)
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
log.debug "setting level to $value"
|
|
||||||
sendEvent(name:"level", value:value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setRangedLevel(value) {
|
|
||||||
log.debug "setting ranged level to $value"
|
|
||||||
sendEvent(name:"rangedLevel", value:value)
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "standardDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Switch"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
// standard tile with actions
|
|
||||||
standardTile("actionRings", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "off", label: '${currentValue}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
|
||||||
state "on", label: '${currentValue}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard flat tile with actions
|
|
||||||
standardTile("actionFlat", "device.switch", width: 2, height: 2, canChangeIcon: true, decoration: "flat") {
|
|
||||||
state "off", label: '${currentValue}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
|
||||||
state "on", label: '${currentValue}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard flat tile without actions
|
|
||||||
standardTile("noActionFlat", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "off", label: '${currentValue}',icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
|
||||||
state "on", label: '${currentValue}', icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard flat tile with only a label
|
|
||||||
standardTile("flatLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "label", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard flat tile with icon and label
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard flat tile with only icon (Refreh text is IN the icon file)
|
|
||||||
standardTile("flatIcon", "device.switch", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "icon", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard with defaultState = true
|
|
||||||
standardTile("flatDefaultState", "null", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "off", label: 'Fail!', icon: "st.switches.switch.off"
|
|
||||||
state "on", label: 'Pass!', icon: "st.switches.switch.on", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard with implicit defaultState based on order (0 index is selected)
|
|
||||||
standardTile("flatImplicitDefaultState1", "null", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "on", label: 'Pass!', icon: "st.switches.switch.on"
|
|
||||||
state "off", label: 'Fail!', icon: "st.switches.switch.off"
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard with implicit defaultState based on state.name == default
|
|
||||||
standardTile("flatImplicitDefaultState2", "null", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "off", label: 'Fail!', icon: "st.switches.switch.off"
|
|
||||||
state "default", label: 'Pass!', icon: "st.switches.switch.on"
|
|
||||||
}
|
|
||||||
|
|
||||||
// utility tiles to fill the spaces
|
|
||||||
standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "emptySmall", label:'', defaultState: true
|
|
||||||
}
|
|
||||||
standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") {
|
|
||||||
state "emptyBigger", label:'', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// multi-line text (explicit newlines)
|
|
||||||
standardTile("multiLine", "device.multiLine", width: 2, height: 2) {
|
|
||||||
state "multiLine", label: '${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("multiLineWithIcon", "device.multiLine", width: 2, height: 2) {
|
|
||||||
state "multiLineIcon", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
main("actionRings")
|
|
||||||
details([
|
|
||||||
"actionRings", "actionFlat", "noActionFlat",
|
|
||||||
|
|
||||||
"flatLabel", "flatIconLabel", "flatIcon",
|
|
||||||
|
|
||||||
"flatDefaultState", "flatImplicitDefaultState1", "flatImplicitDefaultState2",
|
|
||||||
|
|
||||||
"multiLine", "multiLineWithIcon"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
sendEvent(name: "multiLine", value: "Line 1\nLine 2\nLine 3")
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
log.debug "on()"
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "off()"
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "valueDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Sensor"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
valueTile("text", "device.text", width: 2, height: 2) {
|
|
||||||
state "val", label:'${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("longText", "device.longText", width: 2, height: 2) {
|
|
||||||
state "val", label:'${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("integer", "device.integer", width: 2, height: 2) {
|
|
||||||
state "val", label:'${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("integerFloat", "device.integerFloat", width: 2, height: 2) {
|
|
||||||
state "val", label:'${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("pi", "device.pi", width: 2, height: 2) {
|
|
||||||
state "val", label:'${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("floatAsText", "device.floatAsText", width: 2, height: 2) {
|
|
||||||
state "val", label:'${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("bgColor", "device.integer", width: 2, height: 2) {
|
|
||||||
state "val", label:'${currentValue}', backgroundColor: "#e86d13", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("bgColorRange", "device.integer", width: 2, height: 2) {
|
|
||||||
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
|
|
||||||
[value: 10, color: "#ff0000"],
|
|
||||||
[value: 90, color: "#0000ff"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) {
|
|
||||||
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
|
|
||||||
[value: 10, color: "#333333"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) {
|
|
||||||
state "valWithConflict", label:'${currentValue}', defaultState: true, backgroundColors: [
|
|
||||||
[value: 10, color: "#990000"],
|
|
||||||
[value: 10, color: "#000099"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("noValue", "device.nada", width: 4, height: 2) {
|
|
||||||
state "noval", label:'${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("multiLine", "device.multiLine", width: 3, height: 2) {
|
|
||||||
state "val", label: '${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("multiLineWithIcon", "device.multiLine", width: 3, height: 2) {
|
|
||||||
state "val", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
main("text")
|
|
||||||
details([
|
|
||||||
"text", "longText", "integer",
|
|
||||||
"integerFloat", "pi", "floatAsText",
|
|
||||||
"bgColor", "bgColorRange", "bgColorRangeSingleItem",
|
|
||||||
"bgColorRangeConflict", "noValue",
|
|
||||||
"multiLine", "multiLineWithIcon"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
sendEvent(name: "text", value: "Test")
|
|
||||||
sendEvent(name: "longText", value: "The Longer The Text, The Better The Test")
|
|
||||||
sendEvent(name: "integer", value: 47)
|
|
||||||
sendEvent(name: "integerFloat", value: 47.0)
|
|
||||||
sendEvent(name: "pi", value: 3.14159)
|
|
||||||
sendEvent(name: "floatAsText", value: "3.14159")
|
|
||||||
sendEvent(name: "multiLine", value: "Line 1\nLine 2\nLine 3")
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "genericDeviceTile", namespace: "smartthings/tile-ux", author: "SmartThings") {
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Switch Level"
|
|
||||||
|
|
||||||
command "levelUp"
|
|
||||||
command "levelDown"
|
|
||||||
command "randomizeLevel"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"basicTile", type:"generic", width:6, height:4) {
|
|
||||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
multiAttributeTile(name:"sliderTile", type:"generic", width:6, height:4) {
|
|
||||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "level", icon: 'st.Weather.weather1', action:"randomizeLevel", defaultState: true
|
|
||||||
}
|
|
||||||
tileAttribute("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel", defaultState: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) {
|
|
||||||
tileAttribute("device.level", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "level", label:'${currentValue}', defaultState: true, backgroundColors:[
|
|
||||||
[value: 0, color: "#ff0000"],
|
|
||||||
[value: 20, color: "#ffff00"],
|
|
||||||
[value: 40, color: "#00ff00"],
|
|
||||||
[value: 60, color: "#00ffff"],
|
|
||||||
[value: 80, color: "#0000ff"],
|
|
||||||
[value: 100, color: "#ff00ff"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
tileAttribute("device.switch", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'…', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute("device.level", key: "VALUE_CONTROL") {
|
|
||||||
attributeState "VALUE_UP", action: "levelUp"
|
|
||||||
attributeState "VALUE_DOWN", action: "levelDown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
multiAttributeTile(name:"multilineTile", type:"generic", width:6, height:4) {
|
|
||||||
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
|
|
||||||
}
|
|
||||||
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
multiAttributeTile(name:"lengthyTileWithIcon", type:"generic", width:6, height:4) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
multiAttributeTile(name:"multilineTileWithIcon", type:"generic", width:6, height:4) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["basicTile"])
|
|
||||||
details(["basicTile", "sliderTile", "valueTile", "lengthyTile", "multilineTile", "lengthyTileWithIcon", "multilineTileWithIcon"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
sendEvent(name: "lengthyText", value: "The value of this tile is long and should wrap to two lines")
|
|
||||||
sendEvent(name: "multilineText", value: "Line 1 YES\nLine 2 YES\nLine 3 NO")
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
// This is a simulated device. No incoming data to parse.
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
log.debug "turningOn"
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "turningOff"
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(percent) {
|
|
||||||
log.debug "setLevel: ${percent}, this"
|
|
||||||
sendEvent(name: "level", value: percent)
|
|
||||||
}
|
|
||||||
|
|
||||||
def randomizeLevel() {
|
|
||||||
def level = Math.round(Math.random() * 100)
|
|
||||||
setLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
def levelUp() {
|
|
||||||
def level = device.latestValue("level") as Integer ?: 0
|
|
||||||
if (level < 100) {
|
|
||||||
level = level + 1
|
|
||||||
}
|
|
||||||
setLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
def levelDown() {
|
|
||||||
def level = device.latestValue("level") as Integer ?: 0
|
|
||||||
if (level > 0) {
|
|
||||||
level = level - 1
|
|
||||||
}
|
|
||||||
setLevel(level)
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "lightingDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Color Control"
|
|
||||||
capability "Power Meter"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "setAdjustedColor"
|
|
||||||
command "reset"
|
|
||||||
command "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.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "power", label:'Power level: ${currentValue}W', icon: "st.Appliances.appliances17"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
|
||||||
attributeState "color", action:"setAdjustedColor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
multiAttributeTile(name:"switchNoPower", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
|
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
|
||||||
attributeState "color", action:"setAdjustedColor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
multiAttributeTile(name:"switchNoSlider", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
|
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "power", label:'The power level is currently: ${currentValue}W', icon: "st.Appliances.appliances17"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
|
||||||
attributeState "color", action:"setAdjustedColor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
multiAttributeTile(name:"switchNoSliderOrColor", type: "lighting", width: 6, height: 4, canChangeIcon: true) {
|
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.power", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "power", label:'The light is currently consuming this amount of power: ${currentValue}W', icon: "st.Appliances.appliances17"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("color", "device.color", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "color", label: '${currentValue}'
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "refresh", label:"", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["switch"])
|
|
||||||
details(["switch", "switchNoPower", "switchNoSlider", "switchNoSliderOrColor", "color", "refresh", "reset"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(description) {
|
|
||||||
log.debug "parse() - $description"
|
|
||||||
def results = []
|
|
||||||
def map = description
|
|
||||||
if (description instanceof String) {
|
|
||||||
log.debug "Hue Bulb stringToMap - ${map}"
|
|
||||||
map = stringToMap(description)
|
|
||||||
}
|
|
||||||
if (map?.name && map?.value) {
|
|
||||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle commands
|
|
||||||
def on() {
|
|
||||||
//log.trace parent.on(this)
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
//log.trace parent.off(this)
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
}
|
|
||||||
|
|
||||||
def nextLevel() {
|
|
||||||
def level = device.latestValue("level") as Integer ?: 0
|
|
||||||
if (level <= 100) {
|
|
||||||
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
level = 25
|
|
||||||
}
|
|
||||||
setLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(percent) {
|
|
||||||
log.debug "setLevel: ${percent}, this"
|
|
||||||
sendEvent(name: "level", value: percent)
|
|
||||||
def power = Math.round(percent / 1.175) * 0.1
|
|
||||||
sendEvent(name: "power", value: power)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setSaturation(percent) {
|
|
||||||
log.debug "setSaturation: ${percent}, $this"
|
|
||||||
sendEvent(name: "saturation", value: percent)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setHue(percent) {
|
|
||||||
log.debug "setHue: ${percent}, $this"
|
|
||||||
sendEvent(name: "hue", value: percent)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColor(value) {
|
|
||||||
log.debug "setColor: ${value}, $this"
|
|
||||||
if (value.hue) { sendEvent(name: "hue", value: value.hue)}
|
|
||||||
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
|
|
||||||
if (value.hex) { sendEvent(name: "color", value: value.hex)}
|
|
||||||
if (value.level) { sendEvent(name: "level", value: value.level)}
|
|
||||||
if (value.switch) { sendEvent(name: "switch", value: value.switch)}
|
|
||||||
}
|
|
||||||
|
|
||||||
def reset() {
|
|
||||||
log.debug "Executing 'reset'"
|
|
||||||
setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23])
|
|
||||||
}
|
|
||||||
|
|
||||||
def setAdjustedColor(value) {
|
|
||||||
if (value) {
|
|
||||||
log.trace "setAdjustedColor: ${value}"
|
|
||||||
def adjusted = value + [:]
|
|
||||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
|
||||||
// Needed because color picker always sends 100
|
|
||||||
adjusted.level = null
|
|
||||||
setColor(adjusted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "Executing 'refresh'"
|
|
||||||
}
|
|
||||||
|
|
||||||
def adjustOutgoingHue(percent) {
|
|
||||||
def adjusted = percent
|
|
||||||
if (percent > 31) {
|
|
||||||
if (percent < 63.0) {
|
|
||||||
adjusted = percent + (7 * (percent -30 ) / 32)
|
|
||||||
}
|
|
||||||
else if (percent < 73.0) {
|
|
||||||
adjusted = 69 + (5 * (percent - 62) / 10)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
adjusted = percent + (2 * (100 - percent) / 28)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info "percent: $percent, adjusted: $adjusted"
|
|
||||||
adjusted
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "mediaPlayerDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Music Player"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name: "mediaMulti", type:"mediaPlayer", width:6, height:4) {
|
|
||||||
tileAttribute("device.status", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState("paused", label:"Paused",)
|
|
||||||
attributeState("playing", label:"Playing")
|
|
||||||
attributeState("stopped", label:"Stopped")
|
|
||||||
}
|
|
||||||
tileAttribute("device.status", key: "MEDIA_STATUS") {
|
|
||||||
attributeState("paused", label:"Paused", action:"music Player.play", nextState: "playing")
|
|
||||||
attributeState("playing", label:"Playing", action:"music Player.pause", nextState: "paused")
|
|
||||||
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
|
|
||||||
}
|
|
||||||
tileAttribute("device.status", key: "PREVIOUS_TRACK") {
|
|
||||||
attributeState("status", action:"music Player.previousTrack", defaultState: true)
|
|
||||||
}
|
|
||||||
tileAttribute("device.status", key: "NEXT_TRACK") {
|
|
||||||
attributeState("status", action:"music Player.nextTrack", defaultState: true)
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState("level", action:"music Player.setLevel")
|
|
||||||
}
|
|
||||||
tileAttribute ("device.mute", key: "MEDIA_MUTED") {
|
|
||||||
attributeState("unmuted", action:"music Player.mute", nextState: "muted")
|
|
||||||
attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
|
|
||||||
}
|
|
||||||
tileAttribute("device.trackDescription", key: "MARQUEE") {
|
|
||||||
attributeState("trackDescription", label:"${currentValue}", defaultState: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main "mediaMulti"
|
|
||||||
details(["mediaMulti"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
state.tracks = [
|
|
||||||
"Gangnam Style (강남스타일)\nPSY\nPsy 6 (Six Rules), Part 1",
|
|
||||||
"Careless Whisper\nWham!\nMake It Big",
|
|
||||||
"Never Gonna Give You Up\nRick Astley\nWhenever You Need Somebody",
|
|
||||||
"Shake It Off\nTaylor Swift\n1989",
|
|
||||||
"Ironic\nAlanis Morissette\nJagged Little Pill",
|
|
||||||
"Hotline Bling\nDrake\nHotline Bling - Single"
|
|
||||||
]
|
|
||||||
state.currentTrack = 0
|
|
||||||
|
|
||||||
sendEvent(name: "level", value: 72)
|
|
||||||
sendEvent(name: "mute", value: "unmuted")
|
|
||||||
sendEvent(name: "status", value: "stopped")
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(description) {
|
|
||||||
// No parsing will happen with this simulated device.
|
|
||||||
}
|
|
||||||
|
|
||||||
def play() {
|
|
||||||
sendEvent(name: "status", value: "playing")
|
|
||||||
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
|
|
||||||
}
|
|
||||||
|
|
||||||
def pause() {
|
|
||||||
sendEvent(name: "status", value: "paused")
|
|
||||||
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
|
|
||||||
}
|
|
||||||
|
|
||||||
def stop() {
|
|
||||||
sendEvent(name: "status", value: "stopped")
|
|
||||||
}
|
|
||||||
|
|
||||||
def previousTrack() {
|
|
||||||
state.currentTrack = state.currentTrack - 1
|
|
||||||
if (state.currentTrack < 0)
|
|
||||||
state.currentTrack = state.tracks.size()-1
|
|
||||||
|
|
||||||
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
|
|
||||||
}
|
|
||||||
|
|
||||||
def nextTrack() {
|
|
||||||
state.currentTrack = state.currentTrack + 1
|
|
||||||
if (state.currentTrack == state.tracks.size())
|
|
||||||
state.currentTrack = 0
|
|
||||||
|
|
||||||
sendEvent(name: "trackDescription", value: state.tracks[state.currentTrack])
|
|
||||||
}
|
|
||||||
|
|
||||||
def mute() {
|
|
||||||
sendEvent(name: "mute", value: "muted")
|
|
||||||
}
|
|
||||||
|
|
||||||
def unmute() {
|
|
||||||
sendEvent(name: "mute", value: "unmuted")
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(level) {
|
|
||||||
sendEvent(name: "level", value: level)
|
|
||||||
}
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "thermostatDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Thermostat"
|
|
||||||
capability "Relative Humidity Measurement"
|
|
||||||
|
|
||||||
command "tempUp"
|
|
||||||
command "tempDown"
|
|
||||||
command "heatUp"
|
|
||||||
command "heatDown"
|
|
||||||
command "coolUp"
|
|
||||||
command "coolDown"
|
|
||||||
command "setTemperature", ["number"]
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) {
|
|
||||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true)
|
|
||||||
}
|
|
||||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
|
||||||
attributeState("VALUE_UP", action: "tempUp")
|
|
||||||
attributeState("VALUE_DOWN", action: "tempDown")
|
|
||||||
}
|
|
||||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState("humidity", label:'${currentValue}%', unit:"%", defaultState: true)
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
|
||||||
attributeState("idle", backgroundColor:"#44b621")
|
|
||||||
attributeState("heating", backgroundColor:"#ffa81e")
|
|
||||||
attributeState("cooling", backgroundColor:"#269bd2")
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
|
||||||
attributeState("off", label:'${name}')
|
|
||||||
attributeState("heat", label:'${name}')
|
|
||||||
attributeState("cool", label:'${name}')
|
|
||||||
attributeState("auto", label:'${name}')
|
|
||||||
}
|
|
||||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
|
||||||
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
|
||||||
}
|
|
||||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
|
||||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) {
|
|
||||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
|
||||||
attributeState("temp", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
|
||||||
attributeState("VALUE_UP", action: "tempUp")
|
|
||||||
attributeState("VALUE_DOWN", action: "tempDown")
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
|
||||||
attributeState("idle", backgroundColor:"#44b621")
|
|
||||||
attributeState("heating", backgroundColor:"#ffa81e")
|
|
||||||
attributeState("cooling", backgroundColor:"#269bd2")
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
|
||||||
attributeState("off", label:'${name}')
|
|
||||||
attributeState("heat", label:'${name}')
|
|
||||||
attributeState("cool", label:'${name}')
|
|
||||||
attributeState("auto", label:'${name}')
|
|
||||||
}
|
|
||||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
|
||||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
|
||||||
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
|
||||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) {
|
|
||||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true,
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 31, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
])
|
|
||||||
}
|
|
||||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
|
||||||
attributeState("VALUE_UP", action: "tempUp")
|
|
||||||
attributeState("VALUE_DOWN", action: "tempDown")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
|
||||||
state("temperature", label:'${currentValue}', unit:"dF",
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 31, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "tempDown", label:'down', action:"tempDown", defaultState: true
|
|
||||||
}
|
|
||||||
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "tempUp", label:'up', action:"tempUp", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "heatDown", label:'down', action:"heatDown", defaultState: true
|
|
||||||
}
|
|
||||||
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "heatUp", label:'up', action:"heatUp", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "coolDown", label:'down', action:"coolDown", defaultState: true
|
|
||||||
}
|
|
||||||
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "coolUp", label:'up', action:"coolUp", defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "off", label:'${name}', action:"thermostat.heat", backgroundColor:"#ffffff"
|
|
||||||
state "heat", label:'${name}', action:"thermostat.cool", backgroundColor:"#ffa81e"
|
|
||||||
state "cool", label:'${name}', action:"thermostat.auto", backgroundColor:"#269bd2"
|
|
||||||
state "auto", label:'${name}', action:"thermostat.off", backgroundColor:"#79b821"
|
|
||||||
}
|
|
||||||
standardTile("fanMode", "device.thermostatFanMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "fanAuto", label:'${name}', action:"thermostat.fanOn", backgroundColor:"#ffffff"
|
|
||||||
state "fanOn", label:'${name}', action:"thermostat.fanCirculate", backgroundColor:"#ffffff"
|
|
||||||
state "fanCirculate", label:'${name}', action:"thermostat.fanAuto", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
standardTile("operatingState", "device.thermostatOperatingState", width: 2, height: 2) {
|
|
||||||
state "idle", label:'${name}', backgroundColor:"#ffffff"
|
|
||||||
state "heating", label:'${name}', backgroundColor:"#ffa81e"
|
|
||||||
state "cooling", label:'${name}', backgroundColor:"#269bd2"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
main("thermostatFull")
|
|
||||||
details([
|
|
||||||
"thermostatFull", "thermostatNoHumidity", "thermostatBasic",
|
|
||||||
"temperature","tempDown","tempUp",
|
|
||||||
"mode", "fanMode", "operatingState",
|
|
||||||
"heatingSetpoint", "heatDown", "heatUp",
|
|
||||||
"coolingSetpoint", "coolDown", "coolUp"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
sendEvent(name: "temperature", value: 72, unit: "F")
|
|
||||||
sendEvent(name: "heatingSetpoint", value: 70, unit: "F")
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: 70, unit: "F")
|
|
||||||
sendEvent(name: "coolingSetpoint", value: 76, unit: "F")
|
|
||||||
sendEvent(name: "thermostatMode", value: "off")
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
|
||||||
sendEvent(name: "humidity", value: 53, unit: "%")
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
}
|
|
||||||
|
|
||||||
def evaluate(temp, heatingSetpoint, coolingSetpoint) {
|
|
||||||
log.debug "evaluate($temp, $heatingSetpoint, $coolingSetpoint"
|
|
||||||
def threshold = 1.0
|
|
||||||
def current = device.currentValue("thermostatOperatingState")
|
|
||||||
def mode = device.currentValue("thermostatMode")
|
|
||||||
|
|
||||||
def heating = false
|
|
||||||
def cooling = false
|
|
||||||
def idle = false
|
|
||||||
if (mode in ["heat","emergency heat","auto"]) {
|
|
||||||
if (heatingSetpoint - temp >= threshold) {
|
|
||||||
heating = true
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "heating")
|
|
||||||
}
|
|
||||||
else if (temp - heatingSetpoint >= threshold) {
|
|
||||||
idle = true
|
|
||||||
}
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
|
||||||
}
|
|
||||||
if (mode in ["cool","auto"]) {
|
|
||||||
if (temp - coolingSetpoint >= threshold) {
|
|
||||||
cooling = true
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "cooling")
|
|
||||||
}
|
|
||||||
else if (coolingSetpoint - temp >= threshold && !heating) {
|
|
||||||
idle = true
|
|
||||||
}
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: coolingSetpoint)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == "off") {
|
|
||||||
idle = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idle && !heating && !cooling) {
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setHeatingSetpoint(Double degreesF) {
|
|
||||||
log.debug "setHeatingSetpoint($degreesF)"
|
|
||||||
sendEvent(name: "heatingSetpoint", value: degreesF)
|
|
||||||
evaluate(device.currentValue("temperature"), degreesF, device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def setCoolingSetpoint(Double degreesF) {
|
|
||||||
log.debug "setCoolingSetpoint($degreesF)"
|
|
||||||
sendEvent(name: "coolingSetpoint", value: degreesF)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), degreesF)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setThermostatMode(String value) {
|
|
||||||
sendEvent(name: "thermostatMode", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def setThermostatFanMode(String value) {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "off")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def heat() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "heat")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def auto() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "auto")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def emergencyHeat() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "emergency heat")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def cool() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "cool")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def fanOn() {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanOn")
|
|
||||||
}
|
|
||||||
|
|
||||||
def fanAuto() {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
|
||||||
}
|
|
||||||
|
|
||||||
def fanCirculate() {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanCirculate")
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll() {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
def tempUp() {
|
|
||||||
def ts = device.currentState("temperature")
|
|
||||||
def value = ts ? ts.integerValue + 1 : 72
|
|
||||||
sendEvent(name:"temperature", value: value)
|
|
||||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def tempDown() {
|
|
||||||
def ts = device.currentState("temperature")
|
|
||||||
def value = ts ? ts.integerValue - 1 : 72
|
|
||||||
sendEvent(name:"temperature", value: value)
|
|
||||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def setTemperature(value) {
|
|
||||||
def ts = device.currentState("temperature")
|
|
||||||
sendEvent(name:"temperature", value: value)
|
|
||||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def heatUp() {
|
|
||||||
def ts = device.currentState("heatingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue + 1 : 68
|
|
||||||
sendEvent(name:"heatingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def heatDown() {
|
|
||||||
def ts = device.currentState("heatingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue - 1 : 68
|
|
||||||
sendEvent(name:"heatingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def coolUp() {
|
|
||||||
def ts = device.currentState("coolingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue + 1 : 76
|
|
||||||
sendEvent(name:"coolingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def coolDown() {
|
|
||||||
def ts = device.currentState("coolingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue - 1 : 76
|
|
||||||
sendEvent(name:"coolingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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: "videoPlayerDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Video Camera"
|
|
||||||
capability "Video Capture"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Switch"
|
|
||||||
|
|
||||||
// custom commands
|
|
||||||
command "start"
|
|
||||||
command "stop"
|
|
||||||
command "setProfileHD"
|
|
||||||
command "setProfileSDH"
|
|
||||||
command "setProfileSDL"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name: "videoPlayer", type: "videoPlayer", width: 6, height: 4) {
|
|
||||||
tileAttribute("device.switch", key: "CAMERA_STATUS") {
|
|
||||||
attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", action: "switch.off", backgroundColor: "#79b821", defaultState: true)
|
|
||||||
attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", action: "switch.on", backgroundColor: "#ffffff")
|
|
||||||
attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
|
|
||||||
attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", action: "refresh.refresh", backgroundColor: "#F22000")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.errorMessage", key: "CAMERA_ERROR_MESSAGE") {
|
|
||||||
attributeState("errorMessage", label: "", value: "", defaultState: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.camera", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", backgroundColor: "#79b821", defaultState: true)
|
|
||||||
attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", backgroundColor: "#ffffff")
|
|
||||||
attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
|
|
||||||
attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", backgroundColor: "#F22000")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.startLive", key: "START_LIVE") {
|
|
||||||
attributeState("live", action: "start", defaultState: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.stream", key: "STREAM_URL") {
|
|
||||||
attributeState("activeURL", defaultState: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.profile", key: "STREAM_QUALITY") {
|
|
||||||
attributeState("1", label: "720p", action: "setProfileHD", defaultState: true)
|
|
||||||
attributeState("2", label: "h360p", action: "setProfileSDH", defaultState: true)
|
|
||||||
attributeState("3", label: "l360p", action: "setProfileSDL", defaultState: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.betaLogo", key: "BETA_LOGO") {
|
|
||||||
attributeState("betaLogo", label: "", value: "", defaultState: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
multiAttributeTile(name: "videoPlayerMin", type: "videoPlayer", width: 6, height: 4) {
|
|
||||||
tileAttribute("device.switch", key: "CAMERA_STATUS") {
|
|
||||||
attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", action: "switch.off", backgroundColor: "#79b821", defaultState: true)
|
|
||||||
attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", action: "switch.on", backgroundColor: "#ffffff")
|
|
||||||
attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
|
|
||||||
attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", action: "refresh.refresh", backgroundColor: "#F22000")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.errorMessage", key: "CAMERA_ERROR_MESSAGE") {
|
|
||||||
attributeState("errorMessage", label: "", value: "", defaultState: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.camera", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState("on", label: "Active", icon: "st.camera.dlink-indoor", backgroundColor: "#79b821", defaultState: true)
|
|
||||||
attributeState("off", label: "Inactive", icon: "st.camera.dlink-indoor", backgroundColor: "#ffffff")
|
|
||||||
attributeState("restarting", label: "Connecting", icon: "st.camera.dlink-indoor", backgroundColor: "#53a7c0")
|
|
||||||
attributeState("unavailable", label: "Unavailable", icon: "st.camera.dlink-indoor", backgroundColor: "#F22000")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.startLive", key: "START_LIVE") {
|
|
||||||
attributeState("live", action: "start", defaultState: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.stream", key: "STREAM_URL") {
|
|
||||||
attributeState("activeURL", defaultState: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main("videoPlayer")
|
|
||||||
details([
|
|
||||||
"videoPlayer", "videoPlayerMin"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.trace "refresh()"
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
log.trace "on()"
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.trace "off()"
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
def setProfile(profile) {
|
|
||||||
log.trace "setProfile(): ${profile}"
|
|
||||||
sendEvent(name: "profile", value: profile, displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setProfileHD() {
|
|
||||||
setProfile(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setProfileSDH() {
|
|
||||||
setProfile(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setProfileSDL() {
|
|
||||||
setProfile(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
def start() {
|
|
||||||
log.trace "start()"
|
|
||||||
def dataLiveVideo = [
|
|
||||||
OutHomeURL : "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8",
|
|
||||||
InHomeURL : "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8",
|
|
||||||
ThumbnailURL: "http://cdn.device-icons.smartthings.com/camera/dlink-indoor@2x.png",
|
|
||||||
cookie : [key: "key", value: "value"]
|
|
||||||
]
|
|
||||||
|
|
||||||
def event = [
|
|
||||||
name : "stream",
|
|
||||||
value : groovy.json.JsonOutput.toJson(dataLiveVideo).toString(),
|
|
||||||
data : groovy.json.JsonOutput.toJson(dataLiveVideo),
|
|
||||||
descriptionText: "Starting the livestream",
|
|
||||||
eventType : "VIDEO",
|
|
||||||
displayed : false,
|
|
||||||
isStateChange : true
|
|
||||||
]
|
|
||||||
sendEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
def stop() {
|
|
||||||
log.trace "stop()"
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
@@ -162,9 +161,40 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
@@ -193,8 +223,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ metadata {
|
|||||||
state "power", label: '${currentValue} W'
|
state "power", label: '${currentValue} W'
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlTile(name: "powerContent", attribute: "powerContent", type: "HTML", whitelist: ["www.wattvision.com"] , url: '${currentValue}', width: 3, height: 2)
|
htmlTile(name: "powerContent", attribute: "powerContent", type: "HTML", whitelist: "www.wattvision.com" , url: '${currentValue}', width: 3, height: 2)
|
||||||
|
|
||||||
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat") {
|
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh"
|
state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh"
|
||||||
|
|||||||
@@ -1,247 +0,0 @@
|
|||||||
/**
|
|
||||||
* ZigBee Button
|
|
||||||
*
|
|
||||||
* Copyright 2015 Mitch Pond
|
|
||||||
*
|
|
||||||
* 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: "ZigBee Button", namespace: "smartthings", author: "Mitch Pond") {
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Battery"
|
|
||||||
capability "Button"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "enrollResponse"
|
|
||||||
|
|
||||||
fingerprint inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM LIGHTIFY Dimming Switch"
|
|
||||||
//fingerprint inClusters: "0000, 0001, 0003, 0020, 0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L", deviceJoinName: "Iris Care Pendant"
|
|
||||||
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
|
|
||||||
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {}
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section {
|
|
||||||
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"", defaultValue: 1, displayDuringSetup: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles {
|
|
||||||
standardTile("button", "device.button", width: 2, height: 2) {
|
|
||||||
state "default", label: "", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
|
|
||||||
state "button 1 pushed", label: "pushed #1", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#79b821"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
main (["button"])
|
|
||||||
details(["button", "battery", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "description is $description"
|
|
||||||
def event = zigbee.getEvent(description)
|
|
||||||
if (event) {
|
|
||||||
sendEvent(event)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
|
|
||||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
|
||||||
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
|
|
||||||
event = getBatteryResult(zigbee.convertHexToInt(descMap.value))
|
|
||||||
}
|
|
||||||
else if (descMap.clusterInt == 0x0006 || descMap.clusterInt == 0x0008) {
|
|
||||||
event = parseNonIasButtonMessage(descMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (description?.startsWith('zone status')) {
|
|
||||||
event = parseIasButtonMessage(description)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug "Parse returned $event"
|
|
||||||
def result = event ? createEvent(event) : []
|
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
|
||||||
List cmds = enrollResponse()
|
|
||||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseIasButtonMessage(String description) {
|
|
||||||
int zoneInt = Integer.parseInt((description - "zone status 0x"), 16)
|
|
||||||
if (zoneInt & 0x02) {
|
|
||||||
resultMap = getButtonResult('press')
|
|
||||||
} else {
|
|
||||||
resultMap = getButtonResult('release')
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getBatteryResult(rawValue) {
|
|
||||||
log.debug 'Battery'
|
|
||||||
def volts = rawValue / 10
|
|
||||||
if (volts > 3.0 || volts == 0 || rawValue == 0xFF) {
|
|
||||||
return [:]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def result = [
|
|
||||||
name: 'battery'
|
|
||||||
]
|
|
||||||
def minVolts = 2.1
|
|
||||||
def maxVolts = 3.0
|
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
|
||||||
result.value = Math.min(100, (int) pct * 100)
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseNonIasButtonMessage(Map descMap){
|
|
||||||
def buttonState = ""
|
|
||||||
def buttonNumber = 0
|
|
||||||
if (((device.getDataValue("model") == "3460-L") || (device.getDataValue("model") == "3450-L"))
|
|
||||||
&&(descMap.clusterInt == 0x0006)) {
|
|
||||||
if (descMap.command == "01") {
|
|
||||||
getButtonResult("press")
|
|
||||||
}
|
|
||||||
else if (descMap.command == "00") {
|
|
||||||
getButtonResult("release")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (descMap.clusterInt == 0x0006) {
|
|
||||||
buttonState = "pushed"
|
|
||||||
if (descMap.command == "01") {
|
|
||||||
buttonNumber = 1
|
|
||||||
}
|
|
||||||
else if (descMap.command == "00") {
|
|
||||||
buttonNumber = 2
|
|
||||||
}
|
|
||||||
if (buttonNumber !=0) {
|
|
||||||
def descriptionText = "$device.displayName button $buttonNumber was $buttonState"
|
|
||||||
return createEvent(name: "button", value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return [:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (descMap.clusterInt == 0x0008) {
|
|
||||||
if (descMap.command == "05") {
|
|
||||||
state.buttonNumber = 1
|
|
||||||
getButtonResult("press", 1)
|
|
||||||
}
|
|
||||||
else if (descMap.command == "01") {
|
|
||||||
state.buttonNumber = 2
|
|
||||||
getButtonResult("press", 2)
|
|
||||||
}
|
|
||||||
else if (descMap.command == "03") {
|
|
||||||
getButtonResult("release", state.buttonNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "Refreshing Battery"
|
|
||||||
|
|
||||||
return zigbee.readAttribute(0x0001, 0x20) +
|
|
||||||
zigbee.enrollResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
|
||||||
def cmds = []
|
|
||||||
if (device.getDataValue("model") == "3450-L") {
|
|
||||||
cmds << [
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 300",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 300",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 300",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 300"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return zigbee.onOffConfig() +
|
|
||||||
zigbee.levelConfig() +
|
|
||||||
zigbee.configureReporting(0x0001, 0x20, 0x20, 30, 21600, 0x01) +
|
|
||||||
zigbee.enrollResponse() +
|
|
||||||
zigbee.readAttribute(0x0001, 0x20) +
|
|
||||||
cmds
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getButtonResult(buttonState, buttonNumber = 1) {
|
|
||||||
if (buttonState == 'release') {
|
|
||||||
log.debug "Button was value : $buttonState"
|
|
||||||
def timeDiff = now() - state.pressTime
|
|
||||||
log.info "timeDiff: $timeDiff"
|
|
||||||
def holdPreference = holdTime ?: 1
|
|
||||||
log.info "holdp1 : $holdPreference"
|
|
||||||
holdPreference = (holdPreference as int) * 1000
|
|
||||||
log.info "holdp2 : $holdPreference"
|
|
||||||
if (timeDiff > 10000) { //timeDiff>10sec check for refresh sending release value causing actions to be executed
|
|
||||||
return [:]
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (timeDiff < holdPreference) {
|
|
||||||
buttonState = "pushed"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
buttonState = "held"
|
|
||||||
}
|
|
||||||
def descriptionText = "$device.displayName button $buttonNumber was $buttonState"
|
|
||||||
return createEvent(name: "button", value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (buttonState == 'press') {
|
|
||||||
log.debug "Button was value : $buttonState"
|
|
||||||
state.pressTime = now()
|
|
||||||
log.info "presstime: ${state.pressTime}"
|
|
||||||
return [:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
if ((device.getDataValue("manufacturer") == "OSRAM") && (device.getDataValue("model") == "LIGHTIFY Dimming Switch")) {
|
|
||||||
sendEvent(name: "numberOfButtons", value: 2)
|
|
||||||
}
|
|
||||||
else if ((device.getDataValue("manufacturer") == "CentraLite") &&
|
|
||||||
((device.getDataValue("model") == "3455-L") || (device.getDataValue("model") == "3460-L"))) {
|
|
||||||
sendEvent(name: "numberOfButtons", value: 1)
|
|
||||||
}
|
|
||||||
else if ((device.getDataValue("manufacturer") == "CentraLite") && (device.getDataValue("model") == "3450-L")) {
|
|
||||||
sendEvent(name: "numberOfButtons", value: 4)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//default. can be changed
|
|
||||||
sendEvent(name: "numberOfButtons", value: 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -56,17 +56,21 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
|
|
||||||
def event = zigbee.getEvent(description)
|
def resultMap = zigbee.getKnownDescription(description)
|
||||||
if (event) {
|
if (resultMap) {
|
||||||
log.info event
|
log.info resultMap
|
||||||
if (event.name == "power") {
|
if (resultMap.type == "update") {
|
||||||
|
log.info "$device updates: ${resultMap.value}"
|
||||||
|
}
|
||||||
|
else if (resultMap.type == "power") {
|
||||||
|
def powerValue
|
||||||
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||||
event.value = (event.value as Integer) / 10 //TODO: The divisor value needs to be set as part of configuration
|
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||||
sendEvent(event)
|
sendEvent(name: "power", value: powerValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sendEvent(event)
|
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -51,9 +51,15 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
|
|
||||||
def event = zigbee.getEvent(description)
|
def resultMap = zigbee.getKnownDescription(description)
|
||||||
if (event) {
|
if (resultMap) {
|
||||||
sendEvent(event)
|
log.info resultMap
|
||||||
|
if (resultMap.type == "update") {
|
||||||
|
log.info "$device updates: ${resultMap.value}"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
|
|||||||
@@ -11,9 +11,6 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//@Deprecated - Moved to zll-rgbw-bulb
|
|
||||||
|
|
||||||
/* Philips Hue (via Zigbee)
|
/* Philips Hue (via Zigbee)
|
||||||
|
|
||||||
Capabilities:
|
Capabilities:
|
||||||
@@ -44,7 +41,7 @@ metadata {
|
|||||||
|
|
||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
|
|
||||||
//fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000", outClusters: "0019"
|
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,0300,1000", outClusters: "0019"
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
|
|||||||
@@ -83,19 +83,32 @@ def uninstalled() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
|
/*
|
||||||
def cmds =
|
def cmds =
|
||||||
zigbee.configureReporting(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE,
|
zigbee.configSetup("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}",
|
||||||
TYPE_ENUM8, 0, 3600, null) +
|
"${TYPE_ENUM8}", 0, 3600, "{01}") +
|
||||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
|
zigbee.configSetup("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}",
|
||||||
TYPE_U8, 600, 21600, 0x01)
|
"${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"
|
log.info "configure() --- cmds: $cmds"
|
||||||
return cmds + refresh() // send refresh cmds as part of config
|
return cmds + refresh() // send refresh cmds as part of config
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
def cmds =
|
def cmds =
|
||||||
zigbee.readAttribute(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE) +
|
zigbee.refreshData("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}") +
|
||||||
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
|
zigbee.refreshData("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}")
|
||||||
log.info "refresh() --- cmds: $cmds"
|
log.info "refresh() --- cmds: $cmds"
|
||||||
return cmds
|
return cmds
|
||||||
}
|
}
|
||||||
@@ -108,27 +121,34 @@ def parse(String description) {
|
|||||||
map = parseReportAttributeMessage(description)
|
map = parseReportAttributeMessage(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug "parse() --- Parse returned $map"
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : null
|
||||||
log.debug "parse() --- returned: $result"
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock capability commands
|
// Lock capability commands
|
||||||
def lock() {
|
def lock() {
|
||||||
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_LOCK_DOOR)
|
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
|
||||||
log.info "lock() -- cmds: $cmds"
|
//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 unlock() {
|
||||||
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_UNLOCK_DOOR)
|
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
|
||||||
log.info "unlock() -- cmds: $cmds"
|
//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
|
// Private methods
|
||||||
private Map parseReportAttributeMessage(String description) {
|
private Map parseReportAttributeMessage(String description) {
|
||||||
|
log.trace "parseReportAttributeMessage() --- description: $description"
|
||||||
|
|
||||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||||
|
|
||||||
|
log.debug "parseReportAttributeMessage() --- descMap: $descMap"
|
||||||
|
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
|
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
|
||||||
resultMap.name = "battery"
|
resultMap.name = "battery"
|
||||||
@@ -136,24 +156,18 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
|
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
|
||||||
resultMap.value = Integer.parseInt(descMap.value, 16)
|
resultMap.value = Integer.parseInt(descMap.value, 16)
|
||||||
}
|
}
|
||||||
|
log.info "parseReportAttributeMessage() --- battery: ${resultMap.value}"
|
||||||
}
|
}
|
||||||
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {
|
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {
|
||||||
def value = Integer.parseInt(descMap.value, 16)
|
def value = Integer.parseInt(descMap.value, 16)
|
||||||
def linkText = getLinkText(device)
|
|
||||||
resultMap.name = "lock"
|
resultMap.name = "lock"
|
||||||
if (value == 0) {
|
resultMap.putAll([0:["value":"unknown",
|
||||||
resultMap.value = "unknown"
|
"descriptionText":"Not fully locked"],
|
||||||
resultMap.descriptionText = "${linkText} is not fully locked"
|
1:["value":"locked"],
|
||||||
} else if (value == 1) {
|
2:["value":"unlocked"]].get(value,
|
||||||
resultMap.value = "locked"
|
["value":"unknown",
|
||||||
resultMap.descriptionText = "${linkText} is locked"
|
"descriptionText":"Unknown lock state"]))
|
||||||
} else if (value == 2) {
|
log.info "parseReportAttributeMessage() --- lock: ${resultMap.value}"
|
||||||
resultMap.value = "unlocked"
|
|
||||||
resultMap.descriptionText = "${linkText} is unlocked"
|
|
||||||
} else {
|
|
||||||
resultMap.value = "unknown"
|
|
||||||
resultMap.descriptionText = "${linkText} is in unknown lock state"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "parseReportAttributeMessage() --- ignoring attribute"
|
log.debug "parseReportAttributeMessage() --- ignoring attribute"
|
||||||
|
|||||||
@@ -51,15 +51,22 @@ metadata {
|
|||||||
// Parse incoming device messages to generate events
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
def event = zigbee.getEvent(description)
|
|
||||||
if (event) {
|
def resultMap = zigbee.getKnownDescription(description)
|
||||||
if (event.name == "power") {
|
if (resultMap) {
|
||||||
|
log.info resultMap
|
||||||
|
if (resultMap.type == "update") {
|
||||||
|
log.info "$device updates: ${resultMap.value}"
|
||||||
|
}
|
||||||
|
else if (resultMap.type == "power") {
|
||||||
def powerValue
|
def powerValue
|
||||||
powerValue = (event.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||||
sendEvent(name: "power", value: powerValue)
|
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||||
|
sendEvent(name: "power", value: powerValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sendEvent(event)
|
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -54,9 +53,16 @@ metadata {
|
|||||||
// Parse incoming device messages to generate events
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
def event = zigbee.getEvent(description)
|
|
||||||
if (event) {
|
def resultMap = zigbee.getKnownDescription(description)
|
||||||
sendEvent(event)
|
if (resultMap) {
|
||||||
|
log.info resultMap
|
||||||
|
if (resultMap.type == "update") {
|
||||||
|
log.info "$device updates: ${resultMap.value}"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* 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:
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
@@ -11,133 +11,100 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
|
* Capabilities
|
||||||
|
* - Battery
|
||||||
|
* - Configuration
|
||||||
|
* - Refresh
|
||||||
|
* - Switch
|
||||||
|
* - Valve
|
||||||
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "ZigBee Valve", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Zigbee Valve", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Actuator"
|
capability "Battery"
|
||||||
capability "Battery"
|
capability "Configuration"
|
||||||
capability "Configuration"
|
capability "Refresh"
|
||||||
capability "Power Source"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Valve"
|
||||||
capability "Valve"
|
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0006, 0020, 0B02, FC02", outClusters: "0019", manufacturer: "WAXMAN", model: "leakSMART Water Valve v2.10", deviceJoinName: "leakSMART Valve"
|
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0020,0006,0B02", outClusters: "0003"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0001, 0003, 0004, 0005, 0006, 0008, 000F, 0020, 0B02", outClusters: "0003, 0019", manufacturer: "WAXMAN", model: "House Water Valve - MDL-TBD", deviceJoinName: "Waxman House Water Valve"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
simulator {
|
simulator {
|
||||||
// status messages
|
// status messages
|
||||||
status "on": "on/off: 1"
|
status "on": "on/off: 1"
|
||||||
status "off": "on/off: 0"
|
status "off": "on/off: 0"
|
||||||
|
|
||||||
// reply messages
|
// reply messages
|
||||||
reply "zcl on-off on": "on/off: 1"
|
reply "zcl on-off on": "on/off: 1"
|
||||||
reply "zcl on-off off": "on/off: 0"
|
reply "zcl on-off off": "on/off: 0"
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
// UI tile definitions
|
||||||
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
tiles {
|
||||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||||
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#53a7c0", nextState:"closing"
|
state "off", label: 'closed', action: "switch.on", icon: "st.Outdoor.outdoor16", backgroundColor: "#e86d13"
|
||||||
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#e86d13", nextState:"opening"
|
state "on", label: 'open', action: "switch.off", icon: "st.Outdoor.outdoor16", backgroundColor: "#53a7c0"
|
||||||
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#53a7c0", nextState:"closing"
|
}
|
||||||
attributeState "closing", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#e86d13", nextState:"opening"
|
main "switch"
|
||||||
}
|
details(["switch"])
|
||||||
tileAttribute ("powerSource", key: "SECONDARY_CONTROL") {
|
}
|
||||||
attributeState "powerSource", label:'Power Source: ${currentValue}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["valve"])
|
|
||||||
details(["valve", "battery", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCLUSTER_BASIC() { 0x0000 }
|
|
||||||
private getBASIC_ATTR_POWER_SOURCE() { 0x0007 }
|
|
||||||
private getCLUSTER_POWER() { 0x0001 }
|
|
||||||
private getPOWER_ATTR_BATTERY_PERCENTAGE_REMAINING() { 0x0021 }
|
|
||||||
private getTYPE_U8() { 0x20 }
|
|
||||||
private getTYPE_ENUM8() { 0x30 }
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.info description
|
||||||
def event = zigbee.getEvent(description)
|
if (description?.startsWith("catchall:")) {
|
||||||
if (event) {
|
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
|
||||||
if(event.name == "switch") {
|
def result = createEvent(name: name, value: value)
|
||||||
event.name = "contact" //0006 cluster in valve is tied to contact
|
def msg = zigbee.parse(description)
|
||||||
if(event.value == "on") {
|
log.debug "Parse returned ${result?.descriptionText}"
|
||||||
event.value = "open"
|
return result
|
||||||
}
|
log.trace msg
|
||||||
else if(event.value == "off") {
|
log.trace "data: $msg.data"
|
||||||
event.value = "closed"
|
}
|
||||||
}
|
else {
|
||||||
}
|
def name = description?.startsWith("on/off: ") ? "switch" : null
|
||||||
sendEvent(event)
|
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
|
||||||
}
|
def result = createEvent(name: name, value: value)
|
||||||
else {
|
log.debug "Parse returned ${result?.descriptionText}"
|
||||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
return result
|
||||||
if (descMap.clusterInt == CLUSTER_BASIC && descMap.attrInt == BASIC_ATTR_POWER_SOURCE){
|
}
|
||||||
def value = descMap.value
|
}
|
||||||
if (value == "01" || value == "02") {
|
|
||||||
sendEvent(name: "powerSource", value: "Mains")
|
// Commands to device
|
||||||
}
|
def on() {
|
||||||
else if (value == "03") {
|
log.debug "on()"
|
||||||
sendEvent(name: "powerSource", value: "Battery")
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||||
else if (value == "04") {
|
}
|
||||||
sendEvent(name: "powerSource", value: "DC")
|
|
||||||
}
|
def off() {
|
||||||
else {
|
log.debug "off()"
|
||||||
sendEvent(name: "powerSource", value: "Unknown")
|
sendEvent(name: "switch", value: "off")
|
||||||
}
|
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||||
}
|
|
||||||
else if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
|
|
||||||
event.name = "battery"
|
|
||||||
event.value = Math.round(Integer.parseInt(descMap.value, 16) / 2)
|
|
||||||
sendEvent(event)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
|
||||||
log.debug descMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def open() {
|
def open() {
|
||||||
zigbee.on()
|
log.debug "on()"
|
||||||
|
sendEvent(name: "switch", value: "on")
|
||||||
|
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def close() {
|
def close() {
|
||||||
zigbee.off()
|
log.debug "off()"
|
||||||
|
sendEvent(name: "switch", value: "off")
|
||||||
|
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "refresh called"
|
log.debug "sending refresh command"
|
||||||
zigbee.onOffRefresh() +
|
"st rattr 0x${device.deviceNetworkId} 1 6 0"
|
||||||
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
|
|
||||||
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) +
|
|
||||||
zigbee.onOffConfig() +
|
|
||||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
|
|
||||||
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
|
||||||
zigbee.onOffConfig() +
|
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}"
|
||||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
|
|
||||||
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1) +
|
|
||||||
zigbee.onOffRefresh() +
|
|
||||||
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
|
|
||||||
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,9 +73,16 @@ metadata {
|
|||||||
// Parse incoming device messages to generate events
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
def event = zigbee.getEvent(description)
|
|
||||||
if (event) {
|
def finalResult = zigbee.getKnownDescription(description)
|
||||||
sendEvent(event)
|
if (finalResult) {
|
||||||
|
log.info finalResult
|
||||||
|
if (finalResult.type == "update") {
|
||||||
|
log.info "$device updates: ${finalResult.value}"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendEvent(name: finalResult.type, value: finalResult.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
*
|
|
||||||
* 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: "ZLL Dimmer Bulb", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Switch"
|
|
||||||
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: "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, 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", outClusters: "0019", manufacturer: "Philips", model: "LWB006", deviceJoinName: "Philips Hue White"
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
// status messages
|
|
||||||
status "on": "on/off: 1"
|
|
||||||
status "off": "on/off: 0"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "zcl on-off on": "on/off: 1"
|
|
||||||
reply "zcl on-off off": "on/off: 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
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.debug "description is $description"
|
|
||||||
|
|
||||||
def resultMap = zigbee.getEvent(description)
|
|
||||||
if (resultMap) {
|
|
||||||
sendEvent(resultMap)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
|
||||||
log.debug zigbee.parseDescriptionAsMap(description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
zigbee.off() + ["delay 1500"] + zigbee.onOffRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
zigbee.on() + ["delay 1500"] + zigbee.onOffRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll() {
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
|
||||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
*
|
|
||||||
* 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: "ZLL RGBW Bulb", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Color Control"
|
|
||||||
capability "Color Temperature"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Switch Level"
|
|
||||||
|
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
|
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019"
|
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 RGBW"
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
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.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
|
||||||
attributeState "color", action:"color control.setColor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
|
|
||||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
|
||||||
}
|
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "colorTemperature", label: '${currentValue} K'
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["switch"])
|
|
||||||
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Globals
|
|
||||||
private getATTRIBUTE_HUE() { 0x0000 }
|
|
||||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
|
||||||
private getHUE_COMMAND() { 0x00 }
|
|
||||||
private getSATURATION_COMMAND() { 0x03 }
|
|
||||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
|
||||||
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "description is $description"
|
|
||||||
|
|
||||||
def finalResult = zigbee.getEvent(description)
|
|
||||||
if (finalResult) {
|
|
||||||
log.debug finalResult
|
|
||||||
sendEvent(finalResult)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
|
|
||||||
log.trace "zigbeeMap : $zigbeeMap"
|
|
||||||
|
|
||||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
|
||||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
|
||||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
|
|
||||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
|
||||||
}
|
|
||||||
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
|
||||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
|
||||||
sendEvent(name: "saturation", value: saturationValue, displayed:false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.info "DID NOT PARSE MESSAGE for description : $description"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
zigbee.on() + ["delay 1500"] + zigbee.onOffRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
zigbee.off() + ["delay 1500"] + zigbee.onOffRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
refreshAttributes() + configureAttributes()
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll() {
|
|
||||||
refreshAttributes()
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
|
||||||
configureAttributes() + refreshAttributes()
|
|
||||||
}
|
|
||||||
|
|
||||||
def configureAttributes() {
|
|
||||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
|
||||||
}
|
|
||||||
|
|
||||||
def refreshAttributes() {
|
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColorTemperature(value) {
|
|
||||||
zigbee.setColorTemperature(value) + ["delay 1500"] + zigbee.colorTemperatureRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColor(value){
|
|
||||||
log.trace "setColor($value)"
|
|
||||||
zigbee.on() + setHue(value.hue) + ["delay 300"] + setSaturation(value.saturation) + ["delay 2000"] + refreshAttributes()
|
|
||||||
}
|
|
||||||
|
|
||||||
def setHue(value) {
|
|
||||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
|
||||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setSaturation(value) {
|
|
||||||
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
|
||||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
*
|
|
||||||
* 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: "ZLL White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Color Temperature"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Switch Level"
|
|
||||||
|
|
||||||
attribute "colorName", "string"
|
|
||||||
command "setGenericName"
|
|
||||||
|
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, 0B04, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Classic A60 Tunable White"
|
|
||||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 1000, FC0F", outClusters: "0019", "manufacturer":"OSRAM", "model":"PAR16 50 TW", deviceJoinName: "OSRAM LIGHTIFY LED PAR16 50 Tunable White"
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
tileAttribute ("colorName", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "colorName", label:'${currentValue}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
|
|
||||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
|
||||||
}
|
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "colorTemperature", label: '${currentValue} K'
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["switch"])
|
|
||||||
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "description is $description"
|
|
||||||
def event = zigbee.getEvent(description)
|
|
||||||
if (event) {
|
|
||||||
sendEvent(event)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
|
||||||
log.debug zigbee.parseDescriptionAsMap(description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
zigbee.off() + ["delay 1500"] + zigbee.onOffRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
zigbee.on() + ["delay 1500"] + zigbee.onOffRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll() {
|
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
|
||||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColorTemperature(value) {
|
|
||||||
setGenericName(value)
|
|
||||||
zigbee.setColorTemperature(value) + ["delay 1500"] + zigbee.colorTemperatureRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
|
||||||
def setGenericName(value){
|
|
||||||
if (value != null) {
|
|
||||||
def genericName = ""
|
|
||||||
if (value < 3300) {
|
|
||||||
genericName = "Soft White"
|
|
||||||
} else if (value < 4150) {
|
|
||||||
genericName = "Moonlight"
|
|
||||||
} else if (value <= 5000) {
|
|
||||||
genericName = "Cool White"
|
|
||||||
} else {
|
|
||||||
genericName = "Daylight"
|
|
||||||
}
|
|
||||||
sendEvent(name: "colorName", value: genericName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 SmartThings
|
|
||||||
*
|
|
||||||
* 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: "Z-Wave Dimmer Switch Generic", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
status "on": "command: 2003, payload: FF"
|
|
||||||
status "off": "command: 2003, payload: 00"
|
|
||||||
status "09%": "command: 2003, payload: 09"
|
|
||||||
status "10%": "command: 2003, payload: 0A"
|
|
||||||
status "33%": "command: 2003, payload: 21"
|
|
||||||
status "66%": "command: 2003, payload: 42"
|
|
||||||
status "99%": "command: 2003, payload: 63"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "2001FF,delay 5000,2602": "command: 2603, payload: FF"
|
|
||||||
reply "200100,delay 5000,2602": "command: 2603, payload: 00"
|
|
||||||
reply "200119,delay 5000,2602": "command: 2603, payload: 19"
|
|
||||||
reply "200132,delay 5000,2602": "command: 2603, payload: 32"
|
|
||||||
reply "20014B,delay 5000,2602": "command: 2603, payload: 4B"
|
|
||||||
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
|
|
||||||
}
|
|
||||||
|
|
||||||
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.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "level", label:'${currentValue} %', unit:"%", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["switch"])
|
|
||||||
details(["switch", "level", "refresh"])
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
def result = null
|
|
||||||
if (description != "updated") {
|
|
||||||
log.debug "parse() >> zwave.parse($description)"
|
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
|
|
||||||
if (cmd) {
|
|
||||||
result = zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
|
|
||||||
result = [result, response(zwave.basicV1.basicGet())]
|
|
||||||
log.debug "Was hailed: requesting state update"
|
|
||||||
} else {
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
|
||||||
dimmerEvents(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
|
||||||
dimmerEvents(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd) {
|
|
||||||
dimmerEvents(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd) {
|
|
||||||
dimmerEvents(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
private dimmerEvents(physicalgraph.zwave.Command cmd) {
|
|
||||||
def value = (cmd.value ? "on" : "off")
|
|
||||||
def result = [createEvent(name: "switch", value: value)]
|
|
||||||
if (cmd.value && cmd.value <= 100) {
|
|
||||||
result << createEvent(name: "level", value: cmd.value, unit: "%")
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
|
|
||||||
log.debug "ConfigurationReport $cmd"
|
|
||||||
def value = "when off"
|
|
||||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
|
||||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
|
||||||
createEvent([name: "indicatorStatus", value: value])
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
|
||||||
createEvent([name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false])
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport 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)
|
|
||||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
|
||||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd) {
|
|
||||||
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
|
||||||
// Handles all Z-Wave commands we aren't interested in
|
|
||||||
[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
delayBetween([
|
|
||||||
zwave.basicV1.basicSet(value: 0xFF).format(),
|
|
||||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
|
||||||
],5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
delayBetween([
|
|
||||||
zwave.basicV1.basicSet(value: 0x00).format(),
|
|
||||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
|
||||||
],5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value) {
|
|
||||||
log.debug "setLevel >> value: $value"
|
|
||||||
def valueaux = value as Integer
|
|
||||||
def level = Math.max(Math.min(valueaux, 99), 0)
|
|
||||||
if (level > 0) {
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
} else {
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
}
|
|
||||||
sendEvent(name: "level", value: level, unit: "%")
|
|
||||||
delayBetween ([zwave.basicV1.basicSet(value: level).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLevel(value, duration) {
|
|
||||||
log.debug "setLevel >> value: $value, duration: $duration"
|
|
||||||
def valueaux = value as Integer
|
|
||||||
def level = Math.max(Math.min(valueaux, 99), 0)
|
|
||||||
def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
|
|
||||||
def getStatusDelay = duration < 128 ? (duration*1000)+2000 : (Math.round(duration / 60)*60*1000)+2000
|
|
||||||
delayBetween ([zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format(),
|
|
||||||
zwave.switchMultilevelV1.switchMultilevelGet().format()], getStatusDelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll() {
|
|
||||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "refresh() is called"
|
|
||||||
def commands = []
|
|
||||||
commands << zwave.switchMultilevelV1.switchMultilevelGet().format()
|
|
||||||
if (getDataValue("MSR") == null) {
|
|
||||||
commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
|
||||||
}
|
|
||||||
delayBetween(commands,100)
|
|
||||||
}
|
|
||||||
|
|
||||||
def invertSwitch(invert=true) {
|
|
||||||
if (invert) {
|
|
||||||
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,9 +25,7 @@ metadata {
|
|||||||
|
|
||||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x80,0x84,0x85,0x86,0x72"
|
fingerprint deviceId: "0x2001", inClusters: "0x30,0x80,0x84,0x85,0x86,0x72"
|
||||||
fingerprint deviceId: "0x07", inClusters: "0x30"
|
fingerprint deviceId: "0x07", inClusters: "0x30"
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98"
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
|
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -35,7 +33,6 @@ metadata {
|
|||||||
// status messages
|
// status messages
|
||||||
status "open": "command: 2001, payload: FF"
|
status "open": "command: 2001, payload: FF"
|
||||||
status "closed": "command: 2001, payload: 00"
|
status "closed": "command: 2001, payload: 00"
|
||||||
status "wake up": "command: 8407, payload: "
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
@@ -81,7 +78,7 @@ def updated() {
|
|||||||
def cmds = []
|
def cmds = []
|
||||||
if (!state.MSR) {
|
if (!state.MSR) {
|
||||||
cmds = [
|
cmds = [
|
||||||
command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()),
|
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
|
||||||
"delay 1200",
|
"delay 1200",
|
||||||
zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||||
]
|
]
|
||||||
@@ -94,9 +91,9 @@ def updated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
commands([
|
delayBetween([
|
||||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
|
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
|
||||||
zwave.batteryV1.batteryGet()
|
batteryGetCommand()
|
||||||
], 6000)
|
], 6000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,11 +144,12 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
|||||||
result << sensorValueEvent(1)
|
result << sensorValueEvent(1)
|
||||||
} else if (cmd.event == 0x03) {
|
} else if (cmd.event == 0x03) {
|
||||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||||
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
|
||||||
|
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||||
} else if (cmd.event == 0x07) {
|
} else if (cmd.event == 0x07) {
|
||||||
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||||
result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion")
|
result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion")
|
||||||
}
|
}
|
||||||
} else if (cmd.notificationType) {
|
} else if (cmd.notificationType) {
|
||||||
@@ -169,11 +167,12 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
|||||||
def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
|
def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
|
||||||
def cmds = []
|
def cmds = []
|
||||||
if (!state.MSR) {
|
if (!state.MSR) {
|
||||||
cmds << command(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId).format()
|
||||||
|
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()
|
||||||
cmds << "delay 1200"
|
cmds << "delay 1200"
|
||||||
}
|
}
|
||||||
if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {
|
if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {
|
||||||
cmds << command(zwave.batteryV1.batteryGet())
|
cmds << batteryGetCommand()
|
||||||
} else {
|
} else {
|
||||||
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||||
}
|
}
|
||||||
@@ -210,7 +209,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
|||||||
if (msr == "0086-0102-0059") {
|
if (msr == "0086-0102-0059") {
|
||||||
result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
|
result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
|
||||||
} else {
|
} else {
|
||||||
result << response(command(zwave.batteryV1.batteryGet()))
|
result << response(batteryGetCommand())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +217,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
|||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1])
|
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1])
|
||||||
// log.debug "encapsulated: $encapsulatedCommand"
|
// log.debug "encapsulated: $encapsulatedCommand"
|
||||||
if (encapsulatedCommand) {
|
if (encapsulatedCommand) {
|
||||||
state.sec = 1
|
state.sec = 1
|
||||||
@@ -230,44 +229,28 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
|||||||
createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)
|
createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private command(physicalgraph.zwave.Command cmd) {
|
def batteryGetCommand() {
|
||||||
if (state.sec == 1) {
|
def cmd = zwave.batteryV1.batteryGet()
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
if (state.sec) {
|
||||||
} else {
|
cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
|
||||||
cmd.format()
|
|
||||||
}
|
}
|
||||||
}
|
cmd.format()
|
||||||
|
|
||||||
private commands(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ command(it) }, delay)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def retypeBasedOnMSR() {
|
def retypeBasedOnMSR() {
|
||||||
switch (state.MSR) {
|
switch (state.MSR) {
|
||||||
case "0086-0002-002D":
|
case "0086-0002-002D":
|
||||||
log.debug "Changing device type to Z-Wave Water Sensor"
|
log.debug("Changing device type to Z-Wave Water Sensor")
|
||||||
setDeviceType("Z-Wave Water Sensor")
|
setDeviceType("Z-Wave Water Sensor")
|
||||||
break
|
break
|
||||||
case "011F-0001-0001": // Schlage motion
|
case "011F-0001-0001": // Schlage motion
|
||||||
case "014A-0001-0001": // Ecolink motion
|
case "014A-0001-0001": // Ecolink motion
|
||||||
case "014A-0004-0001": // Ecolink motion +
|
|
||||||
case "0060-0001-0002": // Everspring SP814
|
case "0060-0001-0002": // Everspring SP814
|
||||||
case "0060-0001-0003": // Everspring HSP02
|
case "0060-0001-0003": // Everspring HSP02
|
||||||
case "011A-0601-0901": // Enerwave ZWN-BPC
|
case "011A-0601-0901": // Enerwave ZWN-BPC
|
||||||
log.debug "Changing device type to Z-Wave Motion Sensor"
|
log.debug("Changing device type to Z-Wave Motion Sensor")
|
||||||
setDeviceType("Z-Wave Motion Sensor")
|
setDeviceType("Z-Wave Motion Sensor")
|
||||||
break
|
break
|
||||||
case "013C-0002-000D": // Philio multi +
|
|
||||||
log.debug "Changing device type to 3-in-1 Multisensor Plus (SG)"
|
|
||||||
setDeviceType("3-in-1 Multisensor Plus (SG)")
|
|
||||||
break
|
|
||||||
case "0109-2001-0106": // Vision door/window
|
|
||||||
log.debug "Changing device type to Z-Wave Plus Door/Window Sensor"
|
|
||||||
setDeviceType("Z-Wave Plus Door/Window Sensor")
|
|
||||||
break
|
|
||||||
case "0109-2002-0205": // Vision Motion
|
|
||||||
log.debug "Changing device type to Z-Wave Plus Motion/Temp Sensor"
|
|
||||||
setDeviceType("Z-Wave Plus Motion/Temp Sensor")
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,6 @@ metadata {
|
|||||||
capability "Motion Sensor"
|
capability "Motion Sensor"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
|
|
||||||
fingerprint mfr: "011F", prod: "0001", model: "0001", deviceJoinName: "Schlage Motion Sensor" // Schlage motion
|
|
||||||
fingerprint mfr: "014A", prod: "0001", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion
|
|
||||||
fingerprint mfr: "014A", prod: "0004", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion +
|
|
||||||
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
|
|
||||||
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
|
|
||||||
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -64,7 +57,7 @@ def parse(String description) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
def sensorValueEvent(value) {
|
def sensorValueEvent(Short value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
|
createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
|
||||||
} else {
|
} else {
|
||||||
@@ -101,24 +94,24 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
|||||||
{
|
{
|
||||||
def result = []
|
def result = []
|
||||||
if (cmd.notificationType == 0x07) {
|
if (cmd.notificationType == 0x07) {
|
||||||
if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice ensors
|
if (cmd.event == 0x01 || cmd.event == 0x02) {
|
||||||
result << sensorValueEvent(cmd.v1AlarmLevel)
|
|
||||||
} else if (cmd.event == 0x01 || cmd.event == 0x02 || cmd.event == 0x07 || cmd.event == 0x08) {
|
|
||||||
result << sensorValueEvent(1)
|
result << sensorValueEvent(1)
|
||||||
} else if (cmd.event == 0x00) {
|
|
||||||
result << sensorValueEvent(0)
|
|
||||||
} else if (cmd.event == 0x03) {
|
} else if (cmd.event == 0x03) {
|
||||||
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||||
result << response(zwave.batteryV1.batteryGet())
|
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
|
||||||
|
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||||
|
} else if (cmd.event == 0x07) {
|
||||||
|
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||||
|
result << sensorValueEvent(1)
|
||||||
}
|
}
|
||||||
} else if (cmd.notificationType) {
|
} else if (cmd.notificationType) {
|
||||||
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
|
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
|
||||||
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, isStateChange: true, displayed: false)
|
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
|
||||||
} else {
|
} else {
|
||||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
||||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false)
|
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@@ -132,9 +125,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
|||||||
}
|
}
|
||||||
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
||||||
result << response(zwave.batteryV1.batteryGet())
|
result << response(zwave.batteryV1.batteryGet())
|
||||||
} else {
|
result << response("delay 1200")
|
||||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
}
|
}
|
||||||
|
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,270 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
* Copyright 2015 AstraLink
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* Z-Wave Plus Door/Window Sensor, ZD2102*-5
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "Z-Wave Plus Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Contact Sensor"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Battery"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
// for Astralink
|
|
||||||
attribute "ManufacturerCode", "string"
|
|
||||||
attribute "ProduceTypeCode", "string"
|
|
||||||
attribute "ProductCode", "string"
|
|
||||||
attribute "WakeUp", "string"
|
|
||||||
attribute "WirelessConfig", "string"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x70, 0x84, 0x7A"
|
|
||||||
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,71"
|
|
||||||
fingerprint mfr:"0109", prod:"2001", model:"0106" // not using deviceJoinName because it's sold under different brand names
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
|
||||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
|
||||||
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
main (["contact"])
|
|
||||||
details(["contact","battery"])
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
// messages the device returns in response to commands it receives
|
|
||||||
status "open (basic)" : "command: 9881, payload: 00 20 01 FF"
|
|
||||||
status "closed (basic)" : "command: 9881 payload: 00 20 01 00"
|
|
||||||
status "open (notification)" : "command: 9881, payload: 00 71 05 06 FF 00 FF 06 16 00 00"
|
|
||||||
status "closed (notification)" : "command: 9881, payload: 00 71 05 06 00 00 FF 06 17 00 00"
|
|
||||||
status "tamper: enclosure opened" : "command: 9881, payload: 00 71 05 07 FF 00 FF 07 03 00 00"
|
|
||||||
status "tamper: enclosure replaced" : "command: 9881, payload: 00 71 05 07 00 00 FF 07 00 00 00"
|
|
||||||
status "wake up" : "command: 9881, payload: 00 84 07"
|
|
||||||
status "battery (100%)" : "command: 9881, payload: 00 80 03 64"
|
|
||||||
status "battery low" : "command: 9881, payload: 00 80 03 FF"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "configure()"
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
if (state.sec != 1) {
|
|
||||||
// secure inclusion may not be complete yet
|
|
||||||
cmds << "delay 1000"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds += secureSequence([
|
|
||||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
|
|
||||||
zwave.batteryV1.batteryGet(),
|
|
||||||
], 500)
|
|
||||||
|
|
||||||
cmds << "delay 8000"
|
|
||||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
return cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCommandClassVersions() {
|
|
||||||
[
|
|
||||||
0x71: 3, // Notification
|
|
||||||
0x5E: 2, // ZwaveplusInfo
|
|
||||||
0x59: 1, // AssociationGrpInfo
|
|
||||||
0x85: 2, // Association
|
|
||||||
0x20: 1, // Basic
|
|
||||||
0x80: 1, // Battery
|
|
||||||
0x70: 1, // Configuration
|
|
||||||
0x5A: 1, // DeviceResetLocally
|
|
||||||
0x7A: 2, // FirmwareUpdateMd
|
|
||||||
0x72: 2, // ManufacturerSpecific
|
|
||||||
0x73: 1, // Powerlevel
|
|
||||||
0x98: 1, // Security
|
|
||||||
0x84: 2, // WakeUp
|
|
||||||
0x86: 1, // Version
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
def result = []
|
|
||||||
def cmd
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
state.sec = 0
|
|
||||||
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
|
|
||||||
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.startsWith("Err")) {
|
|
||||||
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
|
|
||||||
} else {
|
|
||||||
cmd = zwave.parse(description, commandClassVersions)
|
|
||||||
if (cmd) {
|
|
||||||
result = zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result instanceof List) {
|
|
||||||
result = result.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug "Parsed '$description' to $result"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
|
|
||||||
log.debug "encapsulated: $encapsulatedCommand"
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
state.sec = 1
|
|
||||||
return zwaveEvent(encapsulatedCommand)
|
|
||||||
} else {
|
|
||||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
|
||||||
return [createEvent(descriptionText: cmd.toString())]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def sensorValueEvent(value) {
|
|
||||||
if (value) {
|
|
||||||
createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")
|
|
||||||
} else {
|
|
||||||
createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
|
||||||
return sensorValueEvent(cmd.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
|
||||||
return sensorValueEvent(cmd.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
|
|
||||||
return sensorValueEvent(cmd.sensorValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
|
|
||||||
return sensorValueEvent(cmd.sensorState)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
|
||||||
def result = []
|
|
||||||
if (cmd.notificationType == 0x06 && cmd.event == 0x16) {
|
|
||||||
result << sensorValueEvent(1)
|
|
||||||
} else if (cmd.notificationType == 0x06 && cmd.event == 0x17) {
|
|
||||||
result << sensorValueEvent(0)
|
|
||||||
} else if (cmd.notificationType == 0x07) {
|
|
||||||
if (cmd.event == 0x00) {
|
|
||||||
if (cmd.eventParametersLength == 0 || cmd.eventParameter[0] != 3) {
|
|
||||||
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
|
|
||||||
} else {
|
|
||||||
result << sensorValueEvent(0)
|
|
||||||
}
|
|
||||||
} else if (cmd.event == 0x01 || cmd.event == 0x02) {
|
|
||||||
result << sensorValueEvent(1)
|
|
||||||
} else if (cmd.event == 0x03) {
|
|
||||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
|
||||||
if (!device.currentState("ManufacturerCode")) {
|
|
||||||
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
|
||||||
}
|
|
||||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
|
||||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
|
||||||
} else {
|
|
||||||
result << createEvent(descriptionText: "$device.displayName event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
|
|
||||||
}
|
|
||||||
} else if (cmd.notificationType) {
|
|
||||||
result << createEvent(descriptionText: "$device.displayName notification $cmd.notificationType event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
|
|
||||||
} else {
|
|
||||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
|
||||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
|
||||||
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
if (!device.currentState("ManufacturerCode")) {
|
|
||||||
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
|
||||||
cmds << "delay 2000"
|
|
||||||
}
|
|
||||||
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
|
|
||||||
event.descriptionText += ", requesting battery"
|
|
||||||
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
|
|
||||||
cmds << "delay 800"
|
|
||||||
cmds << secure(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 2000"
|
|
||||||
} else {
|
|
||||||
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
|
|
||||||
}
|
|
||||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
|
|
||||||
return [event, response(cmds)]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [ name: "battery", unit: "%" ]
|
|
||||||
if (cmd.batteryLevel == 0xFF) {
|
|
||||||
map.value = 1
|
|
||||||
map.descriptionText = "${device.displayName} has a low battery"
|
|
||||||
map.isStateChange = true
|
|
||||||
} else {
|
|
||||||
map.value = cmd.batteryLevel
|
|
||||||
}
|
|
||||||
def event = createEvent(map)
|
|
||||||
|
|
||||||
// Save at least one battery report in events list every few days
|
|
||||||
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
|
|
||||||
map.isStateChange = true
|
|
||||||
}
|
|
||||||
state.lastbat = now()
|
|
||||||
return [event]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
def result = []
|
|
||||||
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
|
|
||||||
def productTypeCode = String.format("%04X", cmd.productTypeId)
|
|
||||||
def productCode = String.format("%04X", cmd.productId)
|
|
||||||
def wirelessConfig = "ZWP"
|
|
||||||
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
|
|
||||||
|
|
||||||
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
|
|
||||||
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
|
|
||||||
result << createEvent(name: "ProductCode", value: productCode)
|
|
||||||
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
|
||||||
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
if (state.sec == 0) { // default to secure
|
|
||||||
cmd.format()
|
|
||||||
} else {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private secureSequence(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ secure(it) }, delay)
|
|
||||||
}
|
|
||||||
@@ -1,327 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
* Copyright 2015 AstraLink
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* Z-Wave Plus Motion Sensor with Temperature Measurement, ZP3102*-5
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "Z-Wave Plus Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Motion Sensor"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Battery"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
// for Astralink
|
|
||||||
attribute "ManufacturerCode", "string"
|
|
||||||
attribute "ProduceTypeCode", "string"
|
|
||||||
attribute "ProductCode", "string"
|
|
||||||
attribute "WakeUp", "string"
|
|
||||||
attribute "WirelessConfig", "string"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x31, 0x70, 0x84, 0x7A"
|
|
||||||
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,31,71"
|
|
||||||
fingerprint mfr:"0109", prod:"2002", model:"0205" // not using deviceJoinName because it's sold under different brand names
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles {
|
|
||||||
standardTile("motion", "device.motion", width: 3, 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"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("temperature", "device.temperature", inactiveLabel: false) {
|
|
||||||
state "temperature", label:'${currentValue}°',
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 31, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:"%"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["motion", "temperature"])
|
|
||||||
details(["motion", "temperature", "battery"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
if (!device.currentState("ManufacturerCode")) {
|
|
||||||
response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "configure()"
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
if (state.sec != 1) {
|
|
||||||
// secure inclusion may not be complete yet
|
|
||||||
cmds << "delay 1000"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds += secureSequence([
|
|
||||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
|
|
||||||
zwave.batteryV1.batteryGet(),
|
|
||||||
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
|
|
||||||
], 500)
|
|
||||||
|
|
||||||
cmds << "delay 8000"
|
|
||||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
return cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCommandClassVersions() {
|
|
||||||
[
|
|
||||||
0x71: 3, // Notification
|
|
||||||
0x5E: 2, // ZwaveplusInfo
|
|
||||||
0x59: 1, // AssociationGrpInfo
|
|
||||||
0x85: 2, // Association
|
|
||||||
0x20: 1, // Basic
|
|
||||||
0x80: 1, // Battery
|
|
||||||
0x70: 1, // Configuration
|
|
||||||
0x5A: 1, // DeviceResetLocally
|
|
||||||
0x7A: 2, // FirmwareUpdateMd
|
|
||||||
0x72: 2, // ManufacturerSpecific
|
|
||||||
0x73: 1, // Powerlevel
|
|
||||||
0x98: 1, // Security
|
|
||||||
0x31: 5, // SensorMultilevel
|
|
||||||
0x84: 2 // WakeUp
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
def result = []
|
|
||||||
def cmd
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
state.sec = 0
|
|
||||||
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
|
|
||||||
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.startsWith("Err")) {
|
|
||||||
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
|
|
||||||
} else {
|
|
||||||
cmd = zwave.parse(description, commandClassVersions)
|
|
||||||
if (cmd) {
|
|
||||||
result = zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result instanceof List) {
|
|
||||||
result = result.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug "Parsed '$description' to $result"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
|
|
||||||
log.debug "encapsulated: $encapsulatedCommand"
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
state.sec = 1
|
|
||||||
return zwaveEvent(encapsulatedCommand)
|
|
||||||
} else {
|
|
||||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
|
||||||
return [createEvent(descriptionText: cmd.toString())]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def sensorValueEvent(value) {
|
|
||||||
def result = []
|
|
||||||
if (value) {
|
|
||||||
log.debug "sensorValueEvent($value) : active"
|
|
||||||
result << createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
|
|
||||||
} else {
|
|
||||||
log.debug "sensorValueEvent($value) : inactive"
|
|
||||||
result << createEvent(name: "motion", value: "inactive", descriptionText: "$device.displayName motion has stopped")
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
|
||||||
return sensorValueEvent(cmd.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
|
||||||
return sensorValueEvent(cmd.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
|
|
||||||
return sensorValueEvent(cmd.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
|
|
||||||
return sensorValueEvent(cmd.sensorValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
|
|
||||||
return sensorValueEvent(cmd.sensorState)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
|
||||||
def result = []
|
|
||||||
if (cmd.notificationType == 0x07) {
|
|
||||||
if (cmd.event == 0x01 || cmd.event == 0x02) {
|
|
||||||
result << sensorValueEvent(1)
|
|
||||||
} else if (cmd.event == 0x03) {
|
|
||||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
|
||||||
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
|
||||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
|
||||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
|
||||||
} else if (cmd.event == 0x07) {
|
|
||||||
result << sensorValueEvent(1)
|
|
||||||
} else if (cmd.event == 0x08) {
|
|
||||||
result << sensorValueEvent(1)
|
|
||||||
} else if (cmd.event == 0x00) {
|
|
||||||
if (cmd.eventParametersLength && cmd.eventParameter[0] == 3) {
|
|
||||||
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
|
|
||||||
} else {
|
|
||||||
result << sensorValueEvent(0)
|
|
||||||
}
|
|
||||||
} else if (cmd.event == 0xFF) {
|
|
||||||
result << sensorValueEvent(1)
|
|
||||||
} else {
|
|
||||||
result << createEvent(descriptionText: "$device.displayName sent event $cmd.event")
|
|
||||||
}
|
|
||||||
} else if (cmd.notificationType) {
|
|
||||||
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
|
|
||||||
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
|
|
||||||
} else {
|
|
||||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
|
||||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
|
||||||
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
if (!device.currentState("ManufacturerCode")) {
|
|
||||||
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
|
||||||
cmds << "delay 2000"
|
|
||||||
}
|
|
||||||
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
|
|
||||||
event.descriptionText += ", requesting battery"
|
|
||||||
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
|
|
||||||
cmds << "delay 800"
|
|
||||||
cmds << secure(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 2000"
|
|
||||||
} else {
|
|
||||||
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
|
|
||||||
}
|
|
||||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
|
|
||||||
return [event, response(cmds)]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def result = []
|
|
||||||
def map = [ name: "battery", unit: "%" ]
|
|
||||||
if (cmd.batteryLevel == 0xFF) {
|
|
||||||
map.value = 1
|
|
||||||
map.descriptionText = "${device.displayName} has a low battery"
|
|
||||||
map.isStateChange = true
|
|
||||||
} else {
|
|
||||||
map.value = cmd.batteryLevel
|
|
||||||
}
|
|
||||||
def event = createEvent(map)
|
|
||||||
|
|
||||||
// Save at least one battery report in events list every few days
|
|
||||||
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
|
|
||||||
map.isStateChange = true
|
|
||||||
}
|
|
||||||
state.lastbat = now()
|
|
||||||
return [event]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
|
||||||
def result = []
|
|
||||||
def map = [:]
|
|
||||||
switch (cmd.sensorType) {
|
|
||||||
case 1:
|
|
||||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
|
||||||
map.name = "temperature"
|
|
||||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
|
||||||
map.unit = getTemperatureScale()
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
map.name = "illuminance"
|
|
||||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
|
||||||
map.unit = "lux"
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
map.name = "humidity"
|
|
||||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
|
||||||
map.unit = cmd.scale == 0 ? "%" : ""
|
|
||||||
break;
|
|
||||||
case 0x1E:
|
|
||||||
map.name = "loudness"
|
|
||||||
map.unit = cmd.scale == 1 ? "dBA" : "dB"
|
|
||||||
map.value = cmd.scaledSensorValue.toString()
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
map.descriptionText = cmd.toString()
|
|
||||||
}
|
|
||||||
result << createEvent(map)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
def result = []
|
|
||||||
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
|
|
||||||
def productTypeCode = String.format("%04X", cmd.productTypeId)
|
|
||||||
def productCode = String.format("%04X", cmd.productId)
|
|
||||||
def wirelessConfig = "ZWP"
|
|
||||||
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
|
|
||||||
|
|
||||||
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
|
|
||||||
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
|
|
||||||
result << createEvent(name: "ProductCode", value: productCode)
|
|
||||||
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
|
|
||||||
|
|
||||||
if (manufacturerCode == "0109" && productTypeCode == "2002") {
|
|
||||||
result << response(secureSequence([
|
|
||||||
// Change re-trigger duration to 1 minute
|
|
||||||
zwave.configurationV1.configurationSet(parameterNumber: 1, configurationValue: [1], size: 1),
|
|
||||||
zwave.batteryV1.batteryGet(),
|
|
||||||
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
|
|
||||||
], 400))
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
|
||||||
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
if (state.sec == 0) { // default to secure
|
|
||||||
cmd.format()
|
|
||||||
} else {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private secureSequence(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ secure(it) }, delay)
|
|
||||||
}
|
|
||||||
@@ -61,44 +61,37 @@ def parse(String description) {
|
|||||||
zwaveEvent(cmd, results)
|
zwaveEvent(cmd, results)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug "'$description' parsed to ${results.inspect()}"
|
// log.debug "\"$description\" parsed to ${results.inspect()}"
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def createSmokeOrCOEvents(name, results) {
|
def createSmokeOrCOEvents(name, results) {
|
||||||
def text = null
|
def text = null
|
||||||
switch (name) {
|
if (name == "smoke") {
|
||||||
case "smoke":
|
text = "$device.displayName smoke was detected!"
|
||||||
text = "$device.displayName smoke was detected!"
|
// these are displayed:false because the composite event is the one we want to see in the app
|
||||||
// these are displayed:false because the composite event is the one we want to see in the app
|
results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false)
|
||||||
results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false)
|
} else if (name == "carbonMonoxide") {
|
||||||
break
|
text = "$device.displayName carbon monoxide was detected!"
|
||||||
case "carbonMonoxide":
|
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
|
||||||
text = "$device.displayName carbon monoxide was detected!"
|
} else if (name == "tested") {
|
||||||
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
|
text = "$device.displayName was tested"
|
||||||
break
|
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
|
||||||
case "tested":
|
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
|
||||||
text = "$device.displayName was tested"
|
} else if (name == "smokeClear") {
|
||||||
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
|
text = "$device.displayName smoke is clear"
|
||||||
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
|
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||||
break
|
name = "clear"
|
||||||
case "smokeClear":
|
} else if (name == "carbonMonoxideClear") {
|
||||||
text = "$device.displayName smoke is clear"
|
text = "$device.displayName carbon monoxide is clear"
|
||||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
|
||||||
name = "clear"
|
name = "clear"
|
||||||
break
|
} else if (name == "testClear") {
|
||||||
case "carbonMonoxideClear":
|
text = "$device.displayName smoke is clear"
|
||||||
text = "$device.displayName carbon monoxide is clear"
|
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||||
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
|
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
|
||||||
name = "clear"
|
name = "clear"
|
||||||
break
|
|
||||||
case "testClear":
|
|
||||||
text = "$device.displayName test cleared"
|
|
||||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
|
||||||
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
|
|
||||||
name = "clear"
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
// This composite event is used for updating the tile
|
// This composite event is used for updating the tile
|
||||||
results << createEvent(name: "alarmState", value: name, descriptionText: text)
|
results << createEvent(name: "alarmState", value: name, descriptionText: text)
|
||||||
@@ -124,10 +117,8 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
|
|||||||
createSmokeOrCOEvents(cmd.alarmLevel ? "tested" : "testClear", results)
|
createSmokeOrCOEvents(cmd.alarmLevel ? "tested" : "testClear", results)
|
||||||
break
|
break
|
||||||
case 13: // sent every hour -- not sure what this means, just a wake up notification?
|
case 13: // sent every hour -- not sure what this means, just a wake up notification?
|
||||||
if (cmd.alarmLevel == 255) {
|
if (cmd.alarmLevel != 255) {
|
||||||
results << createEvent(descriptionText: "$device.displayName checked in", isStateChange: false)
|
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", displayed: true)
|
||||||
} else {
|
|
||||||
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", isStateChange:true, displayed:false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear smoke in case they pulled batteries and we missed the clear msg
|
// Clear smoke in case they pulled batteries and we missed the clear msg
|
||||||
@@ -136,8 +127,9 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check battery if we don't have a recent battery event
|
// Check battery if we don't have a recent battery event
|
||||||
if (!state.lastbatt || (now() - state.lastbatt) >= 48*60*60*1000) {
|
def prevBattery = device.currentState("battery")
|
||||||
results << response(zwave.batteryV1.batteryGet())
|
if (!prevBattery || (new Date().time - prevBattery.date.time)/60000 >= 60 * 53) {
|
||||||
|
results << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
@@ -166,17 +158,12 @@ def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd,
|
|||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) {
|
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) {
|
||||||
|
results << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format())
|
||||||
results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
|
results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
|
||||||
if (!state.lastbatt || (now() - state.lastbatt) >= 56*60*60*1000) {
|
|
||||||
results << response(zwave.batteryV1.batteryGet(), "delay 2000", zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
} else {
|
|
||||||
results << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
|
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
|
||||||
def map = [ name: "battery", unit: "%", isStateChange: true ]
|
def map = [ name: "battery", unit: "%" ]
|
||||||
state.lastbatt = now()
|
|
||||||
if (cmd.batteryLevel == 0xFF) {
|
if (cmd.batteryLevel == 0xFF) {
|
||||||
map.value = 1
|
map.value = 1
|
||||||
map.descriptionText = "$device.displayName battery is low!"
|
map.descriptionText = "$device.displayName battery is low!"
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 SmartThings
|
|
||||||
*
|
|
||||||
* 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: "Z-Wave Switch Generic", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
status "on": "command: 2003, payload: FF"
|
|
||||||
status "off": "command: 2003, payload: 00"
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
reply "2001FF,delay 100,2502": "command: 2503, payload: FF"
|
|
||||||
reply "200100,delay 100,2502": "command: 2503, payload: 00"
|
|
||||||
}
|
|
||||||
|
|
||||||
// tile definitions
|
|
||||||
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.switch.on", backgroundColor: "#79b821"
|
|
||||||
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "switch"
|
|
||||||
details(["switch","refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
def result = null
|
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
|
|
||||||
if (cmd) {
|
|
||||||
result = createEvent(zwaveEvent(cmd))
|
|
||||||
}
|
|
||||||
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
|
|
||||||
result = [result, response(zwave.basicV1.basicGet())]
|
|
||||||
log.debug "Was hailed: requesting state update"
|
|
||||||
} else {
|
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
|
||||||
[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
|
||||||
[name: "switch", value: cmd.value ? "on" : "off", type: "physical"]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
|
|
||||||
[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
|
|
||||||
def value = "when off"
|
|
||||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
|
||||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
|
||||||
[name: "indicatorStatus", value: value, display: false]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
|
||||||
[name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport 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)
|
|
||||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
|
||||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
|
||||||
// Handles all Z-Wave commands we aren't interested in
|
|
||||||
[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
delayBetween([
|
|
||||||
zwave.basicV1.basicSet(value: 0xFF).format(),
|
|
||||||
zwave.switchBinaryV1.switchBinaryGet().format()
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
delayBetween([
|
|
||||||
zwave.basicV1.basicSet(value: 0x00).format(),
|
|
||||||
zwave.switchBinaryV1.switchBinaryGet().format()
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll() {
|
|
||||||
delayBetween([
|
|
||||||
zwave.switchBinaryV1.switchBinaryGet().format(),
|
|
||||||
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
delayBetween([
|
|
||||||
zwave.switchBinaryV1.switchBinaryGet().format(),
|
|
||||||
zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
def invertSwitch(invert=true) {
|
|
||||||
if (invert) {
|
|
||||||
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 4, size: 1).format()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,15 +15,12 @@ metadata {
|
|||||||
definition (name: "Z-Wave Switch", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Z-Wave Switch", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Indicator"
|
capability "Indicator"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Polling"
|
capability "Polling"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch"
|
fingerprint inClusters: "0x25"
|
||||||
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
|
||||||
fingerprint mfr:"0063", prod:"5052", deviceJoinName: "Z-Wave Plug-In Switch"
|
|
||||||
fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -36,10 +33,6 @@ metadata {
|
|||||||
reply "200100,delay 100,2502": "command: 2503, payload: 00"
|
reply "200100,delay 100,2502": "command: 2503, payload: 00"
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
|
||||||
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
|
|
||||||
}
|
|
||||||
|
|
||||||
// tile definitions
|
// tile definitions
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
@@ -59,27 +52,10 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main "switch"
|
main "switch"
|
||||||
details(["switch","refresh"])
|
details(["switch","refresh","indicator"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated(){
|
|
||||||
switch (ledIndicator) {
|
|
||||||
case "on":
|
|
||||||
indicatorWhenOn()
|
|
||||||
break
|
|
||||||
case "off":
|
|
||||||
indicatorWhenOff()
|
|
||||||
break
|
|
||||||
case "never":
|
|
||||||
indicatorNever()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
indicatorWhenOn()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
|
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
|
||||||
@@ -119,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
|
||||||
[:]
|
[:]
|
||||||
@@ -163,19 +133,19 @@ def refresh() {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOn() {
|
def indicatorWhenOn() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOff() {
|
def indicatorWhenOff() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorNever() {
|
def indicatorNever() {
|
||||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
def invertSwitch(invert=true) {
|
def invertSwitch(invert=true) {
|
||||||
|
|||||||
@@ -1,259 +0,0 @@
|
|||||||
/**
|
|
||||||
* Gidjit Hub
|
|
||||||
*
|
|
||||||
* Copyright 2016 Matthew Page
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
|
||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing permissions and limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
definition(
|
|
||||||
name: "Gidjit Hub",
|
|
||||||
namespace: "com.gidjit.smartthings.hub",
|
|
||||||
author: "Matthew Page",
|
|
||||||
description: "Act as an endpoint so user's of Gidjit can quickly access and control their devices and execute routines. Users can do this quickly as Gidjit filters these actions based on their environment",
|
|
||||||
category: "Convenience",
|
|
||||||
iconUrl: "http://www.gidjit.com/appicon.png",
|
|
||||||
iconX2Url: "http://www.gidjit.com/appicon@2x.png",
|
|
||||||
iconX3Url: "http://www.gidjit.com/appicon@3x.png",
|
|
||||||
oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"])
|
|
||||||
|
|
||||||
preferences(oauthPage: "deviceAuthorization") {
|
|
||||||
// deviceAuthorization page is simply the devices to authorize
|
|
||||||
page(name: "deviceAuthorization", title: "Device Authorization", nextPage: "instructionPage",
|
|
||||||
install: false, uninstall: true) {
|
|
||||||
section ("Allow Gidjit to have access, thereby allowing you to quickly control and monitor your following devices. Privacy Policy can be found at http://priv.gidjit.com/privacy.html") {
|
|
||||||
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
|
|
||||||
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
|
|
||||||
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
page(name: "instructionPage", title: "Device Discovery", install: true) {
|
|
||||||
section() {
|
|
||||||
paragraph "Now the process is complete return to the Devices section of the Detected Screen. From there and you can add actions to each of your device panels, including launching SmartThings routines."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mappings {
|
|
||||||
path("/structureinfo") {
|
|
||||||
action: [
|
|
||||||
GET: "structureInfo"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/helloactions") {
|
|
||||||
action: [
|
|
||||||
GET: "helloActions"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/helloactions/:label") {
|
|
||||||
action: [
|
|
||||||
PUT: "executeAction"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
path("/switch/:id/:command") {
|
|
||||||
action: [
|
|
||||||
PUT: "updateSwitch"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
path("/thermostat/:id/:command") {
|
|
||||||
action: [
|
|
||||||
PUT: "updateThermostat"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
path("/windowshade/:id/:command") {
|
|
||||||
action: [
|
|
||||||
PUT: "updateWindowShade"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/acquiredata/:id") {
|
|
||||||
action: [
|
|
||||||
GET: "acquiredata"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
// subscribe to attributes, devices, locations, etc.
|
|
||||||
}
|
|
||||||
def helloActions() {
|
|
||||||
def actions = location.helloHome?.getPhrases()*.label
|
|
||||||
if(!actions) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return actions
|
|
||||||
}
|
|
||||||
def executeAction() {
|
|
||||||
def actions = location.helloHome?.getPhrases()*.label
|
|
||||||
def a = actions?.find() { it == params.label }
|
|
||||||
if (!a) {
|
|
||||||
httpError(400, "invalid label $params.label")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
location.helloHome?.execute(params.label)
|
|
||||||
}
|
|
||||||
/* this is the primary function called to query at the structure and its devices */
|
|
||||||
def structureInfo() { //list all devices
|
|
||||||
def list = [:]
|
|
||||||
def currId = location.id
|
|
||||||
list[currId] = [:]
|
|
||||||
list[currId].name = location.name
|
|
||||||
list[currId].id = location.id
|
|
||||||
list[currId].temperatureScale = location.temperatureScale
|
|
||||||
list[currId].devices = [:]
|
|
||||||
|
|
||||||
def setValues = {
|
|
||||||
if (params.brief) {
|
|
||||||
return [id: it.id, name: it.displayName]
|
|
||||||
}
|
|
||||||
def newList = [id: it.id, name: it.displayName, suppCapab: it.capabilities.collect {
|
|
||||||
"$it.name"
|
|
||||||
}, suppAttributes: it.supportedAttributes.collect {
|
|
||||||
"$it.name"
|
|
||||||
}, suppCommands: it.supportedCommands.collect {
|
|
||||||
"$it.name"
|
|
||||||
}]
|
|
||||||
|
|
||||||
return newList
|
|
||||||
}
|
|
||||||
switches?.each {
|
|
||||||
list[currId].devices[it.id] = setValues(it)
|
|
||||||
}
|
|
||||||
thermostats?.each {
|
|
||||||
list[currId].devices[it.id] = setValues(it)
|
|
||||||
}
|
|
||||||
windowShades?.each {
|
|
||||||
list[currId].devices[it.id] = setValues(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
return list
|
|
||||||
|
|
||||||
}
|
|
||||||
/* This function returns all of the current values of the specified Devices attributes */
|
|
||||||
def acquiredata() {
|
|
||||||
def resp = [:]
|
|
||||||
if (!params.id) {
|
|
||||||
httpError(400, "invalid id $params.id")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
def dev = switches.find() { it.id == params.id } ?: windowShades.find() { it.id == params.id } ?:
|
|
||||||
thermostats.find() { it.id == params.id }
|
|
||||||
|
|
||||||
if (!dev) {
|
|
||||||
httpError(400, "invalid id $params.id")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
def att = dev.supportedAttributes
|
|
||||||
att.each {
|
|
||||||
resp[it.name] = dev.currentValue("$it.name")
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateSwitch() {
|
|
||||||
// use the built-in request object to get the command parameter
|
|
||||||
def command = params.command
|
|
||||||
def sw = switches.find() { it.id == params.id }
|
|
||||||
if (!sw) {
|
|
||||||
httpError(400, "invalid id $params.id")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch(command) {
|
|
||||||
case "on":
|
|
||||||
if ( sw.currentSwitch != "on" ) {
|
|
||||||
sw.on()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "off":
|
|
||||||
if ( sw.currentSwitch != "off" ) {
|
|
||||||
sw.off()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
httpError(400, "$command is not a valid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void updateThermostat() {
|
|
||||||
// use the built-in request object to get the command parameter
|
|
||||||
def command = params.command
|
|
||||||
def therm = thermostats.find() { it.id == params.id }
|
|
||||||
if (!therm || !command) {
|
|
||||||
httpError(400, "invalid id $params.id")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
def passComm = [
|
|
||||||
"off",
|
|
||||||
"heat",
|
|
||||||
"emergencyHeat",
|
|
||||||
"cool",
|
|
||||||
"fanOn",
|
|
||||||
"fanAuto",
|
|
||||||
"fanCirculate",
|
|
||||||
"auto"
|
|
||||||
|
|
||||||
]
|
|
||||||
def passNumParamComm = [
|
|
||||||
"setHeatingSetpoint",
|
|
||||||
"setCoolingSetpoint",
|
|
||||||
]
|
|
||||||
def passStringParamComm = [
|
|
||||||
"setThermostatMode",
|
|
||||||
"setThermostatFanMode",
|
|
||||||
]
|
|
||||||
if (command in passComm) {
|
|
||||||
therm."$command"()
|
|
||||||
} else if (command in passNumParamComm && params.p1 && params.p1.isFloat()) {
|
|
||||||
therm."$command"(Float.parseFloat(params.p1))
|
|
||||||
} else if (command in passStringParamComm && params.p1) {
|
|
||||||
therm."$command"(params.p1)
|
|
||||||
} else {
|
|
||||||
httpError(400, "$command is not a valid command")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateWindowShade() {
|
|
||||||
// use the built-in request object to get the command parameter
|
|
||||||
def command = params.command
|
|
||||||
def ws = windowShades.find() { it.id == params.id }
|
|
||||||
if (!ws || !command) {
|
|
||||||
httpError(400, "invalid id $params.id")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
def passComm = [
|
|
||||||
"open",
|
|
||||||
"close",
|
|
||||||
"presetPosition",
|
|
||||||
]
|
|
||||||
if (command in passComm) {
|
|
||||||
ws."$command"()
|
|
||||||
} else {
|
|
||||||
httpError(400, "$command is not a valid command")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: implement event handlers
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,10 +371,10 @@ def initialize() {
|
|||||||
|
|
||||||
settings.devices.each {
|
settings.devices.each {
|
||||||
def deviceId = it
|
def deviceId = it
|
||||||
def detail = state?.deviceDetail[deviceId]
|
def detail = state.deviceDetail[deviceId]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch(detail?.type) {
|
switch(detail.type) {
|
||||||
case 'NAMain':
|
case 'NAMain':
|
||||||
log.debug "Base station"
|
log.debug "Base station"
|
||||||
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
|
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
|
||||||
@@ -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
|
||||||
]
|
]
|
||||||
@@ -487,12 +521,12 @@ def poll() {
|
|||||||
log.debug "State: ${state.deviceState}"
|
log.debug "State: ${state.deviceState}"
|
||||||
|
|
||||||
settings.devices.each { deviceId ->
|
settings.devices.each { deviceId ->
|
||||||
def detail = state?.deviceDetail[deviceId]
|
def detail = state.deviceDetail[deviceId]
|
||||||
def data = state?.deviceState[deviceId]
|
def data = state.deviceState[deviceId]
|
||||||
def child = children?.find { it.deviceNetworkId == deviceId }
|
def child = children.find { it.deviceNetworkId == deviceId }
|
||||||
|
|
||||||
log.debug "Update: $child";
|
log.debug "Update: $child";
|
||||||
switch(detail?.type) {
|
switch(detail.type) {
|
||||||
case 'NAMain':
|
case 'NAMain':
|
||||||
log.debug "Updating NAMain $data"
|
log.debug "Updating NAMain $data"
|
||||||
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
|
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
/**
|
|
||||||
* Door Jammed Notification
|
|
||||||
*
|
|
||||||
* Copyright 2015 John Rucker
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
|
||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing permissions and limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
definition(
|
|
||||||
name: "Door Jammed Notification",
|
|
||||||
namespace: "JohnRucker",
|
|
||||||
author: "John.Rucker@Solar-current.com",
|
|
||||||
description: "Sends a SmartThings notification and text messages when your CoopBoss detects a door jam.",
|
|
||||||
category: "My Apps",
|
|
||||||
iconUrl: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo.png",
|
|
||||||
iconX2Url: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo2x.png",
|
|
||||||
iconX3Url: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo3x.png")
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section("When the door state changes") {
|
|
||||||
paragraph "Send a SmartThings notification when the coop's door jammed and did not close."
|
|
||||||
input "doorSensor", "capability.doorControl", title: "Select CoopBoss", required: true, multiple: false
|
|
||||||
input("recipients", "contact", title: "Recipients", description: "Send notifications to") {
|
|
||||||
input "phone", "phone", title: "Phone number?", required: true}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
unsubscribe()
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
subscribe(doorSensor, "doorState", coopDoorStateHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
def coopDoorStateHandler(evt) {
|
|
||||||
if (evt.value == "jammed"){
|
|
||||||
def msg = "WARNING ${doorSensor.displayName} door is jammed and did not close!"
|
|
||||||
log.debug "WARNING ${doorSensor.displayName} door is jammed and did not close, texting $phone"
|
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
|
||||||
sendNotificationToContacts(msg, recipients)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sendPush(msg)
|
|
||||||
if (phone) {
|
|
||||||
sendSms(phone, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
/**
|
|
||||||
* CoopBoss Door Status to color
|
|
||||||
*
|
|
||||||
* Copyright 2015 John Rucker
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
|
||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing permissions and limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
definition(
|
|
||||||
name: "Door State to Color Light (Hue Bulb)",
|
|
||||||
namespace: "JohnRucker",
|
|
||||||
author: "John Rucker",
|
|
||||||
description: "Change the color of your Hue bulbs based on your coop's door status.",
|
|
||||||
category: "My Apps",
|
|
||||||
iconUrl: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo.png",
|
|
||||||
iconX2Url: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo2x.png",
|
|
||||||
iconX3Url: "http://coopboss.com/images/SmartThingsIcons/coopbossLogo3x.png")
|
|
||||||
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section("When the door opens/closese...") {
|
|
||||||
paragraph "Sets a Hue bulb or bulbs to a color based on your coop's door status:\r unknown = white\r open = blue\r opening = purple\r closed = green\r closing = pink\r jammed = red\r forced close = orange."
|
|
||||||
input "doorSensor", "capability.doorControl", title: "Select CoopBoss", required: true, multiple: false
|
|
||||||
input "bulbs", "capability.colorControl", title: "pick a bulb", required: true, multiple: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
unsubscribe()
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
subscribe(doorSensor, "doorState", coopDoorStateHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
def coopDoorStateHandler(evt) {
|
|
||||||
log.debug "${evt.descriptionText}, $evt.value"
|
|
||||||
def color = "White"
|
|
||||||
def hueColor = 100
|
|
||||||
def saturation = 100
|
|
||||||
Map hClr = [:]
|
|
||||||
hClr.hex = "#FFFFFF"
|
|
||||||
|
|
||||||
switch(evt.value) {
|
|
||||||
case "open":
|
|
||||||
color = "Blue"
|
|
||||||
break;
|
|
||||||
case "opening":
|
|
||||||
color = "Purple"
|
|
||||||
break;
|
|
||||||
case "closed":
|
|
||||||
color = "Green"
|
|
||||||
break;
|
|
||||||
case "closing":
|
|
||||||
color = "Pink"
|
|
||||||
break;
|
|
||||||
case "jammed":
|
|
||||||
color = "Red"
|
|
||||||
break;
|
|
||||||
case "forced close":
|
|
||||||
color = "Orange"
|
|
||||||
break;
|
|
||||||
case "unknown":
|
|
||||||
color = "White"
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(color) {
|
|
||||||
case "White":
|
|
||||||
hueColor = 52
|
|
||||||
saturation = 19
|
|
||||||
break;
|
|
||||||
case "Daylight":
|
|
||||||
hueColor = 53
|
|
||||||
saturation = 91
|
|
||||||
break;
|
|
||||||
case "Soft White":
|
|
||||||
hueColor = 23
|
|
||||||
saturation = 56
|
|
||||||
break;
|
|
||||||
case "Warm White":
|
|
||||||
hueColor = 20
|
|
||||||
saturation = 80 //83
|
|
||||||
break;
|
|
||||||
case "Blue":
|
|
||||||
hueColor = 70
|
|
||||||
hClr.hex = "#0000FF"
|
|
||||||
break;
|
|
||||||
case "Green":
|
|
||||||
hueColor = 39
|
|
||||||
hClr.hex = "#00FF00"
|
|
||||||
break;
|
|
||||||
case "Yellow":
|
|
||||||
hueColor = 25
|
|
||||||
hClr.hex = "#FFFF00"
|
|
||||||
break;
|
|
||||||
case "Orange":
|
|
||||||
hueColor = 10
|
|
||||||
hClr.hex = "#FF6000"
|
|
||||||
break;
|
|
||||||
case "Purple":
|
|
||||||
hueColor = 75
|
|
||||||
hClr.hex = "#BF7FBF"
|
|
||||||
break;
|
|
||||||
case "Pink":
|
|
||||||
hueColor = 83
|
|
||||||
hClr.hex = "#FF5F5F"
|
|
||||||
break;
|
|
||||||
case "Red":
|
|
||||||
hueColor = 100
|
|
||||||
hClr.hex = "#FF0000"
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//bulbs*.on()
|
|
||||||
bulbs*.setHue(hueColor)
|
|
||||||
bulbs*.setSaturation(saturation)
|
|
||||||
bulbs*.setColor(hClr)
|
|
||||||
|
|
||||||
//bulbs.each{
|
|
||||||
//it.on() // Turn the bulb on when open (this method does not come directly from the colorControl capability)
|
|
||||||
//it.setLevel(100) // Make sure the light brightness is 100%
|
|
||||||
//it.setHue(hueColor)
|
|
||||||
//it.setSaturation(saturation)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -21,12 +21,6 @@ preferences()
|
|||||||
section("Allow Simple Control to Monitor and Control These Things...")
|
section("Allow Simple Control to Monitor and Control These Things...")
|
||||||
{
|
{
|
||||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
|
||||||
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
|
|
||||||
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
|
|
||||||
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
|
|
||||||
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
|
|
||||||
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
|
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
|
||||||
@@ -37,17 +31,12 @@ preferences()
|
|||||||
|
|
||||||
mappings {
|
mappings {
|
||||||
path("/devices") {
|
path("/devices") {
|
||||||
action: [
|
|
||||||
GET: "getDevices"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/:deviceType/devices") {
|
|
||||||
action: [
|
action: [
|
||||||
GET: "getDevices",
|
GET: "getDevices",
|
||||||
POST: "handleDevicesWithIDs"
|
POST: "handleDevicesWithIDs"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
path("/device/:deviceType/:id") {
|
path("/device/:id") {
|
||||||
action: [
|
action: [
|
||||||
GET: "getDevice",
|
GET: "getDevice",
|
||||||
POST: "updateDevice"
|
POST: "updateDevice"
|
||||||
@@ -104,40 +93,33 @@ def handleDevicesWithIDs()
|
|||||||
//log.debug("ids: ${ids}")
|
//log.debug("ids: ${ids}")
|
||||||
def command = data?.command
|
def command = data?.command
|
||||||
def arguments = data?.arguments
|
def arguments = data?.arguments
|
||||||
def type = params?.deviceType
|
|
||||||
//log.debug("device type: ${type}")
|
|
||||||
if (command)
|
if (command)
|
||||||
{
|
{
|
||||||
def statusCode = 404
|
def success = false
|
||||||
//log.debug("command ${command}, arguments ${arguments}")
|
//log.debug("command ${command}, arguments ${arguments}")
|
||||||
for (devId in ids)
|
for (devId in ids)
|
||||||
{
|
{
|
||||||
def device = allDevices.find { it.id == devId }
|
def device = allDevices.find { it.id == devId }
|
||||||
//log.debug("device: ${device}")
|
if (device) {
|
||||||
// Check if we have a device that responds to the specified command
|
if (arguments) {
|
||||||
if (validateCommand(device, type, command)) {
|
|
||||||
if (arguments) {
|
|
||||||
device."$command"(*arguments)
|
device."$command"(*arguments)
|
||||||
}
|
} else {
|
||||||
else {
|
device."$command"()
|
||||||
device."$command"()
|
}
|
||||||
}
|
success = true
|
||||||
statusCode = 200
|
|
||||||
} else {
|
} else {
|
||||||
statusCode = 403
|
//log.debug("device not found ${devId}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def responseData = "{}"
|
|
||||||
switch (statusCode)
|
if (success)
|
||||||
{
|
{
|
||||||
case 403:
|
render status: 200, data: "{}"
|
||||||
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
|
}
|
||||||
break
|
else
|
||||||
case 404:
|
{
|
||||||
responseData = '{"msg": "Device not found"}'
|
render status: 404, data: '{"msg": "Device not found"}'
|
||||||
break
|
|
||||||
}
|
}
|
||||||
render status: statusCode, data: responseData
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -182,101 +164,25 @@ def updateDevice()
|
|||||||
def data = request.JSON
|
def data = request.JSON
|
||||||
def command = data?.command
|
def command = data?.command
|
||||||
def arguments = data?.arguments
|
def arguments = data?.arguments
|
||||||
def type = params?.deviceType
|
|
||||||
//log.debug("device type: ${type}")
|
|
||||||
|
|
||||||
//log.debug("updateDevice, params: ${params}, request: ${data}")
|
//log.debug("updateDevice, params: ${params}, request: ${data}")
|
||||||
if (!command) {
|
if (!command) {
|
||||||
render status: 400, data: '{"msg": "command is required"}'
|
render status: 400, data: '{"msg": "command is required"}'
|
||||||
} else {
|
} else {
|
||||||
def statusCode = 404
|
|
||||||
def device = allDevices.find { it.id == params.id }
|
def device = allDevices.find { it.id == params.id }
|
||||||
if (device) {
|
if (device) {
|
||||||
// Check if we have a device that responds to the specified command
|
if (arguments) {
|
||||||
if (validateCommand(device, type, command)) {
|
device."$command"(*arguments)
|
||||||
if (arguments) {
|
|
||||||
device."$command"(*arguments)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
device."$command"()
|
|
||||||
}
|
|
||||||
statusCode = 200
|
|
||||||
} else {
|
} else {
|
||||||
statusCode = 403
|
device."$command"()
|
||||||
}
|
}
|
||||||
|
render status: 204, data: "{}"
|
||||||
|
} else {
|
||||||
|
render status: 404, data: '{"msg": "Device not found"}'
|
||||||
}
|
}
|
||||||
|
|
||||||
def responseData = "{}"
|
|
||||||
switch (statusCode)
|
|
||||||
{
|
|
||||||
case 403:
|
|
||||||
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
|
|
||||||
break
|
|
||||||
case 404:
|
|
||||||
responseData = '{"msg": "Device not found"}'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
render status: statusCode, data: responseData
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validating the command passed by the user based on capability.
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
def validateCommand(device, deviceType, command) {
|
|
||||||
//log.debug("validateCommand ${command}")
|
|
||||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
|
||||||
//log.debug("capabilityCommands: ${capabilityCommands}")
|
|
||||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
|
||||||
//log.debug("currentDeviceCapability: ${currentDeviceCapability}")
|
|
||||||
if (capabilityCommands[currentDeviceCapability]) {
|
|
||||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
|
||||||
} else {
|
|
||||||
// Handling other device types here, which don't accept commands
|
|
||||||
httpError(400, "Bad request.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Need to get the attribute name to do the lookup. Only
|
|
||||||
* doing it for the device types which accept commands
|
|
||||||
* @return attribute name of the device type
|
|
||||||
*/
|
|
||||||
def getCapabilityName(type) {
|
|
||||||
switch(type) {
|
|
||||||
case "switches":
|
|
||||||
return "Switch"
|
|
||||||
case "locks":
|
|
||||||
return "Lock"
|
|
||||||
case "thermostats":
|
|
||||||
return "Thermostat"
|
|
||||||
case "doorControls":
|
|
||||||
return "Door Control"
|
|
||||||
case "colorControls":
|
|
||||||
return "Color Control"
|
|
||||||
case "musicPlayers":
|
|
||||||
return "Music Player"
|
|
||||||
case "switchLevels":
|
|
||||||
return "Switch Level"
|
|
||||||
default:
|
|
||||||
return type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructing the map over here of
|
|
||||||
* supported commands by device capability
|
|
||||||
* @return a map of device capability -> supported commands
|
|
||||||
*/
|
|
||||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
|
||||||
def map = [:]
|
|
||||||
deviceCapabilities.collect {
|
|
||||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
def listSubscriptions()
|
def listSubscriptions()
|
||||||
{
|
{
|
||||||
//log.debug "listSubscriptions()"
|
//log.debug "listSubscriptions()"
|
||||||
@@ -455,13 +361,7 @@ def agentDiscovery(params=[:])
|
|||||||
}
|
}
|
||||||
section("Allow Simple Control to Monitor and Control These Things...")
|
section("Allow Simple Control to Monitor and Control These Things...")
|
||||||
{
|
{
|
||||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
|
||||||
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
|
|
||||||
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
|
|
||||||
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
|
|
||||||
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
|
|
||||||
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -772,3 +672,5 @@ def List getRealHubFirmwareVersions()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ preferences {
|
|||||||
page(name: "completionPage")
|
page(name: "completionPage")
|
||||||
page(name: "numbersPage")
|
page(name: "numbersPage")
|
||||||
page(name: "controllerExplanationPage")
|
page(name: "controllerExplanationPage")
|
||||||
page(name: "unsupportedDevicesPage")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def rootPage() {
|
def rootPage() {
|
||||||
@@ -48,9 +47,6 @@ def rootPage() {
|
|||||||
section("What to dim") {
|
section("What to dim") {
|
||||||
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
||||||
if (dimmers) {
|
if (dimmers) {
|
||||||
if (dimmersContainUnsupportedDevices()) {
|
|
||||||
href(name: "toUnsupportedDevicesPage", page: "unsupportedDevicesPage", title: "Some of your selected dimmers don't seem to be supported", description: "Tap here to fix it", required: true)
|
|
||||||
}
|
|
||||||
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,31 +71,6 @@ def rootPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def unsupportedDevicesPage() {
|
|
||||||
|
|
||||||
def unsupportedDimmers = dimmers.findAll { !hasSetLevelCommand(it) }
|
|
||||||
|
|
||||||
dynamicPage(name: "unsupportedDevicesPage") {
|
|
||||||
if (unsupportedDimmers) {
|
|
||||||
section("These devices do not support the setLevel command") {
|
|
||||||
unsupportedDimmers.each {
|
|
||||||
paragraph deviceLabel(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
input(name: "dimmers", type: "capability.sensor", title: "Please remove the above devices from this list.", submitOnChange: true, multiple: true)
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
paragraph "If you think there is a mistake here, please contact support."
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
section {
|
|
||||||
paragraph "You're all set. You can hit the back button, now. Thanks for cleaning up your settings :)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def controllerExplanationPage() {
|
def controllerExplanationPage() {
|
||||||
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
|
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
|
||||||
|
|
||||||
@@ -237,7 +208,7 @@ def completionPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
section("Notifications") {
|
section("Notifications") {
|
||||||
input("recipients", "contact", title: "Send notifications to", required: false) {
|
input("recipients", "contact", title: "Send notifications to") {
|
||||||
input(name: "completionPhoneNumber", type: "phone", title: "Text This Number", description: "Phone number", required: false)
|
input(name: "completionPhoneNumber", type: "phone", title: "Text This Number", description: "Phone number", required: false)
|
||||||
input(name: "completionPush", type: "bool", title: "Send A Push Notification", description: "Phone number", required: false)
|
input(name: "completionPush", type: "bool", title: "Send A Push Notification", description: "Phone number", required: false)
|
||||||
}
|
}
|
||||||
@@ -557,16 +528,14 @@ def updateDimmers(percentComplete) {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
def shouldChangeColors = (colorize && colorize != "false")
|
def shouldChangeColors = (colorize && colorize != "false")
|
||||||
|
def canChangeColors = hasSetColorCommand(dimmer)
|
||||||
|
|
||||||
if (shouldChangeColors && hasSetColorCommand(dimmer)) {
|
log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}"
|
||||||
def hue = getHue(dimmer, nextLevel)
|
|
||||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel} and hue to ${hue}"
|
if (shouldChangeColors && canChangeColors) {
|
||||||
dimmer.setColor([hue: hue, saturation: 100, level: nextLevel])
|
dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel])
|
||||||
} else if (hasSetLevelCommand(dimmer)) {
|
|
||||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel}"
|
|
||||||
dimmer.setLevel(nextLevel)
|
|
||||||
} else {
|
} else {
|
||||||
log.warn "${deviceLabel(dimmer)} does not have setColor or setLevel commands."
|
dimmer.setLevel(nextLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -720,7 +689,7 @@ def completionPercentage() {
|
|||||||
|
|
||||||
def now = new Date().getTime()
|
def now = new Date().getTime()
|
||||||
def timeElapsed = now - atomicState.start
|
def timeElapsed = now - atomicState.start
|
||||||
def totalRunTime = totalRunTimeMillis() ?: 1
|
def totalRunTime = totalRunTimeMillis()
|
||||||
def percentComplete = timeElapsed / totalRunTime * 100
|
def percentComplete = timeElapsed / totalRunTime * 100
|
||||||
log.debug "percentComplete: ${percentComplete}"
|
log.debug "percentComplete: ${percentComplete}"
|
||||||
|
|
||||||
@@ -848,21 +817,24 @@ private getRedHue(level) {
|
|||||||
if (level >= 96) return 17
|
if (level >= 96) return 17
|
||||||
}
|
}
|
||||||
|
|
||||||
private dimmersContainUnsupportedDevices() {
|
|
||||||
def found = dimmers.find { hasSetLevelCommand(it) == false }
|
|
||||||
return found != null
|
|
||||||
}
|
|
||||||
|
|
||||||
private hasSetLevelCommand(device) {
|
private hasSetLevelCommand(device) {
|
||||||
return hasCommand(device, "setLevel")
|
def isDimmer = false
|
||||||
|
device.supportedCommands.each {
|
||||||
|
if (it.name.contains("setLevel")) {
|
||||||
|
isDimmer = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isDimmer
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasSetColorCommand(device) {
|
private hasSetColorCommand(device) {
|
||||||
return hasCommand(device, "setColor")
|
def hasColor = false
|
||||||
}
|
device.supportedCommands.each {
|
||||||
|
if (it.name.contains("setColor")) {
|
||||||
private hasCommand(device, String command) {
|
hasColor = true
|
||||||
return (device.supportedCommands.find { it.name == command } != null)
|
}
|
||||||
|
}
|
||||||
|
return hasColor
|
||||||
}
|
}
|
||||||
|
|
||||||
private dimmersWithSetColorCommand() {
|
private dimmersWithSetColorCommand() {
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ definition(
|
|||||||
preferences {
|
preferences {
|
||||||
page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
|
page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
|
||||||
page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
|
page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
|
||||||
page(name:"bridgeDiscoveryFailed", title:"Bridge Discovery Failed", content:"bridgeDiscoveryFailed", refreshTimeout:0)
|
|
||||||
page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
|
page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
|
||||||
page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5)
|
page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5)
|
||||||
}
|
}
|
||||||
@@ -54,23 +53,14 @@ def bridgeDiscovery(params=[:])
|
|||||||
def options = bridges ?: []
|
def options = bridges ?: []
|
||||||
def numFound = options.size() ?: 0
|
def numFound = options.size() ?: 0
|
||||||
|
|
||||||
if (numFound == 0) {
|
if (numFound == 0 && state.bridgeRefreshCount > 25) {
|
||||||
if (state.bridgeRefreshCount == 25) {
|
log.trace "Cleaning old bridges memory"
|
||||||
log.trace "Cleaning old bridges memory"
|
state.bridges = [:]
|
||||||
state.bridges = [:]
|
state.bridgeRefreshCount = 0
|
||||||
app.updateSetting("selectedHue", "")
|
app.updateSetting("selectedHue", "")
|
||||||
} else if (state.bridgeRefreshCount > 100) {
|
}
|
||||||
// five minutes have passed, give up
|
|
||||||
// there seems to be a problem going back from discovey failed page in some instances (compared to pressing next)
|
|
||||||
// however it is probably a SmartThings settings issue
|
|
||||||
state.bridges = [:]
|
|
||||||
app.updateSetting("selectedHue", "")
|
|
||||||
state.bridgeRefreshCount = 0
|
|
||||||
return bridgeDiscoveryFailed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ssdpSubscribe()
|
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||||
|
|
||||||
//bridge discovery request every 15 //25 seconds
|
//bridge discovery request every 15 //25 seconds
|
||||||
if((bridgeRefreshCount % 5) == 0) {
|
if((bridgeRefreshCount % 5) == 0) {
|
||||||
@@ -78,7 +68,7 @@ def bridgeDiscovery(params=[:])
|
|||||||
}
|
}
|
||||||
|
|
||||||
//setup.xml request every 3 seconds except on discoveries
|
//setup.xml request every 3 seconds except on discoveries
|
||||||
if(((bridgeRefreshCount % 3) == 0) && ((bridgeRefreshCount % 5) != 0)) {
|
if(((bridgeRefreshCount % 1) == 0) && ((bridgeRefreshCount % 5) != 0)) {
|
||||||
verifyHueBridges()
|
verifyHueBridges()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,13 +79,6 @@ def bridgeDiscovery(params=[:])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def bridgeDiscoveryFailed() {
|
|
||||||
return dynamicPage(name:"bridgeDiscoveryFailed", title: "Bridge Discovery Failed", nextPage: "bridgeDiscovery") {
|
|
||||||
section("Failed to discover any Hue Bridges. Please confirm that the Hue Bridge is connected to the same network as your SmartThings Hub, and that it has power.") {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def bridgeLinking()
|
def bridgeLinking()
|
||||||
{
|
{
|
||||||
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
|
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
|
||||||
@@ -105,15 +88,19 @@ def bridgeLinking()
|
|||||||
def nextPage = ""
|
def nextPage = ""
|
||||||
def title = "Linking with your Hue"
|
def title = "Linking with your Hue"
|
||||||
def paragraphText
|
def paragraphText
|
||||||
|
def hueimage = null
|
||||||
if (selectedHue) {
|
if (selectedHue) {
|
||||||
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
||||||
|
hueimage = "http://huedisco.mediavibe.nl/wp-content/uploads/2013/09/pair-bridge.png"
|
||||||
} else {
|
} else {
|
||||||
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
|
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
|
||||||
|
hueimage = null
|
||||||
}
|
}
|
||||||
if (state.username) { //if discovery worked
|
if (state.username) { //if discovery worked
|
||||||
nextPage = "bulbDiscovery"
|
nextPage = "bulbDiscovery"
|
||||||
title = "Success!"
|
title = "Success!"
|
||||||
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
|
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
|
||||||
|
hueimage = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if((linkRefreshcount % 2) == 0 && !state.username) {
|
if((linkRefreshcount % 2) == 0 && !state.username) {
|
||||||
@@ -123,6 +110,8 @@ def bridgeLinking()
|
|||||||
return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
|
return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
|
||||||
section("") {
|
section("") {
|
||||||
paragraph """${paragraphText}"""
|
paragraph """${paragraphText}"""
|
||||||
|
if (hueimage != null)
|
||||||
|
image "${hueimage}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,14 +135,13 @@ def bulbDiscovery() {
|
|||||||
if((bulbRefreshCount % 5) == 0) {
|
if((bulbRefreshCount % 5) == 0) {
|
||||||
discoverHueBulbs()
|
discoverHueBulbs()
|
||||||
}
|
}
|
||||||
def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) }
|
|
||||||
def title = selectedBridge?.value?.name ?: "Find bridges"
|
|
||||||
|
|
||||||
return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||||
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
|
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
|
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
|
||||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -164,10 +152,6 @@ private discoverBridges() {
|
|||||||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:basic:1", physicalgraph.device.Protocol.LAN))
|
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:basic:1", physicalgraph.device.Protocol.LAN))
|
||||||
}
|
}
|
||||||
|
|
||||||
void ssdpSubscribe() {
|
|
||||||
subscribe(location, "ssdpTerm.urn:schemas-upnp-org:device:basic:1", ssdpBridgeHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
private sendDeveloperReq() {
|
private sendDeveloperReq() {
|
||||||
def token = app.id
|
def token = app.id
|
||||||
def host = getBridgeIP()
|
def host = getBridgeIP()
|
||||||
@@ -177,7 +161,7 @@ private sendDeveloperReq() {
|
|||||||
headers: [
|
headers: [
|
||||||
HOST: host
|
HOST: host
|
||||||
],
|
],
|
||||||
body: [devicetype: "$token-0"]], "${selectedHue}", [callback: "usernameHandler"]))
|
body: [devicetype: "$token-0", username: "$token-0"]], "${selectedHue}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private discoverHueBulbs() {
|
private discoverHueBulbs() {
|
||||||
@@ -187,17 +171,16 @@ private discoverHueBulbs() {
|
|||||||
path: "/api/${state.username}/lights",
|
path: "/api/${state.username}/lights",
|
||||||
headers: [
|
headers: [
|
||||||
HOST: host
|
HOST: host
|
||||||
]], "${selectedHue}", [callback: "lightsHandler"]))
|
]], "${selectedHue}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private verifyHueBridge(String deviceNetworkId, String host) {
|
private verifyHueBridge(String deviceNetworkId, String host) {
|
||||||
log.trace "Verify Hue Bridge $deviceNetworkId"
|
|
||||||
sendHubCommand(new physicalgraph.device.HubAction([
|
sendHubCommand(new physicalgraph.device.HubAction([
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/description.xml",
|
path: "/description.xml",
|
||||||
headers: [
|
headers: [
|
||||||
HOST: host
|
HOST: host
|
||||||
]], deviceNetworkId, [callback: "bridgeDescriptionHandler"]))
|
]], deviceNetworkId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private verifyHueBridges() {
|
private verifyHueBridges() {
|
||||||
@@ -305,53 +288,17 @@ def bulbListHandler(hub, data = "") {
|
|||||||
def object = new groovy.json.JsonSlurper().parseText(data)
|
def object = new groovy.json.JsonSlurper().parseText(data)
|
||||||
object.each { k,v ->
|
object.each { k,v ->
|
||||||
if (v instanceof Map)
|
if (v instanceof Map)
|
||||||
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub]
|
bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def bridge = null
|
def bridge = null
|
||||||
if (selectedHue) {
|
if (selectedHue)
|
||||||
bridge = getChildDevice(selectedHue)
|
bridge = getChildDevice(selectedHue)
|
||||||
}
|
|
||||||
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
|
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
|
||||||
msg = "${bulbs.size()} bulbs found. ${bulbs}"
|
msg = "${bulbs.size()} bulbs found. ${bulbs}"
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
private upgradeDeviceType(device, newHueType) {
|
|
||||||
def deviceType = getDeviceType(newHueType)
|
|
||||||
|
|
||||||
// Automatically change users Hue bulbs to correct device types
|
|
||||||
if (deviceType && !(device?.typeName?.equalsIgnoreCase(deviceType))) {
|
|
||||||
log.debug "Update device type: \"$device.label\" ${device?.typeName}->$deviceType"
|
|
||||||
device.setDeviceType(deviceType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDeviceType(hueType) {
|
|
||||||
// Determine ST device type based on Hue classification of light
|
|
||||||
if (hueType?.equalsIgnoreCase("Dimmable light"))
|
|
||||||
return "Hue Lux Bulb"
|
|
||||||
else if (hueType?.equalsIgnoreCase("Extended Color Light"))
|
|
||||||
return "Hue Bulb"
|
|
||||||
else if (hueType?.equalsIgnoreCase("Color Light"))
|
|
||||||
return "Hue Bloom"
|
|
||||||
else if (hueType?.equalsIgnoreCase("Color Temperature Light"))
|
|
||||||
return "Hue White Ambiance Bulb"
|
|
||||||
else
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private addChildBulb(dni, hueType, name, hub, update=false, device = null) {
|
|
||||||
def deviceType = getDeviceType(hueType)
|
|
||||||
|
|
||||||
if (deviceType) {
|
|
||||||
return addChildDevice("smartthings", deviceType, dni, hub, ["label": name])
|
|
||||||
} else {
|
|
||||||
log.warn "Device type $hueType not supported"
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def addBulbs() {
|
def addBulbs() {
|
||||||
def bulbs = getHueBulbs()
|
def bulbs = getHueBulbs()
|
||||||
selectedBulbs?.each { dni ->
|
selectedBulbs?.each { dni ->
|
||||||
@@ -360,30 +307,30 @@ def addBulbs() {
|
|||||||
def newHueBulb
|
def newHueBulb
|
||||||
if (bulbs instanceof java.util.Map) {
|
if (bulbs instanceof java.util.Map) {
|
||||||
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||||
|
if (newHueBulb != null) {
|
||||||
if (newHueBulb != null) {
|
if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") ) {
|
||||||
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
d = addChildDevice("smartthings", "Hue Lux Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
|
||||||
if (d) {
|
} else {
|
||||||
log.debug "created ${d.displayName} with id $dni"
|
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
|
||||||
d.completedSetup = true
|
}
|
||||||
d.refresh()
|
log.debug "created ${d.displayName} with id $dni"
|
||||||
}
|
d.refresh()
|
||||||
} else {
|
} else {
|
||||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//backwards compatable
|
//backwards compatable
|
||||||
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
||||||
d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
|
||||||
d?.completedSetup = true
|
d.refresh()
|
||||||
d?.refresh()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
|
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
|
||||||
if (bulbs instanceof java.util.Map) {
|
if (bulbs instanceof java.util.Map) {
|
||||||
// Update device type if incorrect
|
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||||
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") && d.typeName == "Hue Bulb") {
|
||||||
upgradeDeviceType(d, newHueBulb?.value?.type)
|
d.setDeviceType("Hue Lux Bulb")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,16 +352,14 @@ def addBridge() {
|
|||||||
def oldDNI = it.deviceNetworkId
|
def oldDNI = it.deviceNetworkId
|
||||||
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
||||||
it.setDeviceNetworkId("${newDNI}")
|
it.setDeviceNetworkId("${newDNI}")
|
||||||
if (oldDNI == selectedHue) {
|
if (oldDNI == selectedHue)
|
||||||
app.updateSetting("selectedHue", newDNI)
|
app.updateSetting("selectedHue", newDNI)
|
||||||
}
|
|
||||||
newbridge = false
|
newbridge = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newbridge) {
|
if (newbridge) {
|
||||||
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
|
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
|
||||||
d?.completedSetup = true
|
|
||||||
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
||||||
def childDevice = getChildDevice(d.deviceNetworkId)
|
def childDevice = getChildDevice(d.deviceNetworkId)
|
||||||
childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber)
|
childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber)
|
||||||
@@ -437,125 +382,6 @@ def addBridge() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def ssdpBridgeHandler(evt) {
|
|
||||||
def description = evt.description
|
|
||||||
log.trace "Location: $description"
|
|
||||||
|
|
||||||
def hub = evt?.hubId
|
|
||||||
def parsedEvent = parseLanMessage(description)
|
|
||||||
parsedEvent << ["hub":hub]
|
|
||||||
|
|
||||||
def bridges = getHueBridges()
|
|
||||||
log.trace bridges.toString()
|
|
||||||
if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) {
|
|
||||||
//bridge does not exist
|
|
||||||
log.trace "Adding bridge ${parsedEvent.ssdpUSN}"
|
|
||||||
bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
|
||||||
} else {
|
|
||||||
// update the values
|
|
||||||
def ip = convertHexToIP(parsedEvent.networkAddress)
|
|
||||||
def port = convertHexToInt(parsedEvent.deviceAddress)
|
|
||||||
def host = ip + ":" + port
|
|
||||||
log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host."
|
|
||||||
def dstate = bridges."${parsedEvent.ssdpUSN.toString()}"
|
|
||||||
def dni = "${parsedEvent.mac}"
|
|
||||||
def d = getChildDevice(dni)
|
|
||||||
def networkAddress = null
|
|
||||||
if (!d) {
|
|
||||||
childDevices.each {
|
|
||||||
if (it.getDeviceDataByName("mac")) {
|
|
||||||
def newDNI = "${it.getDeviceDataByName("mac")}"
|
|
||||||
if (newDNI != it.deviceNetworkId) {
|
|
||||||
def oldDNI = it.deviceNetworkId
|
|
||||||
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
|
||||||
it.setDeviceNetworkId("${newDNI}")
|
|
||||||
if (oldDNI == selectedHue) {
|
|
||||||
app.updateSetting("selectedHue", newDNI)
|
|
||||||
}
|
|
||||||
doDeviceSync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (d.getDeviceDataByName("networkAddress")) {
|
|
||||||
networkAddress = d.getDeviceDataByName("networkAddress")
|
|
||||||
} else {
|
|
||||||
networkAddress = d.latestState('networkAddress').stringValue
|
|
||||||
}
|
|
||||||
log.trace "Host: $host - $networkAddress"
|
|
||||||
if (host != networkAddress) {
|
|
||||||
log.debug "Device's port or ip changed for device $d..."
|
|
||||||
dstate.ip = ip
|
|
||||||
dstate.port = port
|
|
||||||
dstate.name = "Philips hue ($ip)"
|
|
||||||
d.sendEvent(name:"networkAddress", value: host)
|
|
||||||
d.updateDataValue("networkAddress", host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
|
|
||||||
log.trace "description.xml response (application/xml)"
|
|
||||||
def body = hubResponse.xml
|
|
||||||
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
|
||||||
def bridges = getHueBridges()
|
|
||||||
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
|
||||||
if (bridge) {
|
|
||||||
// serialNumber from API is in format of 0017882413ad (mac address), however on the actual bridge only last six
|
|
||||||
// characters are printed on the back so using that to identify bridge
|
|
||||||
def idNumber = body?.device?.serialNumber?.text()
|
|
||||||
if (idNumber?.size() >= 6)
|
|
||||||
idNumber = idNumber[-6..-1].toUpperCase()
|
|
||||||
|
|
||||||
// usually in form of bridge name followed by (ip), i.e. defaults to Philips Hue (192.168.1.2)
|
|
||||||
// replace IP with serial number to make it easier for user to identify
|
|
||||||
def name = body?.device?.friendlyName?.text()
|
|
||||||
def index = name?.indexOf('(')
|
|
||||||
if (index != -1) {
|
|
||||||
name = name.substring(0,index)
|
|
||||||
name += " ($idNumber)"
|
|
||||||
}
|
|
||||||
bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
|
||||||
} else {
|
|
||||||
log.error "/description.xml returned a bridge that didn't exist"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void lightsHandler(physicalgraph.device.HubResponse hubResponse) {
|
|
||||||
if (isValidSource(hubResponse.mac)) {
|
|
||||||
def body = hubResponse.json
|
|
||||||
if (!body?.state?.on) { //check if first time poll made it here by mistake
|
|
||||||
def bulbs = getHueBulbs()
|
|
||||||
log.debug "Adding bulbs to state!"
|
|
||||||
body.each { k, v ->
|
|
||||||
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub: hubResponse.hubId]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void usernameHandler(physicalgraph.device.HubResponse hubResponse) {
|
|
||||||
if (isValidSource(hubResponse.mac)) {
|
|
||||||
def body = hubResponse.json
|
|
||||||
if (body.success != null) {
|
|
||||||
if (body.success[0] != null) {
|
|
||||||
if (body.success[0].username)
|
|
||||||
state.username = body.success[0].username
|
|
||||||
}
|
|
||||||
} else if (body.error != null) {
|
|
||||||
//TODO: handle retries...
|
|
||||||
log.error "ERROR: application/json ${body.error}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated This has been replaced by the combination of {@link #ssdpBridgeHandler()}, {@link #bridgeDescriptionHandler()},
|
|
||||||
* {@link #lightsHandler()}, and {@link #usernameHandler()}. After a pending event subscription migration, it can be removed.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
def locationHandler(evt) {
|
def locationHandler(evt) {
|
||||||
def description = evt.description
|
def description = evt.description
|
||||||
log.trace "Location: $description"
|
log.trace "Location: $description"
|
||||||
@@ -591,19 +417,17 @@ def locationHandler(evt) {
|
|||||||
def oldDNI = it.deviceNetworkId
|
def oldDNI = it.deviceNetworkId
|
||||||
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}"
|
||||||
it.setDeviceNetworkId("${newDNI}")
|
it.setDeviceNetworkId("${newDNI}")
|
||||||
if (oldDNI == selectedHue) {
|
if (oldDNI == selectedHue)
|
||||||
app.updateSetting("selectedHue", newDNI)
|
app.updateSetting("selectedHue", newDNI)
|
||||||
}
|
|
||||||
doDeviceSync()
|
doDeviceSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (d.getDeviceDataByName("networkAddress")) {
|
if (d.getDeviceDataByName("networkAddress"))
|
||||||
networkAddress = d.getDeviceDataByName("networkAddress")
|
networkAddress = d.getDeviceDataByName("networkAddress")
|
||||||
} else {
|
else
|
||||||
networkAddress = d.latestState('networkAddress').stringValue
|
networkAddress = d.latestState('networkAddress').stringValue
|
||||||
}
|
|
||||||
log.trace "Host: $host - $networkAddress"
|
log.trace "Host: $host - $networkAddress"
|
||||||
if(host != networkAddress) {
|
if(host != networkAddress) {
|
||||||
log.debug "Device's port or ip changed for device $d..."
|
log.debug "Device's port or ip changed for device $d..."
|
||||||
@@ -636,9 +460,8 @@ def locationHandler(evt) {
|
|||||||
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
|
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
|
||||||
if (body.success != null) {
|
if (body.success != null) {
|
||||||
if (body.success[0] != null) {
|
if (body.success[0] != null) {
|
||||||
if (body.success[0].username) {
|
if (body.success[0].username)
|
||||||
state.username = body.success[0].username
|
state.username = body.success[0].username
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (body.error != null) {
|
} else if (body.error != null) {
|
||||||
//TODO: handle retries...
|
//TODO: handle retries...
|
||||||
@@ -649,7 +472,7 @@ def locationHandler(evt) {
|
|||||||
def bulbs = getHueBulbs()
|
def bulbs = getHueBulbs()
|
||||||
log.debug "Adding bulbs to state!"
|
log.debug "Adding bulbs to state!"
|
||||||
body.each { k,v ->
|
body.each { k,v ->
|
||||||
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub]
|
bulbs[k] = [id: k, name: v.name, type: v.type, hub:parsedEvent.hub]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -663,7 +486,11 @@ def doDeviceSync(){
|
|||||||
log.trace "Doing Hue Device Sync!"
|
log.trace "Doing Hue Device Sync!"
|
||||||
convertBulbListToMap()
|
convertBulbListToMap()
|
||||||
poll()
|
poll()
|
||||||
ssdpSubscribe()
|
try {
|
||||||
|
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||||
|
} catch (all) {
|
||||||
|
log.trace "Subscription already exist"
|
||||||
|
}
|
||||||
discoverBridges()
|
discoverBridges()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,7 +516,7 @@ def parse(childDevice, description) {
|
|||||||
log.warn "Parsing Body failed - trying again..."
|
log.warn "Parsing Body failed - trying again..."
|
||||||
poll()
|
poll()
|
||||||
}
|
}
|
||||||
if (body instanceof java.util.Map) {
|
if (body instanceof java.util.HashMap) {
|
||||||
//poll response
|
//poll response
|
||||||
def bulbs = getChildDevices()
|
def bulbs = getChildDevices()
|
||||||
for (bulb in body) {
|
for (bulb in body) {
|
||||||
@@ -775,20 +602,6 @@ def parse(childDevice, description) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def hubVerification(bodytext) {
|
|
||||||
log.trace "Bridge sent back description.xml for verification"
|
|
||||||
def body = new XmlSlurper().parseText(bodytext)
|
|
||||||
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
|
||||||
def bridges = getHueBridges()
|
|
||||||
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
|
||||||
if (bridge) {
|
|
||||||
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
|
||||||
} else {
|
|
||||||
log.error "/description.xml returned a bridge that didn't exist"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def on(childDevice) {
|
def on(childDevice) {
|
||||||
log.debug "Executing 'on'"
|
log.debug "Executing 'on'"
|
||||||
put("lights/${getId(childDevice)}/state", [on: true])
|
put("lights/${getId(childDevice)}/state", [on: true])
|
||||||
@@ -829,53 +642,39 @@ def setColorTemperature(childDevice, huesettings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def setColor(childDevice, huesettings) {
|
def setColor(childDevice, huesettings) {
|
||||||
log.debug "Executing 'setColor($huesettings)'"
|
log.debug "Executing 'setColor($huesettings)'"
|
||||||
|
|
||||||
def value = [:]
|
|
||||||
def hue = null
|
def hue = null
|
||||||
def sat = null
|
def sat = null
|
||||||
def xy = null
|
def xy = null
|
||||||
|
if (huesettings.hex) {
|
||||||
if (huesettings.hex != null) {
|
xy = getHextoXY(huesettings.hex)
|
||||||
value.xy = getHextoXY(huesettings.hex)
|
} else if (huesettings.hue && huesettings.saturation) {
|
||||||
} else {
|
hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||||
if (huesettings.hue != null)
|
sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
|
||||||
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
|
||||||
if (huesettings.saturation != null)
|
|
||||||
value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
|
|
||||||
}
|
}
|
||||||
|
def alert = huesettings.alert ? huesettings.alert : "none"
|
||||||
|
def transition = huesettings.transition ? huesettings.transition : 4
|
||||||
|
|
||||||
// Default behavior is to turn light on
|
def value = [xy: xy, sat: sat, hue: hue, alert: alert, transitiontime: transition, on: true]
|
||||||
value.on = true
|
|
||||||
|
|
||||||
if (huesettings.level != null) {
|
if (huesettings.level != null) {
|
||||||
if (huesettings.level <= 0)
|
if (huesettings.level == 1) value.bri = 1 else value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
||||||
value.on = false
|
value.on = value.bri > 0
|
||||||
else if (huesettings.level == 1)
|
}
|
||||||
value.bri = 1
|
|
||||||
else
|
|
||||||
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
|
||||||
}
|
|
||||||
value.alert = huesettings.alert ? huesettings.alert : "none"
|
|
||||||
value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4
|
|
||||||
|
|
||||||
// Make sure to turn off light if requested
|
log.debug "sending command $value"
|
||||||
if (huesettings.switch == "off")
|
put("lights/${getId(childDevice)}/state", value)
|
||||||
value.on = false
|
|
||||||
|
|
||||||
log.debug "sending command $value"
|
|
||||||
put("lights/${getId(childDevice)}/state", value)
|
|
||||||
return "Color set to $value"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def nextLevel(childDevice) {
|
def nextLevel(childDevice) {
|
||||||
def level = device.latestValue("level") as Integer ?: 0
|
def level = device.latestValue("level") as Integer ?: 0
|
||||||
if (level < 100) {
|
if (level < 100) {
|
||||||
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
||||||
} else {
|
}
|
||||||
level = 25
|
else {
|
||||||
}
|
level = 25
|
||||||
setLevel(childDevice,level)
|
}
|
||||||
|
setLevel(childDevice,level)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getId(childDevice) {
|
private getId(childDevice) {
|
||||||
@@ -890,11 +689,15 @@ private getId(childDevice) {
|
|||||||
private poll() {
|
private poll() {
|
||||||
def host = getBridgeIP()
|
def host = getBridgeIP()
|
||||||
def uri = "/api/${state.username}/lights/"
|
def uri = "/api/${state.username}/lights/"
|
||||||
log.debug "GET: $host$uri"
|
try {
|
||||||
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
|
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
|
||||||
HOST: ${host}
|
HOST: ${host}
|
||||||
|
|
||||||
""", physicalgraph.device.Protocol.LAN, selectedHue))
|
""", physicalgraph.device.Protocol.LAN, selectedHue))
|
||||||
|
} catch (all) {
|
||||||
|
log.warn "Parsing Body failed - trying again..."
|
||||||
|
doDeviceSync()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private put(path, body) {
|
private put(path, body) {
|
||||||
@@ -970,14 +773,16 @@ private getHextoXY(String colorStr) {
|
|||||||
|
|
||||||
// Make green more vivid
|
// Make green more vivid
|
||||||
if (normalizedToOne[1] > 0.04045) {
|
if (normalizedToOne[1] > 0.04045) {
|
||||||
green = (float) Math.pow((normalizedToOne[1] + 0.055) / (1.0 + 0.055), 2.4);
|
green = (float) Math.pow((normalizedToOne[1] + 0.055)
|
||||||
|
/ (1.0 + 0.055), 2.4);
|
||||||
} else {
|
} else {
|
||||||
green = (float) (normalizedToOne[1] / 12.92);
|
green = (float) (normalizedToOne[1] / 12.92);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make blue more vivid
|
// Make blue more vivid
|
||||||
if (normalizedToOne[2] > 0.04045) {
|
if (normalizedToOne[2] > 0.04045) {
|
||||||
blue = (float) Math.pow((normalizedToOne[2] + 0.055) / (1.0 + 0.055), 2.4);
|
blue = (float) Math.pow((normalizedToOne[2] + 0.055)
|
||||||
|
/ (1.0 + 0.055), 2.4);
|
||||||
} else {
|
} else {
|
||||||
blue = (float) (normalizedToOne[2] / 12.92);
|
blue = (float) (normalizedToOne[2] / 12.92);
|
||||||
}
|
}
|
||||||
@@ -986,8 +791,8 @@ private getHextoXY(String colorStr) {
|
|||||||
float Y = (float) (red * 0.234327 + green * 0.743075 + blue * 0.022598);
|
float Y = (float) (red * 0.234327 + green * 0.743075 + blue * 0.022598);
|
||||||
float Z = (float) (red * 0.0000000 + green * 0.053077 + blue * 1.035763);
|
float Z = (float) (red * 0.0000000 + green * 0.053077 + blue * 1.035763);
|
||||||
|
|
||||||
float x = (X != 0 ? X / (X + Y + Z) : 0);
|
float x = X / (X + Y + Z);
|
||||||
float y = (Y != 0 ? Y / (X + Y + Z) : 0);
|
float y = Y / (X + Y + Z);
|
||||||
|
|
||||||
double[] xy = new double[2];
|
double[] xy = new double[2];
|
||||||
xy[0] = x;
|
xy[0] = x;
|
||||||
@@ -1004,7 +809,7 @@ def convertBulbListToMap() {
|
|||||||
if (state.bulbs instanceof java.util.List) {
|
if (state.bulbs instanceof java.util.List) {
|
||||||
def map = [:]
|
def map = [:]
|
||||||
state.bulbs.unique {it.id}.each { bulb ->
|
state.bulbs.unique {it.id}.each { bulb ->
|
||||||
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub]]
|
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "hub":bulb.hub]]
|
||||||
}
|
}
|
||||||
state.bulbs = map
|
state.bulbs = map
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,69 +131,19 @@ def update() {
|
|||||||
def type = params.deviceType
|
def type = params.deviceType
|
||||||
def data = request.JSON
|
def data = request.JSON
|
||||||
def devices = settings[type]
|
def devices = settings[type]
|
||||||
def device = settings[type]?.find { it.id == params.id }
|
|
||||||
def command = data.command
|
def command = data.command
|
||||||
|
|
||||||
log.debug "[PROD] update, params: ${params}, request: ${data}, devices: ${devices*.id}"
|
log.debug "[PROD] update, params: ${params}, request: ${data}, devices: ${devices*.id}"
|
||||||
|
if (command) {
|
||||||
if (!device) {
|
def device = devices?.find { it.id == params.id }
|
||||||
httpError(404, "Device not found")
|
if (!device) {
|
||||||
}
|
httpError(404, "Device not found")
|
||||||
|
} else {
|
||||||
if (validateCommand(device, type, command)) {
|
device."$command"()
|
||||||
device."$command"()
|
}
|
||||||
} else {
|
|
||||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validating the command passed by the user based on capability.
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
def validateCommand(device, deviceType, command) {
|
|
||||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
|
||||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
|
||||||
if (capabilityCommands[currentDeviceCapability]) {
|
|
||||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
|
||||||
} else {
|
|
||||||
// Handling other device types here, which don't accept commands
|
|
||||||
httpError(400, "Bad request.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Need to get the attribute name to do the lookup. Only
|
|
||||||
* doing it for the device types which accept commands
|
|
||||||
* @return attribute name of the device type
|
|
||||||
*/
|
|
||||||
def getCapabilityName(type) {
|
|
||||||
switch(type) {
|
|
||||||
case "switches":
|
|
||||||
return "Switch"
|
|
||||||
case "alarms":
|
|
||||||
return "Alarm"
|
|
||||||
case "locks":
|
|
||||||
return "Lock"
|
|
||||||
default:
|
|
||||||
return type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructing the map over here of
|
|
||||||
* supported commands by device capability
|
|
||||||
* @return a map of device capability -> supported commands
|
|
||||||
*/
|
|
||||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
|
||||||
def map = [:]
|
|
||||||
deviceCapabilities.collect {
|
|
||||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def show() {
|
def show() {
|
||||||
def type = params.deviceType
|
def type = params.deviceType
|
||||||
def devices = settings[type]
|
def devices = settings[type]
|
||||||
|
|||||||
@@ -73,8 +73,7 @@ def temperatureHandler(evt) {
|
|||||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||||
} else {
|
} else {
|
||||||
log.debug "Temperature dropped below $tooCold: sending SMS to $phone1 and activating $mySwitch"
|
log.debug "Temperature dropped below $tooCold: sending SMS to $phone1 and activating $mySwitch"
|
||||||
def tempScale = location.temperatureScale ?: "F"
|
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:"F"}")
|
||||||
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
|
||||||
switch1?.on()
|
switch1?.on()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,8 +73,7 @@ def temperatureHandler(evt) {
|
|||||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||||
} else {
|
} else {
|
||||||
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
|
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
|
||||||
def tempScale = location.temperatureScale ?: "F"
|
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:"F"}")
|
||||||
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
|
||||||
switch1?.on()
|
switch1?.on()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user