mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 13:21:53 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8570c84d37 |
23
.gitignore
vendored
23
.gitignore
vendored
@@ -1,23 +0,0 @@
|
||||
# Eclipse files
|
||||
/.settings
|
||||
/.classpath
|
||||
/.project
|
||||
/eclipse/
|
||||
/target-eclipse
|
||||
|
||||
# IntelliJ files
|
||||
*.iws
|
||||
*.iml
|
||||
.idea
|
||||
/out
|
||||
*.ipr
|
||||
|
||||
# Gradle files
|
||||
.gradletasknamecache
|
||||
.gradle/
|
||||
|
||||
# Mac OS files
|
||||
.DS_Store
|
||||
|
||||
# Build files
|
||||
/build
|
||||
105
build.gradle
105
build.gradle
@@ -1,105 +0,0 @@
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Paths
|
||||
import com.smartthings.deployment.slack.FileUpload
|
||||
import com.smartthings.deployment.slack.Message
|
||||
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'smartthings-executable-deployment'
|
||||
apply plugin: 'smartthings-slack'
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.8"
|
||||
}
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven {
|
||||
credentials {
|
||||
username smartThingsArtifactoryUserName
|
||||
password smartThingsArtifactoryPassword
|
||||
}
|
||||
url "http://artifactory.smartthings.com/libs-release-local"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
}
|
||||
|
||||
slackSendMessage {
|
||||
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 = []
|
||||
File rootDir = new File("${project.buildDir}/archives")
|
||||
if (rootDir.exists()) {
|
||||
// Create a list of archives which were deployed.
|
||||
java.nio.file.Path rootPath = Paths.get(rootDir.absolutePath)
|
||||
rootDir.eachFileRecurse { File file ->
|
||||
if (file.name.endsWith('.tar.gz')) {
|
||||
java.nio.file.Path archivePath = Paths.get(file.absolutePath)
|
||||
archives.add(rootPath.relativize(archivePath).toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
Date date = new Date()
|
||||
String fileDate = date.format('yyyy-MM-dd_HH-mm-ss', TimeZone.getTimeZone('GMT'))
|
||||
|
||||
// Required Task Arguments.
|
||||
file = new FileUpload(
|
||||
data: archives.join('\n').getBytes(StandardCharsets.UTF_8),
|
||||
filename: "deployment-notes-${fileDate}.txt",
|
||||
title: 'Deployment Notes',
|
||||
channels: channel,
|
||||
token: token,
|
||||
color: color
|
||||
)
|
||||
message = new Message(
|
||||
webhookUrl: webhookUrl,
|
||||
username: username,
|
||||
asUser: true,
|
||||
iconUrl: iconUrl,
|
||||
channel: channel,
|
||||
fallback: 'Deployment Notification',
|
||||
text: messageText
|
||||
)
|
||||
}
|
||||
25
circle.yml
25
circle.yml
@@ -1,25 +0,0 @@
|
||||
machine:
|
||||
java:
|
||||
version:
|
||||
oraclejdk8
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
- echo "Nothing to do."
|
||||
|
||||
test:
|
||||
override:
|
||||
- echo "We don't have any tests :-("
|
||||
|
||||
deployment:
|
||||
develop:
|
||||
branch: master
|
||||
commands:
|
||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_DEV"
|
||||
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
||||
|
||||
stage:
|
||||
branch: staging
|
||||
commands:
|
||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_STAGE"
|
||||
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
||||
@@ -1,107 +0,0 @@
|
||||
/**
|
||||
* BeaconThing
|
||||
*
|
||||
* Copyright 2015 obycode
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
metadata {
|
||||
definition (name: "BeaconThing", namespace: "com.obycode", author: "obycode") {
|
||||
capability "Beacon"
|
||||
capability "Presence Sensor"
|
||||
capability "Sensor"
|
||||
|
||||
attribute "inRange", "json_object"
|
||||
attribute "inRangeFriendly", "string"
|
||||
|
||||
command "setPresence", ["string"]
|
||||
command "arrived", ["string"]
|
||||
command "left", ["string"]
|
||||
}
|
||||
|
||||
simulator {
|
||||
status "present": "presence: 1"
|
||||
status "not present": "presence: 0"
|
||||
}
|
||||
|
||||
tiles {
|
||||
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
|
||||
state("present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0")
|
||||
state("not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("inRange", "device.inRangeFriendly", inactiveLabel: true, height:1, width:3, decoration: "flat") {
|
||||
state "default", label:'${currentValue}', backgroundColor:"#ffffff"
|
||||
}
|
||||
main "presence"
|
||||
details (["presence","inRange"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "presence", value: "not present")
|
||||
def emptyList = []
|
||||
def json = new groovy.json.JsonBuilder(emptyList)
|
||||
sendEvent(name:"inRange", value:json.toString())
|
||||
}
|
||||
|
||||
def setPresence(status) {
|
||||
log.debug "Status is $status"
|
||||
sendEvent(name:"presence", value:status)
|
||||
}
|
||||
|
||||
def arrived(id) {
|
||||
log.debug "$id has arrived"
|
||||
def theList = device.latestValue("inRange")
|
||||
def inRangeList = new JsonSlurper().parseText(theList)
|
||||
if (inRangeList.contains(id)) {
|
||||
return
|
||||
}
|
||||
inRangeList += id
|
||||
def json = new groovy.json.JsonBuilder(inRangeList)
|
||||
log.debug "Now in range: ${json.toString()}"
|
||||
sendEvent(name:"inRange", value:json.toString())
|
||||
|
||||
// Generate human friendly string for tile
|
||||
def friendlyList = "Nearby: " + inRangeList.join(", ")
|
||||
sendEvent(name:"inRangeFriendly", value:friendlyList)
|
||||
|
||||
if (inRangeList.size() == 1) {
|
||||
setPresence("present")
|
||||
}
|
||||
}
|
||||
|
||||
def left(id) {
|
||||
log.debug "$id has left"
|
||||
def theList = device.latestValue("inRange")
|
||||
def inRangeList = new JsonSlurper().parseText(theList)
|
||||
inRangeList -= id
|
||||
def json = new groovy.json.JsonBuilder(inRangeList)
|
||||
log.debug "Now in range: ${json.toString()}"
|
||||
sendEvent(name:"inRange", value:json.toString())
|
||||
|
||||
// Generate human friendly string for tile
|
||||
def friendlyList = "Nearby: " + inRangeList.join(", ")
|
||||
|
||||
if (inRangeList.empty) {
|
||||
setPresence("not present")
|
||||
friendlyList = "No one is nearby"
|
||||
}
|
||||
|
||||
sendEvent(name:"inRangeFriendly", value:friendlyList)
|
||||
}
|
||||
@@ -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,239 +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", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
||||
capability "Battery"
|
||||
capability "Contact Sensor"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
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: ""
|
||||
}
|
||||
|
||||
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:""
|
||||
}
|
||||
|
||||
main "FGK"
|
||||
details(["FGK","battery"])
|
||||
}
|
||||
}
|
||||
|
||||
// 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, [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 = [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 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: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.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,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()
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ def setInstallSmartApp(value){
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.debug description
|
||||
|
||||
def description_map = parseDescriptionAsMap(description)
|
||||
def event_name = ""
|
||||
def measurement_map = [
|
||||
@@ -129,7 +129,10 @@ def parse(String description) {
|
||||
zigbeedeviceid: device.zigbeeId,
|
||||
created: new Date().time /1000 as int
|
||||
]
|
||||
if (description_map.cluster == "0001"){
|
||||
if (description_map.cluster == "0000"){
|
||||
/* version number, not used */
|
||||
|
||||
} else if (description_map.cluster == "0001"){
|
||||
/* battery voltage in mV (device needs minimium 2.1v to run) */
|
||||
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
|
||||
event_name = "battery_status"
|
||||
@@ -155,10 +158,6 @@ def parse(String description) {
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
if(nameAndValue.length == 2){
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}else{
|
||||
map += []
|
||||
}
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
@@ -1,627 +0,0 @@
|
||||
/**
|
||||
* Spruce Controller - Pre Release V2 10/11/2015
|
||||
*
|
||||
* Copyright 2015 Plaid Systems
|
||||
*
|
||||
* Author: NC
|
||||
* Date: 2015-11
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
-----------V3 updates-11-2015------------
|
||||
-Start program button updated to signal schedule check in Scheduler
|
||||
11/17 alarm "0" -> 0 (ln 305)
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Spruce Controller", namespace: "plaidsystems", author: "NCauffman") {
|
||||
capability "Switch"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Actuator"
|
||||
capability "Valve"
|
||||
|
||||
attribute "switch", "string"
|
||||
attribute "switch1", "string"
|
||||
attribute "switch2", "string"
|
||||
attribute "switch8", "string"
|
||||
attribute "switch5", "string"
|
||||
attribute "switch3", "string"
|
||||
attribute "switch4", "string"
|
||||
attribute "switch6", "string"
|
||||
attribute "switch7", "string"
|
||||
attribute "switch9", "string"
|
||||
attribute "switch10", "string"
|
||||
attribute "switch11", "string"
|
||||
attribute "switch12", "string"
|
||||
attribute "switch13", "string"
|
||||
attribute "switch14", "string"
|
||||
attribute "switch15", "string"
|
||||
attribute "switch16", "string"
|
||||
attribute "status", "string"
|
||||
|
||||
command "programOn"
|
||||
command "programOff"
|
||||
command "on"
|
||||
command "off"
|
||||
command "z1on"
|
||||
command "z1off"
|
||||
command "z2on"
|
||||
command "z2off"
|
||||
command "z3on"
|
||||
command "z3off"
|
||||
command "z4on"
|
||||
command "z4off"
|
||||
command "z5on"
|
||||
command "z5off"
|
||||
command "z6on"
|
||||
command "z6off"
|
||||
command "z7on"
|
||||
command "z7off"
|
||||
command "z8on"
|
||||
command "z8off"
|
||||
command "z9on"
|
||||
command "z9off"
|
||||
command "z10on"
|
||||
command "z10off"
|
||||
command "z11on"
|
||||
command "z11off"
|
||||
command "z12on"
|
||||
command "z12off"
|
||||
command "z13on"
|
||||
command "z13off"
|
||||
command "z14on"
|
||||
command "z14off"
|
||||
command "z15on"
|
||||
command "z15off"
|
||||
command "z16on"
|
||||
command "z16off"
|
||||
command "offtime"
|
||||
|
||||
command "refresh"
|
||||
command "rain"
|
||||
command "manual"
|
||||
command "setDisplay"
|
||||
|
||||
command "settingsMap"
|
||||
command "writeTime"
|
||||
command "writeType"
|
||||
command "notify"
|
||||
command "updated"
|
||||
|
||||
fingerprint endpointId: "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18", profileId: "0104", deviceId: "0002", deviceVersion: "00", inClusters: "0000,0003,0004,0005,0006,000F", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZ16-01"
|
||||
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
|
||||
// reply messages
|
||||
|
||||
}
|
||||
preferences {
|
||||
input description: "Press Configure button after making changes to these preferences", displayDuringSetup: true, type: "paragraph", element: "paragraph", title: ""
|
||||
input "RainEnable", "bool", title: "Rain Sensor Attached?", required: false, displayDuringSetup: true
|
||||
input "ManualTime", "number", title: "Automatic shutoff time when a zone is turned on manually?", required: false, displayDuringSetup: true
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
|
||||
standardTile("status", "device.status") {
|
||||
state "schedule", label: 'Schedule Set', icon: "http://www.plaidsystems.com/smartthings/st_spruce_leaf_225_t.png"
|
||||
state "finished", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#46c2e8"
|
||||
state "raintoday", label: 'Rain Today', icon: "st.custom.wuk.nt_chancerain"
|
||||
state "rainy", label: 'Previous Rain', icon: "st.custom.wuk.nt_chancerain"
|
||||
state "raintom", label: 'Rain Tomorrow', icon: "st.custom.wuk.nt_chancerain"
|
||||
state "donewweek", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#52c435"
|
||||
state "skipping", label: 'Skip Today', icon: "st.Outdoor.outdoor20", backgroundColor: "#36cfe3"
|
||||
state "moisture", label: '', icon: "st.Weather.weather2", backgroundColor: "#36cfe3"
|
||||
state "pause", label: 'PAUSE', icon: "st.contact.contact.open", backgroundColor: "#f2a51f"
|
||||
state "active", label: 'Active', icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
|
||||
state "season", label: 'Seasonal Adjustment', icon: "st.Outdoor.outdoor17", backgroundColor: "#ffb900"
|
||||
state "disable", label: 'Disabled', icon: "st.secondary.off", backgroundColor: "#888888"
|
||||
state "warning", label: '', icon: "st.categories.damageAndDanger", backgroundColor: "#ffff7f"
|
||||
state "alarm", label: 'Alarm', icon: "st.categories.damageAndDanger", backgroundColor: "#f9240c"
|
||||
}
|
||||
standardTile("switch", "device.switch") {
|
||||
//state "programOff", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
|
||||
state "off", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
|
||||
state "programOn", label: 'Initialize Program', action: "programOff", icon: "st.contact.contact.open", backgroundColor: "#f6e10e"
|
||||
state "on", label: 'Program Running', action: "off", icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
|
||||
}
|
||||
standardTile("rainsensor", "device.rainsensor") {
|
||||
state "rainSensrooff", label: 'Rain Sensor Clear', icon: "st.Weather.weather14", backgroundColor: "#a9a9a9"
|
||||
state "rainSensoron", label: 'Rain Detected', icon: "st.Weather.weather10", backgroundColor: "#f6e10e"
|
||||
}
|
||||
standardTile("switch1", "device.switch1") {
|
||||
state "z1off", label: '1', action: "z1on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z1on", label: '1', action: "z1off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch2", "device.switch2") {
|
||||
state "z2off", label: '2', action: "z2on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z2on", label: '2', action: "z2off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch3", "device.switch3", inactiveLabel: false) {
|
||||
state "z3off", label: '3', action: "z3on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z3on", label: '3', action: "z3off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch4", "device.switch4", inactiveLabel: false) {
|
||||
state "z4off", label: '4', action: "z4on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z4on", label: '4', action: "z4off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch5", "device.switch5", inactiveLabel: false) {
|
||||
state "z5off", label: '5', action: "z5on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z5on", label: '5', action: "z5off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch6", "device.switch6", inactiveLabel: false) {
|
||||
state "z6off", label: '6', action: "z6on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z6on", label: '6', action: "z6off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch7", "device.switch7", inactiveLabel: false) {
|
||||
state "z7off", label: '7', action: "z7on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z7on", label: '7', action: "z7off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch8", "device.switch8", inactiveLabel: false) {
|
||||
state "z8off", label: '8', action: "z8on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z8on", label: '8', action: "z8off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch9", "device.switch9", inactiveLabel: false) {
|
||||
state "z9off", label: '9', action: "z9on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z9on", label: '9', action: "z9off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch10", "device.switch10", inactiveLabel: false) {
|
||||
state "z10off", label: '10', action: "z10on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z10on", label: '10', action: "z10off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch11", "device.switch11", inactiveLabel: false) {
|
||||
state "z11off", label: '11', action: "z11on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z11on", label: '11', action: "z11off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch12", "device.switch12", inactiveLabel: false) {
|
||||
state "z12off", label: '12', action: "z12on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z12on", label: '12', action: "z12off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch13", "device.switch13", inactiveLabel: false) {
|
||||
state "z13off", label: '13', action: "z13on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z13on", label: '13', action: "z13off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch14", "device.switch14", inactiveLabel: false) {
|
||||
state "z14off", label: '14', action: "z14on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z14on", label: '14', action: "z14off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch15", "device.switch15", inactiveLabel: false) {
|
||||
state "z15off", label: '15', action: "z15on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z15on", label: '15', action: "z15off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("switch16", "device.switch16", inactiveLabel: false) {
|
||||
state "z16off", label: '16', action: "z16on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
||||
state "z16on", label: '16', action: "z16off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", action: "refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
|
||||
main (["status"])
|
||||
details(["status","rainsensor","switch","switch1","switch2","switch3","switch4","switch5","switch6","switch7","switch8","switch9","switch10","switch11","switch12","switch13","switch14","switch15","switch16","refresh","configure"])
|
||||
}
|
||||
}
|
||||
|
||||
def programOn(){
|
||||
sendEvent(name: "switch", value: "programOn", descriptionText: "Program turned on")
|
||||
}
|
||||
|
||||
def programOff(){
|
||||
sendEvent(name: "switch", value: "off", descriptionText: "Program turned off")
|
||||
off()
|
||||
}
|
||||
|
||||
def updated(){
|
||||
log.debug "updated"
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
//log.debug "Parse description $description"
|
||||
def result = null
|
||||
def map = [:]
|
||||
if (description?.startsWith("read attr -")) {
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
//log.debug "Desc Map: $descMap"
|
||||
//using 000F cluster instead of 0006 (switch) because ST does not differentiate between EPs and processes all as switch
|
||||
if (descMap.cluster == "000F" && descMap.attrId == "0055") {
|
||||
log.debug "Zone"
|
||||
map = getZone(descMap)
|
||||
}
|
||||
else if (descMap.cluster == "0009" && descMap.attrId == "0000") {
|
||||
log.debug "Alarm"
|
||||
map = getAlarm(descMap)
|
||||
}
|
||||
}
|
||||
|
||||
if (map) {
|
||||
result = createEvent(map)
|
||||
}
|
||||
log.debug "Parse returned $map $result"
|
||||
return result
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
def getZone(descMap){
|
||||
def map = [:]
|
||||
|
||||
def EP = Integer.parseInt(descMap.endpoint.trim(), 16)
|
||||
|
||||
String onoff
|
||||
if(descMap.value == "00"){
|
||||
onoff = "off"
|
||||
}
|
||||
else onoff = "on"
|
||||
|
||||
if (EP == 1){
|
||||
map.name = "switch"
|
||||
map.value = onoff
|
||||
map.descriptionText = "${device.displayName} turned sprinkler program $onoff"
|
||||
}
|
||||
|
||||
else if (EP == 18) {
|
||||
map.name = "rainsensor"
|
||||
map.value = "rainSensor" + onoff
|
||||
map.descriptionText = "${device.displayName} rain sensor is $onoff"
|
||||
}
|
||||
else {
|
||||
EP -= 1
|
||||
map.name = "switch" + EP
|
||||
map.value = "z" + EP + onoff
|
||||
map.descriptionText = "${device.displayName} turned Zone $EP $onoff"
|
||||
}
|
||||
|
||||
map.isStateChange = true
|
||||
map.displayed = true
|
||||
return map
|
||||
}
|
||||
|
||||
def getAlarm(descMap){
|
||||
def map = [:]
|
||||
map.name = "status"
|
||||
def alarmID = Integer.parseInt(descMap.value.trim(), 16)
|
||||
log.debug "${alarmID}"
|
||||
if(alarmID <= 0) map.descriptionText = "${device.displayName} has rebooted, no other alarms"
|
||||
else map.descriptionText = "${device.displayName} rebooted, reported error on zone ${alarmID - 1}, please check zone is working correctly"
|
||||
map.value = "alarm"
|
||||
map.isStateChange = true
|
||||
map.displayed = true
|
||||
return map
|
||||
}
|
||||
|
||||
//status notify and change status
|
||||
def notify(value, text){
|
||||
sendEvent(name:"status", value:"$value", descriptionText:"$text", isStateChange: true, display: false)
|
||||
|
||||
}
|
||||
|
||||
//prefrences - rain sensor, manual time
|
||||
def rain() {
|
||||
log.debug "Rain $RainEnable"
|
||||
if (RainEnable) "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {01}"
|
||||
else "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {00}"
|
||||
}
|
||||
def manual(){
|
||||
log.debug "Time $ManualTime"
|
||||
def mTime = 10
|
||||
if (ManualTime) mTime = ManualTime
|
||||
def manualTime = hex(mTime)
|
||||
"st wattr 0x${device.deviceNetworkId} 1 6 0x4002 0x21 {00${manualTime}}"
|
||||
|
||||
}
|
||||
|
||||
//write switch time settings map
|
||||
def settingsMap(WriteTimes, attrType){
|
||||
log.debug WriteTimes
|
||||
|
||||
def i = 1
|
||||
def runTime
|
||||
def sendCmds = []
|
||||
while(i <= 17){
|
||||
|
||||
if (WriteTimes."${i}"){
|
||||
runTime = hex(Integer.parseInt(WriteTimes."${i}"))
|
||||
log.debug "${i} : $runTime"
|
||||
|
||||
if (attrType == 4001) sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4001 0x21 {00${runTime}}")
|
||||
else sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4002 0x21 {00${runTime}}")
|
||||
sendCmds.push("delay 500")
|
||||
}
|
||||
i++
|
||||
}
|
||||
return sendCmds
|
||||
}
|
||||
|
||||
//send switch time
|
||||
def writeType(wEP, cycle){
|
||||
log.debug "wt ${wEP} ${cycle}"
|
||||
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4001 0x21 {00" + hex(cycle) + "}"
|
||||
}
|
||||
//send switch off time
|
||||
def writeTime(wEP, runTime){
|
||||
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4002 0x21 {00" + hex(runTime) + "}"
|
||||
}
|
||||
|
||||
//set reporting and binding
|
||||
def configure() {
|
||||
|
||||
String zigbeeId = swapEndianHex(device.hub.zigbeeId)
|
||||
log.debug "Confuguring Reporting and Bindings ${device.deviceNetworkId} ${device.zigbeeId}"
|
||||
sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
|
||||
|
||||
def configCmds = [
|
||||
//program on/off
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x09 {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
//zones 1-8
|
||||
"zdo bind 0x${device.deviceNetworkId} 2 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 3 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 4 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 5 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 6 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 7 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 8 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 9 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
//zones 9-16
|
||||
"zdo bind 0x${device.deviceNetworkId} 10 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 11 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 12 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 13 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 14 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 15 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 16 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 17 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
||||
//rain sensor
|
||||
"zdo bind 0x${device.deviceNetworkId} 18 1 0x0F {${device.zigbeeId}} {}",
|
||||
|
||||
"zcl global send-me-a-report 6 0 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 2", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 3", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 4", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 5", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 6", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 7", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 8", "delay 500",
|
||||
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 9", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 10", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 11", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 12", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 13", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 14", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 15", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 16", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 17", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 18", "delay 500",
|
||||
|
||||
"zcl global send-me-a-report 0x09 0x00 0x21 1 0 {00}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
]
|
||||
return configCmds + rain() + manual()
|
||||
}
|
||||
|
||||
|
||||
|
||||
private hex(value) {
|
||||
new BigInteger(Math.round(value).toString()).toString(16)
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
|
||||
log.debug "refresh"
|
||||
def refreshCmds = [
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x0F 0x55", "delay 500",
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 2 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 3 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 4 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 5 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 6 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 7 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 8 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 9 0x0F 0x55", "delay 500",
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 10 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 11 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 12 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 13 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 14 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 15 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 16 0x0F 0x55", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 17 0x0F 0x55", "delay 500",
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 18 0x0F 0x51","delay 500",
|
||||
|
||||
]
|
||||
return refreshCmds + rain() + manual()
|
||||
}
|
||||
|
||||
// Commands to device
|
||||
//zones on - 8
|
||||
def on() {
|
||||
//sendEvent(name:"status", value:"active", descriptionText:"Program Running", isStateChange: true, display: false)
|
||||
log.debug "on"
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||
}
|
||||
def off() {
|
||||
log.debug "off"
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||
}
|
||||
def z1on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 2 6 1 {}"
|
||||
}
|
||||
def z1off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 2 6 0 {}"
|
||||
}
|
||||
def z2on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 3 6 1 {}"
|
||||
}
|
||||
def z2off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 3 6 0 {}"
|
||||
}
|
||||
def z3on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 4 6 1 {}"
|
||||
}
|
||||
def z3off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 4 6 0 {}"
|
||||
}
|
||||
def z4on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 5 6 1 {}"
|
||||
}
|
||||
def z4off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 5 6 0 {}"
|
||||
}
|
||||
def z5on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 6 6 1 {}"
|
||||
}
|
||||
def z5off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 6 6 0 {}"
|
||||
}
|
||||
def z6on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 7 6 1 {}"
|
||||
}
|
||||
def z6off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 7 6 0 {}"
|
||||
}
|
||||
def z7on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 8 6 1 {}"
|
||||
}
|
||||
def z7off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 8 6 0 {}"
|
||||
}
|
||||
def z8on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 9 6 1 {}"
|
||||
}
|
||||
def z8off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 9 6 0 {}"
|
||||
}
|
||||
|
||||
//zones 9 - 16
|
||||
def z9on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 10 6 1 {}"
|
||||
}
|
||||
def z9off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 10 6 0 {}"
|
||||
}
|
||||
def z10on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 11 6 1 {}"
|
||||
}
|
||||
def z10off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 11 6 0 {}"
|
||||
}
|
||||
def z11on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 12 6 1 {}"
|
||||
}
|
||||
def z11off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 12 6 0 {}"
|
||||
}
|
||||
def z12on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 13 6 1 {}"
|
||||
}
|
||||
def z12off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 13 6 0 {}"
|
||||
}
|
||||
def z13on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 14 6 1 {}"
|
||||
}
|
||||
def z13off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 14 6 0 {}"
|
||||
}
|
||||
def z14on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 15 6 1 {}"
|
||||
}
|
||||
def z14off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 15 6 0 {}"
|
||||
}
|
||||
def z15on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 16 6 1 {}"
|
||||
}
|
||||
def z15off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 16 6 0 {}"
|
||||
}
|
||||
def z16on() {
|
||||
"st cmd 0x${device.deviceNetworkId} 17 6 1 {}"
|
||||
}
|
||||
def z16off() {
|
||||
"st cmd 0x${device.deviceNetworkId} 17 6 0 {}"
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
/**
|
||||
* Spruce Sensor -Pre-release V2 10/8/2015
|
||||
*
|
||||
* Copyright 2014 Plaid Systems
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
-------10/20/2015 Updates--------
|
||||
-Fix/add battery reporting interval to update
|
||||
-remove polling and/or refresh(?)
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") {
|
||||
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Temperature Measurement"
|
||||
capability "Sensor"
|
||||
//capability "Polling"
|
||||
|
||||
attribute "maxHum", "string"
|
||||
attribute "minHum", "string"
|
||||
|
||||
command "resetHumidity"
|
||||
command "refresh"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01"
|
||||
}
|
||||
|
||||
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", title: ""
|
||||
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
input "interval", "number", title: "Measurement Interval 1-120 minutes (default: 10 minutes)", description: "Set how often you would like to check soil moisture in minutes", range: "1..120", defaultValue: 10, displayDuringSetup: false
|
||||
input "resetMinMax", "bool", title: "Reset Humidity min and max", required: false, displayDuringSetup: false
|
||||
}
|
||||
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", canChangeIcon: false, canChangeBackground: 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("humidity", "device.humidity", width: 2, height: 2, canChangeIcon: false, canChangeBackground: true) {
|
||||
state "humidity", label:'${currentValue}%', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#635C0C"],
|
||||
[value: 16, color: "#EBEB21"],
|
||||
[value: 22, color: "#C7DE6A"],
|
||||
[value: 42, color: "#9AD290"],
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("maxHum", "device.maxHum", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "maxHum", label:'High ${currentValue}%', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#635C0C"],
|
||||
[value: 16, color: "#EBEB21"],
|
||||
[value: 22, color: "#C7DE6A"],
|
||||
[value: 42, color: "#9AD290"],
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
}
|
||||
valueTile("minHum", "device.minHum", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "minHum", label:'Low ${currentValue}%', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#635C0C"],
|
||||
[value: 16, color: "#EBEB21"],
|
||||
[value: 22, color: "#C7DE6A"],
|
||||
[value: 42, color: "#9AD290"],
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", decoration: "flat", canChangeIcon: false, canChangeBackground: false) {
|
||||
state "battery", label:'${currentValue}% battery'
|
||||
}
|
||||
|
||||
main (["humidity"])
|
||||
details(["humidity","maxHum","minHum","temperature","battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "Parse description $description config: ${device.latestValue('configuration')} interval: $interval"
|
||||
|
||||
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)
|
||||
}
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
//check in configuration change
|
||||
if (!device.latestValue('configuration')) result = poll()
|
||||
if (device.latestValue('configuration').toInteger() != interval && interval != null) {
|
||||
result = poll()
|
||||
}
|
||||
log.debug "result: $result"
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def linkText = getLinkText(device)
|
||||
//log.debug "Catchall"
|
||||
def descMap = zigbee.parse(description)
|
||||
|
||||
//check humidity configuration is complete
|
||||
if (descMap.command == 0x07 && descMap.clusterId == 0x0405){
|
||||
def configInterval = 10
|
||||
if (interval != null) configInterval = interval
|
||||
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration Successful")
|
||||
//setConfig()
|
||||
log.debug "config complete"
|
||||
//return resultMap = [name: 'configuration', value: configInterval, descriptionText: "Settings configured successfully"]
|
||||
}
|
||||
else if (descMap.command == 0x0001){
|
||||
def hexString = "${hex(descMap.data[5])}" + "${hex(descMap.data[4])}"
|
||||
def intString = Integer.parseInt(hexString, 16)
|
||||
//log.debug "command: $descMap.command clusterid: $descMap.clusterId $hexString $intString"
|
||||
|
||||
if (descMap.clusterId == 0x0402){
|
||||
def value = getTemperature(hexString)
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
else if (descMap.clusterId == 0x0405){
|
||||
def value = Math.round(new BigDecimal(intString / 100)).toString()
|
||||
resultMap = getHumidityResult(value)
|
||||
|
||||
}
|
||||
else return null
|
||||
}
|
||||
else return null
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
def descMap = parseDescriptionAsMap(description)
|
||||
log.debug "Desc Map: $descMap"
|
||||
log.debug "Report Attributes"
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0001" && descMap.attrId == "0000") {
|
||||
resultMap = getBatteryResult(descMap.value)
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
|
||||
log.debug "parseCustom"
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
|
||||
resultMap = getTemperatureResult(value)
|
||||
}
|
||||
else if (description?.startsWith('humidity: ')) {
|
||||
def pct = (description - "humidity: " - "%").trim()
|
||||
if (pct.isNumber()) {
|
||||
def value = Math.round(new BigDecimal(pct)).toString()
|
||||
resultMap = getHumidityResult(value)
|
||||
} else {
|
||||
log.error "invalid humidity: ${pct}"
|
||||
}
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private Map getHumidityResult(value) {
|
||||
def linkText = getLinkText(device)
|
||||
def maxHumValue = 0
|
||||
def minHumValue = 0
|
||||
if (device.currentValue("maxHum") != null) maxHumValue = device.currentValue("maxHum").toInteger()
|
||||
if (device.currentValue("minHum") != null) minHumValue = device.currentValue("minHum").toInteger()
|
||||
log.debug "Humidity max: ${maxHumValue} min: ${minHumValue}"
|
||||
def compare = value.toInteger()
|
||||
|
||||
if (compare > maxHumValue) {
|
||||
sendEvent(name: 'maxHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture high is ${value}%")
|
||||
}
|
||||
else if (((compare < minHumValue) || (minHumValue <= 2)) && (compare != 0)) {
|
||||
sendEvent(name: 'minHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture low is ${value}%")
|
||||
}
|
||||
|
||||
return [
|
||||
name: 'humidity',
|
||||
value: value,
|
||||
unit: '%',
|
||||
descriptionText: "${linkText} soil moisture is ${value}%"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = (Integer.parseInt(value, 16).shortValue()/100)
|
||||
//log.debug "Report Temp $value : $celsius C"
|
||||
if(getTemperatureScale() == "C"){
|
||||
return celsius
|
||||
} else {
|
||||
return celsiusToFahrenheit(celsius) as Integer
|
||||
}
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature: $value"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = "${linkText} is ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
private Map getBatteryResult(value) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
def min = 2500
|
||||
def percent = ((Integer.parseInt(value, 16) - min) / 5)
|
||||
percent = Math.max(0, Math.min(percent, 100.0))
|
||||
result.value = Math.round(percent)
|
||||
|
||||
def descriptionText
|
||||
if (percent < 10) result.descriptionText = "${linkText} battery is getting low $percent %."
|
||||
else result.descriptionText = "${linkText} battery is ${result.value}%"
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def resetHumidity(){
|
||||
def linkText = getLinkText(device)
|
||||
def minHumValue = 0
|
||||
def maxHumValue = 0
|
||||
sendEvent(name: 'minHum', value: minHumValue, unit: '%', descriptionText: "${linkText} min soil moisture reset to ${minHumValue}%")
|
||||
sendEvent(name: 'maxHum', value: maxHumValue, unit: '%', descriptionText: "${linkText} max soil moisture reset to ${maxHumValue}%")
|
||||
}
|
||||
|
||||
def setConfig(){
|
||||
def configInterval = 100
|
||||
if (interval != null) configInterval = interval
|
||||
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized")
|
||||
}
|
||||
|
||||
//when device preferences are changed
|
||||
def updated(){
|
||||
log.debug "device updated"
|
||||
if (!device.latestValue('configuration')) configure()
|
||||
else{
|
||||
if (resetMinMax == true) resetHumidity()
|
||||
if (device.latestValue('configuration').toInteger() != interval && interval != null){
|
||||
sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//poll
|
||||
def poll() {
|
||||
log.debug "poll called"
|
||||
List cmds = []
|
||||
if (!device.latestValue('configuration')) cmds += configure()
|
||||
else if (device.latestValue('configuration').toInteger() != interval && interval != null) {
|
||||
cmds += intervalUpdate()
|
||||
}
|
||||
//cmds += refresh()
|
||||
log.debug "commands $cmds"
|
||||
return cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
|
||||
//update intervals
|
||||
def intervalUpdate(){
|
||||
log.debug "intervalUpdate"
|
||||
def minReport = 10
|
||||
def maxReport = 610
|
||||
if (interval != null) {
|
||||
minReport = interval
|
||||
maxReport = interval * 61
|
||||
}
|
||||
[
|
||||
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
]
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh"
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x405 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0"
|
||||
]
|
||||
}
|
||||
|
||||
//configure
|
||||
def configure() {
|
||||
//set minReport = measurement in minutes
|
||||
def minReport = 10
|
||||
def maxReport = 610
|
||||
|
||||
//String zigbeeId = swapEndianHex(device.hub.zigbeeId)
|
||||
//log.debug "zigbeeid ${device.zigbeeId} deviceId ${device.deviceNetworkId}"
|
||||
if (!device.zigbeeId) sendEvent(name: 'configuration',value: 0, descriptionText: "Device Zigbee Id not found, remove and attempt to rejoin device")
|
||||
else sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
|
||||
//log.debug "Configuring Reporting and Bindings. min: $minReport max: $maxReport "
|
||||
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x405 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 1000",
|
||||
|
||||
//temperature
|
||||
"zcl global send-me-a-report 0x402 0x0000 0x29 1 0 {3200}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
//min = soil measure interval
|
||||
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||
|
||||
//min = battery measure interval 1 = 1 hour
|
||||
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||
] + refresh()
|
||||
}
|
||||
|
||||
private hex(value) {
|
||||
new BigInteger(Math.round(value).toString()).toString(16)
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/**
|
||||
* Simple Sync
|
||||
*
|
||||
* Copyright 2015 Roomie Remote, Inc.
|
||||
*
|
||||
* Date: 2015-09-22
|
||||
*/
|
||||
metadata
|
||||
{
|
||||
definition (name: "Simple Sync", namespace: "roomieremote-agent", author: "Roomie Remote, Inc.")
|
||||
{
|
||||
capability "Media Controller"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator
|
||||
{
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles
|
||||
{
|
||||
standardTile("mainTile", "device.status", width: 1, height: 1, icon: "st.Entertainment.entertainment11")
|
||||
{
|
||||
state "default", label: "Simple Sync", icon: "st.Home.home2", backgroundColor: "#55A7FF"
|
||||
}
|
||||
|
||||
def detailTiles = ["mainTile"]
|
||||
|
||||
main "mainTile"
|
||||
details(detailTiles)
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description)
|
||||
{
|
||||
def results = []
|
||||
|
||||
try
|
||||
{
|
||||
def msg = parseLanMessage(description)
|
||||
|
||||
if (msg.headers && msg.body)
|
||||
{
|
||||
switch (msg.headers["X-Roomie-Echo"])
|
||||
{
|
||||
case "getAllActivities":
|
||||
handleGetAllActivitiesResponse(msg)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
sendEvent(name: "parseError", value: "$t", description: description)
|
||||
throw t
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
def handleGetAllActivitiesResponse(response)
|
||||
{
|
||||
def body = parseJson(response.body)
|
||||
|
||||
if (body.status == "success")
|
||||
{
|
||||
def json = new groovy.json.JsonBuilder()
|
||||
def root = json activities: body.data
|
||||
def data = json.toString()
|
||||
|
||||
sendEvent(name: "activities", value: data)
|
||||
}
|
||||
}
|
||||
|
||||
def getAllActivities(evt)
|
||||
{
|
||||
def host = getHostAddress(device.deviceNetworkId)
|
||||
|
||||
def action = new physicalgraph.device.HubAction(method: "GET",
|
||||
path: "/api/v1/activities",
|
||||
headers: [HOST: host, "X-Roomie-Echo": "getAllActivities"])
|
||||
|
||||
action
|
||||
}
|
||||
|
||||
def startActivity(evt)
|
||||
{
|
||||
def uuid = evt
|
||||
def host = getHostAddress(device.deviceNetworkId)
|
||||
def activity = new groovy.json.JsonSlurper().parseText(device.currentValue('activities') ?: "{ 'activities' : [] }").activities.find { it.uuid == uuid }
|
||||
def toggle = activity["toggle"]
|
||||
def jsonMap = ["activity_uuid": uuid]
|
||||
|
||||
if (toggle != null)
|
||||
{
|
||||
jsonMap << ["toggle_state": toggle ? "on" : "off"]
|
||||
}
|
||||
|
||||
def json = new groovy.json.JsonBuilder(jsonMap)
|
||||
def jsonBody = json.toString()
|
||||
def headers = [HOST: host, "Content-Type": "application/json"]
|
||||
|
||||
def action = new physicalgraph.device.HubAction(method: "POST",
|
||||
path: "/api/v1/runactivity",
|
||||
body: jsonBody,
|
||||
headers: headers)
|
||||
|
||||
action
|
||||
}
|
||||
|
||||
def getHostAddress(d)
|
||||
{
|
||||
def parts = d.split(":")
|
||||
def ip = convertHexToIP(parts[0])
|
||||
def port = convertHexToInt(parts[1])
|
||||
return ip + ":" + port
|
||||
}
|
||||
|
||||
def String convertHexToIP(hex)
|
||||
{
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
def Integer convertHexToInt(hex)
|
||||
{
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
@@ -11,7 +11,6 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Tone"
|
||||
@@ -33,7 +32,7 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input "checkInterval", "enum", title: "Presence timeout (minutes)", description: "Tap to set",
|
||||
input "checkInterval", "enum", title: "Presence timeout (minutes)",
|
||||
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
@@ -83,6 +82,7 @@ def parse(String description) {
|
||||
|
||||
private handleReportAttributeMessage(String description) {
|
||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||
|
||||
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
|
||||
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
@@ -94,7 +94,6 @@ private handleReportAttributeMessage(String description) {
|
||||
* @param volts Battery voltage in .1V increments
|
||||
*/
|
||||
private handleBatteryEvent(volts) {
|
||||
def descriptionText
|
||||
if (volts == 0 || volts == 255) {
|
||||
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
|
||||
}
|
||||
@@ -108,17 +107,15 @@ private handleBatteryEvent(volts) {
|
||||
volts = minVolts
|
||||
else if (volts > maxVolts)
|
||||
volts = maxVolts
|
||||
def value = batteryMap[volts]
|
||||
if (value != null) {
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
def linkText = getLinkText(device)
|
||||
descriptionText = '{{ linkText }} battery was {{ value }}'
|
||||
def eventMap = [
|
||||
name: 'battery',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
value: pct,
|
||||
descriptionText: "${linkText} battery was ${pct}%"
|
||||
]
|
||||
log.debug "Creating battery event for voltage=${volts/10}V: ${linkText} ${eventMap.name} is ${eventMap.value}%"
|
||||
log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
}
|
||||
@@ -134,19 +131,13 @@ private handlePresenceEvent(present) {
|
||||
stopTimer()
|
||||
}
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText
|
||||
if ( present )
|
||||
descriptionText = "{{ linkText }} has arrived"
|
||||
else
|
||||
descriptionText = "{{ linkText }} has left"
|
||||
def eventMap = [
|
||||
name: "presence",
|
||||
value: present ? "present" : "not present",
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
|
||||
]
|
||||
log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value}"
|
||||
log.debug "Creating presence event: ${eventMap}"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
|
||||
@@ -167,4 +158,4 @@ def checkPresenceCallback() {
|
||||
if (timeSinceLastCheckin >= theCheckInterval) {
|
||||
handlePresenceEvent(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +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.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Set Device Image'''.ko=기기 이미지 설정
|
||||
'''Presence timeout (minutes)'''.ko=알람 유예 시간 설정 (분)
|
||||
'''Tap to set'''.ko=눌러서 설정
|
||||
'''Arrival Sensor'''.ko=도착알림 센서
|
||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||
# Events / Notifications
|
||||
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}의 남은 배터리 {{ value }}
|
||||
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
|
||||
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
|
||||
#==============================================================================
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Cree Bulb
|
||||
*
|
||||
* Copyright 2016 SmartThings
|
||||
* Copyright 2014 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:
|
||||
@@ -13,32 +13,31 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
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: "0000,0019"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
status "on": "on/off: 1"
|
||||
status "off": "on/off: 0"
|
||||
// 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"
|
||||
}
|
||||
// reply messages
|
||||
reply "zcl on-off on": "on/off: 1"
|
||||
reply "zcl on-off off": "on/off: 0"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
@@ -63,12 +62,18 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getEvent(description)
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
sendEvent(resultMap)
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
@@ -82,17 +87,13 @@ def on() {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||
}
|
||||
|
||||
def poll() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
|
||||
@@ -138,7 +138,6 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
||||
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])
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
* Ecobee Sensor
|
||||
*
|
||||
* Copyright 2015 Juan Risso
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -10,9 +12,6 @@
|
||||
* 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.
|
||||
*
|
||||
* Ecobee Sensor
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -20,22 +19,18 @@ metadata {
|
||||
capability "Temperature Measurement"
|
||||
capability "Motion Sensor"
|
||||
capability "Refresh"
|
||||
capability "Polling"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state("temperature", label:'${currentValue}°', unit:"F",
|
||||
backgroundColors:[
|
||||
// Celsius
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 28, color: "#f1d801"],
|
||||
[value: 35, color: "#d04e00"],
|
||||
[value: 37, color: "#bc2323"],
|
||||
// Fahrenheit
|
||||
[value: 40, color: "#153591"],
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
@@ -47,8 +42,8 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("motion", "device.motion") {
|
||||
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
|
||||
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
|
||||
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
||||
@@ -61,12 +56,16 @@ metadata {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh called"
|
||||
log.debug "refresh..."
|
||||
poll()
|
||||
}
|
||||
|
||||
void poll() {
|
||||
log.debug "Executing 'poll' using parent SmartApp"
|
||||
parent.pollChild()
|
||||
|
||||
parent.pollChildren(this)
|
||||
}
|
||||
|
||||
//generate custom mobile activity feeds event
|
||||
def generateActivityFeedsEvent(notificationMessage) {
|
||||
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
|
||||
}
|
||||
|
||||
@@ -19,48 +19,34 @@ metadata {
|
||||
definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Thermostat"
|
||||
capability "Temperature Measurement"
|
||||
capability "Polling"
|
||||
capability "Sensor"
|
||||
capability "Refresh"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Refresh"
|
||||
|
||||
command "generateEvent"
|
||||
command "raiseSetpoint"
|
||||
command "lowerSetpoint"
|
||||
command "resumeProgram"
|
||||
command "switchMode"
|
||||
command "switchFanMode"
|
||||
command "generateEvent"
|
||||
command "raiseSetpoint"
|
||||
command "lowerSetpoint"
|
||||
command "resumeProgram"
|
||||
command "switchMode"
|
||||
|
||||
attribute "thermostatSetpoint","number"
|
||||
attribute "thermostatStatus","string"
|
||||
attribute "maxHeatingSetpoint", "number"
|
||||
attribute "minHeatingSetpoint", "number"
|
||||
attribute "maxCoolingSetpoint", "number"
|
||||
attribute "minCoolingSetpoint", "number"
|
||||
attribute "deviceTemperatureUnit", "number"
|
||||
attribute "thermostatSetpoint","number"
|
||||
attribute "thermostatStatus","string"
|
||||
}
|
||||
|
||||
tiles {
|
||||
simulator { }
|
||||
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state("temperature", label:'${currentValue}°', unit:"F",
|
||||
backgroundColors:[
|
||||
// Celsius
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 28, color: "#f1d801"],
|
||||
[value: 35, color: "#d04e00"],
|
||||
[value: 37, color: "#bc2323"],
|
||||
// Fahrenheit
|
||||
[value: 40, 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"]
|
||||
]
|
||||
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("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
|
||||
@@ -72,9 +58,10 @@ metadata {
|
||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||
}
|
||||
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
|
||||
state "auto", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-auto"
|
||||
state "on", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-on"
|
||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||
state "auto", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on"
|
||||
state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off"
|
||||
state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate"
|
||||
state "circulate", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "auto"
|
||||
}
|
||||
standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
|
||||
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up"
|
||||
@@ -104,14 +91,11 @@ metadata {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") {
|
||||
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume', icon:"st.samsung.da.oven_ic_send"
|
||||
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send"
|
||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||
}
|
||||
valueTile("humidity", "device.humidity", decoration: "flat") {
|
||||
state "humidity", label:'${currentValue}%'
|
||||
}
|
||||
main "temperature"
|
||||
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","humidity", "resumeProgram", "refresh"])
|
||||
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"])
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -123,6 +107,8 @@ metadata {
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
// TODO: handle '' attribute
|
||||
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
@@ -133,7 +119,9 @@ def refresh() {
|
||||
|
||||
void poll() {
|
||||
log.debug "Executing 'poll' using parent SmartApp"
|
||||
parent.pollChild()
|
||||
|
||||
def results = parent.pollChild(this)
|
||||
generateEvent(results) //parse received message from parent
|
||||
}
|
||||
|
||||
def generateEvent(Map results) {
|
||||
@@ -145,27 +133,16 @@ def generateEvent(Map results) {
|
||||
def isChange = false
|
||||
def isDisplayed = true
|
||||
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
|
||||
handlerName: name]
|
||||
handlerName: name]
|
||||
|
||||
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
|
||||
def sendValue = value? convertTemperatureIfNeeded(value.toDouble(), "F", 1): value //API return temperature value in F
|
||||
isChange = isTemperatureStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
|
||||
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
event << [value: sendValue, displayed: false]
|
||||
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
||||
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
||||
} else if (name=="thermostatFanMode"){
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
||||
} else if (name=="humidity") {
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
|
||||
} else {
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
@@ -181,19 +158,13 @@ def generateEvent(Map results) {
|
||||
//return descriptionText to be shown on mobile activity feed
|
||||
private getThermostatDescriptionText(name, value, linkText) {
|
||||
if(name == "temperature") {
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
return "$linkText temperature is $sendValue ${location.temperatureScale}"
|
||||
return "$linkText temperature is $value°F"
|
||||
|
||||
} else if(name == "heatingSetpoint") {
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
return "heating setpoint is $sendValue ${location.temperatureScale}"
|
||||
return "heating setpoint is $value°F"
|
||||
|
||||
} else if(name == "coolingSetpoint"){
|
||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
||||
return "cooling setpoint is $sendValue ${location.temperatureScale}"
|
||||
return "cooling setpoint is $value°F"
|
||||
|
||||
} else if (name == "thermostatMode") {
|
||||
return "thermostat mode is ${value}"
|
||||
@@ -201,26 +172,26 @@ private getThermostatDescriptionText(name, value, linkText) {
|
||||
} else if (name == "thermostatFanMode") {
|
||||
return "thermostat fan mode is ${value}"
|
||||
|
||||
} else if (name == "humidity") {
|
||||
return "humidity is ${value} %"
|
||||
} else {
|
||||
return "${name} = ${value}"
|
||||
}
|
||||
}
|
||||
|
||||
void setHeatingSetpoint(setpoint) {
|
||||
log.debug "***heating setpoint $setpoint"
|
||||
setHeatingSetpoint(setpoint.toDouble())
|
||||
}
|
||||
|
||||
void setHeatingSetpoint(Double setpoint) {
|
||||
// def mode = device.currentValue("thermostatMode")
|
||||
def heatingSetpoint = setpoint
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
||||
|
||||
//enforce limits of heatingSetpoint
|
||||
if (heatingSetpoint > maxHeatingSetpoint) {
|
||||
heatingSetpoint = maxHeatingSetpoint
|
||||
} else if (heatingSetpoint < minHeatingSetpoint) {
|
||||
heatingSetpoint = minHeatingSetpoint
|
||||
if (heatingSetpoint > 79) {
|
||||
heatingSetpoint = 79
|
||||
} else if (heatingSetpoint < 45) {
|
||||
heatingSetpoint = 45
|
||||
}
|
||||
|
||||
//enforce limits of heatingSetpoint vs coolingSetpoint
|
||||
@@ -230,34 +201,32 @@ void setHeatingSetpoint(setpoint) {
|
||||
|
||||
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
|
||||
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
|
||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
||||
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||
generateSetpointEvent()
|
||||
generateStatusEvent()
|
||||
} else {
|
||||
log.error "Error setHeatingSetpoint(setpoint)"
|
||||
log.error "Error setHeatingSetpoint(setpoint)" //This error is handled by the connect app
|
||||
}
|
||||
}
|
||||
|
||||
void setCoolingSetpoint(setpoint) {
|
||||
log.debug "***cooling setpoint $setpoint"
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
setCoolingSetpoint(setpoint.toDouble())
|
||||
}
|
||||
|
||||
void setCoolingSetpoint(Double setpoint) {
|
||||
// def mode = device.currentValue("thermostatMode")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
|
||||
def coolingSetpoint = setpoint
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||
|
||||
|
||||
if (coolingSetpoint > maxCoolingSetpoint) {
|
||||
coolingSetpoint = maxCoolingSetpoint
|
||||
} else if (coolingSetpoint < minCoolingSetpoint) {
|
||||
coolingSetpoint = minCoolingSetpoint
|
||||
if (coolingSetpoint > 92) {
|
||||
coolingSetpoint = 92
|
||||
} else if (coolingSetpoint < 65) {
|
||||
coolingSetpoint = 65
|
||||
}
|
||||
|
||||
//enforce limits of heatingSetpoint vs coolingSetpoint
|
||||
@@ -267,18 +236,15 @@ void setCoolingSetpoint(setpoint) {
|
||||
|
||||
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
|
||||
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
|
||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
||||
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
|
||||
generateSetpointEvent()
|
||||
generateStatusEvent()
|
||||
} else {
|
||||
log.error "Error setCoolingSetpoint(setpoint)"
|
||||
log.error "Error setCoolingSetpoint(setpoint)" //This error is handled by the connect app
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +278,7 @@ def modes() {
|
||||
}
|
||||
|
||||
def fanModes() {
|
||||
["on", "auto"]
|
||||
["off", "on", "auto", "circulate"]
|
||||
}
|
||||
|
||||
def switchMode() {
|
||||
@@ -341,15 +307,17 @@ def switchFanMode() {
|
||||
def returnCommand
|
||||
|
||||
switch (currentFanMode) {
|
||||
case "on":
|
||||
returnCommand = switchToFanMode("auto")
|
||||
case "fanAuto":
|
||||
returnCommand = switchToFanMode("fanOn")
|
||||
break
|
||||
case "auto":
|
||||
returnCommand = switchToFanMode("on")
|
||||
case "fanOn":
|
||||
returnCommand = switchToFanMode("fanCirculate")
|
||||
break
|
||||
case "fanCirculate":
|
||||
returnCommand = switchToFanMode("fanAuto")
|
||||
break
|
||||
|
||||
}
|
||||
if(!currentFanMode) { returnCommand = switchToFanMode("auto") }
|
||||
if(!currentFanMode) { returnCommand = switchToFanMode("fanOn") }
|
||||
returnCommand
|
||||
}
|
||||
|
||||
@@ -358,20 +326,25 @@ def switchToFanMode(nextMode) {
|
||||
log.debug "switching to fan mode: $nextMode"
|
||||
def returnCommand
|
||||
|
||||
if(nextMode == "auto") {
|
||||
if(!fanModes.contains("auto")) {
|
||||
if(nextMode == "fanAuto") {
|
||||
if(!fanModes.contains("fanAuto")) {
|
||||
returnCommand = fanAuto()
|
||||
} else {
|
||||
returnCommand = switchToFanMode("on")
|
||||
returnCommand = switchToFanMode("fanOn")
|
||||
}
|
||||
} else if(nextMode == "on") {
|
||||
if(!fanModes.contains("on")) {
|
||||
} else if(nextMode == "fanOn") {
|
||||
if(!fanModes.contains("fanOn")) {
|
||||
returnCommand = fanOn()
|
||||
} else {
|
||||
returnCommand = switchToFanMode("auto")
|
||||
returnCommand = switchToFanMode("fanCirculate")
|
||||
}
|
||||
} else if(nextMode == "fanCirculate") {
|
||||
if(!fanModes.contains("fanCirculate")) {
|
||||
returnCommand = fanCirculate()
|
||||
} else {
|
||||
returnCommand = switchToFanMode("fanAuto")
|
||||
}
|
||||
}
|
||||
|
||||
returnCommand
|
||||
}
|
||||
|
||||
@@ -379,16 +352,15 @@ def getDataByName(String name) {
|
||||
state[name] ?: device.getDataValue(name)
|
||||
}
|
||||
|
||||
def setThermostatMode(String mode) {
|
||||
log.debug "setThermostatMode($mode)"
|
||||
mode = mode.toLowerCase()
|
||||
switchToMode(mode)
|
||||
def setThermostatMode(String value) {
|
||||
log.debug "setThermostatMode({$value})"
|
||||
|
||||
}
|
||||
|
||||
def setThermostatFanMode(String mode) {
|
||||
log.debug "setThermostatFanMode($mode)"
|
||||
mode = mode.toLowerCase()
|
||||
switchToFanMode(mode)
|
||||
def setThermostatFanMode(String value) {
|
||||
|
||||
log.debug "setThermostatFanMode({$value})"
|
||||
|
||||
}
|
||||
|
||||
def generateModeEvent(mode) {
|
||||
@@ -396,7 +368,7 @@ def generateModeEvent(mode) {
|
||||
}
|
||||
|
||||
def generateFanModeEvent(fanMode) {
|
||||
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${fanMode} mode", displayed: true)
|
||||
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${mode} mode", displayed: true)
|
||||
}
|
||||
|
||||
def generateOperatingStateEvent(operatingState) {
|
||||
@@ -431,10 +403,6 @@ def heat() {
|
||||
generateStatusEvent()
|
||||
}
|
||||
|
||||
def emergencyHeat() {
|
||||
auxHeatOnly()
|
||||
}
|
||||
|
||||
def auxHeatOnly() {
|
||||
log.debug "auxHeatOnly"
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
@@ -479,44 +447,26 @@ def auto() {
|
||||
|
||||
def fanOn() {
|
||||
log.debug "fanOn"
|
||||
String fanMode = "on"
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
// parent.setFanMode (this,"on")
|
||||
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||
|
||||
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
||||
generateFanModeEvent(fanMode)
|
||||
} else {
|
||||
log.debug "Error setting new mode."
|
||||
def currentFanMode = device.currentState("thermostatFanMode")?.value
|
||||
generateFanModeEvent(currentFanMode) // reset the tile back
|
||||
}
|
||||
}
|
||||
|
||||
def fanAuto() {
|
||||
log.debug "fanAuto"
|
||||
String fanMode = "auto"
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
// parent.setFanMode (this,"auto")
|
||||
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
}
|
||||
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
||||
def fanCirculate() {
|
||||
log.debug "fanCirculate"
|
||||
// parent.setFanMode (this,"circulate")
|
||||
|
||||
}
|
||||
|
||||
def fanOff() {
|
||||
log.debug "fanOff"
|
||||
// parent.setFanMode (this,"off")
|
||||
|
||||
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
||||
generateFanModeEvent(fanMode)
|
||||
} else {
|
||||
log.debug "Error setting new mode."
|
||||
def currentFanMode = device.currentState("thermostatFanMode")?.value
|
||||
generateFanModeEvent(currentFanMode) // reset the tile back
|
||||
}
|
||||
}
|
||||
|
||||
def generateSetpointEvent() {
|
||||
@@ -526,42 +476,20 @@ def generateSetpointEvent() {
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
log.debug "Current Mode = ${mode}"
|
||||
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
log.debug "Heating Setpoint = ${heatingSetpoint}"
|
||||
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
log.debug "Cooling Setpoint = ${coolingSetpoint}"
|
||||
|
||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||
|
||||
if(location.temperatureScale == "C")
|
||||
{
|
||||
maxHeatingSetpoint = roundC(maxHeatingSetpoint)
|
||||
maxCoolingSetpoint = roundC(maxCoolingSetpoint)
|
||||
minHeatingSetpoint = roundC(minHeatingSetpoint)
|
||||
minCoolingSetpoint = roundC(minCoolingSetpoint)
|
||||
heatingSetpoint = roundC(heatingSetpoint)
|
||||
coolingSetpoint = roundC(coolingSetpoint)
|
||||
}
|
||||
|
||||
|
||||
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
|
||||
|
||||
|
||||
if (mode == "heat") {
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
|
||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
|
||||
|
||||
}
|
||||
else if (mode == "cool") {
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
|
||||
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString())
|
||||
|
||||
} else if (mode == "auto") {
|
||||
|
||||
@@ -571,9 +499,9 @@ def generateSetpointEvent() {
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":"Off")
|
||||
|
||||
} else if (mode == "auxHeatOnly") {
|
||||
} else if (mode == "emergencyHeat") {
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
|
||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
|
||||
|
||||
}
|
||||
|
||||
@@ -582,30 +510,26 @@ def generateSetpointEvent() {
|
||||
void raiseSetpoint() {
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
def targetvalue
|
||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||
|
||||
|
||||
if (mode == "off" || mode == "auto") {
|
||||
log.warn "this mode: $mode does not allow raiseSetpoint"
|
||||
} else {
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
|
||||
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
||||
|
||||
if (device.latestState('thermostatSetpoint')) {
|
||||
targetvalue = device.latestState('thermostatSetpoint').value
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
|
||||
targetvalue = device.latestState('thermostatSetpoint').value as Integer
|
||||
} else {
|
||||
targetvalue = 0
|
||||
}
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
|
||||
targetvalue = targetvalue + 1
|
||||
|
||||
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
|
||||
targetvalue = maxHeatingSetpoint
|
||||
} else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
|
||||
targetvalue = maxCoolingSetpoint
|
||||
if (mode == "heat" && targetvalue > 79) {
|
||||
targetvalue = 79
|
||||
} else if (mode == "cool" && targetvalue > 92) {
|
||||
targetvalue = 92
|
||||
}
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
||||
@@ -619,29 +543,25 @@ void raiseSetpoint() {
|
||||
void lowerSetpoint() {
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
def targetvalue
|
||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||
|
||||
|
||||
if (mode == "off" || mode == "auto") {
|
||||
log.warn "this mode: $mode does not allow lowerSetpoint"
|
||||
} else {
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
|
||||
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
||||
if (device.latestState('thermostatSetpoint')) {
|
||||
targetvalue = device.latestState('thermostatSetpoint').value
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
|
||||
targetvalue = device.latestState('thermostatSetpoint').value as Integer
|
||||
} else {
|
||||
targetvalue = 0
|
||||
}
|
||||
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
|
||||
targetvalue = targetvalue - 1
|
||||
|
||||
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
|
||||
targetvalue = minHeatingSetpoint
|
||||
} else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
|
||||
targetvalue = minCoolingSetpoint
|
||||
if (mode == "heat" && targetvalue.toInteger() < 45) {
|
||||
targetvalue = 45
|
||||
} else if (mode == "cool" && targetvalue.toInteger() < 65) {
|
||||
targetvalue = 65
|
||||
}
|
||||
|
||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
||||
@@ -655,15 +575,15 @@ void lowerSetpoint() {
|
||||
void alterSetpoint(temp) {
|
||||
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
|
||||
def targetHeatingSetpoint
|
||||
def targetCoolingSetpoint
|
||||
|
||||
//step1: check thermostatMode, enforce limits before sending request to cloud
|
||||
if (mode == "heat" || mode == "auxHeatOnly"){
|
||||
if (mode == "heat"){
|
||||
if (temp.value > coolingSetpoint){
|
||||
targetHeatingSetpoint = temp.value
|
||||
targetCoolingSetpoint = temp.value
|
||||
@@ -682,22 +602,19 @@ void alterSetpoint(temp) {
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
|
||||
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
|
||||
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to ${targetHeatingSetpoint} " +
|
||||
"coolingSetpoint to ${targetCoolingSetpoint} with holdType : ${holdType}"
|
||||
|
||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||
|
||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
|
||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
|
||||
|
||||
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
|
||||
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
|
||||
//step2: call parent.setHold to send http request to 3rd party cloud
|
||||
if (parent.setHold(this, targetHeatingSetpoint, targetCoolingSetpoint, deviceId, sendHoldType)) {
|
||||
sendEvent("name": "thermostatSetpoint", "value": temp.value.toString(), displayed: false)
|
||||
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
|
||||
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
|
||||
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
|
||||
} else {
|
||||
log.error "Error alterSetpoint()"
|
||||
if (mode == "heat" || mode == "auxHeatOnly"){
|
||||
if (mode == "heat"){
|
||||
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
||||
} else if (mode == "cool") {
|
||||
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
|
||||
@@ -709,9 +626,9 @@ void alterSetpoint(temp) {
|
||||
def generateStatusEvent() {
|
||||
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def temperature = device.currentValue("temperature")
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||
def temperature = device.currentValue("temperature").toInteger()
|
||||
|
||||
def statusText
|
||||
|
||||
@@ -726,14 +643,14 @@ def generateStatusEvent() {
|
||||
if (temperature >= heatingSetpoint)
|
||||
statusText = "Right Now: Idle"
|
||||
else
|
||||
statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
|
||||
statusText = "Heating to ${heatingSetpoint}° F"
|
||||
|
||||
} else if (mode == "cool") {
|
||||
|
||||
if (temperature <= coolingSetpoint)
|
||||
statusText = "Right Now: Idle"
|
||||
else
|
||||
statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
|
||||
statusText = "Cooling to ${coolingSetpoint}° F"
|
||||
|
||||
} else if (mode == "auto") {
|
||||
|
||||
@@ -743,7 +660,7 @@ def generateStatusEvent() {
|
||||
|
||||
statusText = "Right Now: Off"
|
||||
|
||||
} else if (mode == "auxHeatOnly") {
|
||||
} else if (mode == "emergencyHeat") {
|
||||
|
||||
statusText = "Emergency Heat"
|
||||
|
||||
@@ -756,18 +673,7 @@ def generateStatusEvent() {
|
||||
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
|
||||
}
|
||||
|
||||
//generate custom mobile activity feeds event
|
||||
def generateActivityFeedsEvent(notificationMessage) {
|
||||
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
|
||||
}
|
||||
|
||||
def roundC (tempC) {
|
||||
return (Math.round(tempC.toDouble() * 2))/2
|
||||
}
|
||||
|
||||
def convertFtoC (tempF) {
|
||||
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
|
||||
}
|
||||
|
||||
def convertCtoF (tempC) {
|
||||
return (Math.round(tempC * (9/5)) + 32).toInteger()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 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
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -36,73 +36,155 @@
|
||||
* Slider range from 0..100
|
||||
* Change 9: 2015-03-06 (Juan Risso)
|
||||
* Setlevel -> value to integer (to prevent smartapp calling this function from not working).
|
||||
* Change 10: 2016-03-06 (Vinay Rao/Tom Manley)
|
||||
* changed 2/3rds of the file to clean up code and add zigbee library improvements
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Sensor"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Switch Level"
|
||||
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", 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
|
||||
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.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState:"turningOff"
|
||||
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action: "switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action: "switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
||||
state "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "level", "levelSliderControl", "refresh"])
|
||||
}
|
||||
|
||||
preferences {
|
||||
|
||||
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
|
||||
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
|
||||
|
||||
preferences {
|
||||
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
|
||||
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
def resultMap = zigbee.getEvent(description)
|
||||
if (resultMap) {
|
||||
if ((resultMap.name == "level" && state.trigger == "setLevel") || resultMap.name != "level") { //doing this to account for weird level reporting bug with GE Link Bulbs
|
||||
sendEvent(resultMap)
|
||||
log.trace description
|
||||
|
||||
if (description?.startsWith("on/off:")) {
|
||||
log.debug "The bulb was sent a command to do something just now..."
|
||||
if (description[-1] == "1") {
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "On command was sent maybe from manually turning on? : Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
} else if (description[-1] == "0") {
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "Off command was sent : Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
|
||||
def msg = zigbee.parse(description)
|
||||
|
||||
if (description?.startsWith("catchall:")) {
|
||||
// log.trace msg
|
||||
// log.trace "data: $msg.data"
|
||||
|
||||
def x = description[-4..-1]
|
||||
// log.debug x
|
||||
|
||||
switch (x)
|
||||
{
|
||||
|
||||
case "0000":
|
||||
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
|
||||
case "1000":
|
||||
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
|
||||
case "0100":
|
||||
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
|
||||
case "1001":
|
||||
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (description?.startsWith("read attr")) {
|
||||
|
||||
// log.trace description[27..28]
|
||||
// log.trace description[-2..-1]
|
||||
|
||||
if (description[27..28] == "0A") {
|
||||
|
||||
// log.debug description[-2..-1]
|
||||
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||
sendEvent( name: "level", value: i )
|
||||
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
|
||||
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
if (description[-2..-1] == "00" && state.trigger == "setLevel") {
|
||||
// log.debug description[-2..-1]
|
||||
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||
sendEvent( name: "level", value: i )
|
||||
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
|
||||
}
|
||||
|
||||
if (description[-2..-1] == state.lvl) {
|
||||
// log.debug description[-2..-1]
|
||||
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||
sendEvent( name: "level", value: i )
|
||||
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def poll() {
|
||||
def refreshCmds = [
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
||||
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 8 0", "delay 500",
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}"
|
||||
]
|
||||
|
||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
@@ -188,63 +270,109 @@ def updated() {
|
||||
}
|
||||
|
||||
def on() {
|
||||
state.lvl = "00"
|
||||
state.trigger = "on/off"
|
||||
zigbee.on()
|
||||
|
||||
// log.debug "on()"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||
}
|
||||
|
||||
def off() {
|
||||
state.lvl = "00"
|
||||
state.trigger = "on/off"
|
||||
zigbee.off()
|
||||
|
||||
// log.debug "off()"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
def refreshCmds = [
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
||||
]
|
||||
|
||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 8 0", "delay 500",
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}"
|
||||
]
|
||||
poll()
|
||||
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
|
||||
def cmds = []
|
||||
value = value as Integer
|
||||
if (value == 0) {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 0 {0000 ${state.rate}}"
|
||||
}
|
||||
else if (device.latestValue("switch") == "off") {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
sendEvent(name: "level", value: value)
|
||||
value = (value * 255 / 100)
|
||||
def level = hex(value);
|
||||
|
||||
state.trigger = "setLevel"
|
||||
def cmd
|
||||
def delayForRefresh = 500
|
||||
state.lvl = "${level}"
|
||||
|
||||
if (dimRate && (state?.rate != null)) {
|
||||
def computedRate = convertRateValue(state.rate)
|
||||
cmd = zigbee.setLevel(value, computedRate)
|
||||
delayForRefresh += computedRate * 100 //converting tenth of second to milliseconds
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} ${state.rate}}"
|
||||
}
|
||||
else {
|
||||
cmd = zigbee.setLevel(value, 20)
|
||||
delayForRefresh += 2000
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 1500}"
|
||||
}
|
||||
cmd + ["delay $delayForRefresh"] + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
int convertRateValue(rate) {
|
||||
int convertedRate = 0
|
||||
switch (rate)
|
||||
{
|
||||
case "0000":
|
||||
convertedRate = 0
|
||||
break
|
||||
|
||||
case "1500":
|
||||
convertedRate = 20 //0015 hex in int is 2.1
|
||||
break
|
||||
|
||||
case "2500":
|
||||
convertedRate = 35 //0025 hex in int is 3.7
|
||||
break
|
||||
|
||||
case "3500":
|
||||
convertedRate = 50 //0035 hex in int is 5.1
|
||||
break
|
||||
}
|
||||
convertedRate
|
||||
log.debug cmds
|
||||
cmds
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
return zigbee.onOffConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def configCmds = [
|
||||
|
||||
//Switch Reporting
|
||||
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1000",
|
||||
|
||||
//Level Control Reporting
|
||||
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}", "delay 500",
|
||||
]
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
private hex(value, width=2) {
|
||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
||||
while (s.size() < width) {
|
||||
s = "0" + s
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
@@ -80,12 +80,19 @@ def parse(String description) {
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
|
||||
// log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.multiinstancev1.MultiInstanceCmdEncap cmd) {
|
||||
def encapsulated = cmd.encapsulatedCommand([0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
|
||||
def encapsulated = null
|
||||
if (cmd.respondsTo("encapsulatedCommand")) {
|
||||
encapsulated = cmd.encapsulatedCommand()
|
||||
} else {
|
||||
def hex1 = { n -> String.format("%02X", n) }
|
||||
def sorry = "command: ${hex1(cmd.commandClass)}${hex1(cmd.command)}, payload: " + cmd.parameter.collect{ hex1(it) }.join(" ")
|
||||
encapsulated = zwave.parse(sorry, [0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
|
||||
}
|
||||
return encapsulated ? zwaveEvent(encapsulated) : null
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
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"
|
||||
}
|
||||
}
|
||||
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
|
||||
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) {
|
||||
state "default", label:'SN: ${currentValue}'
|
||||
}
|
||||
@@ -31,7 +34,7 @@ metadata {
|
||||
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
|
||||
}
|
||||
|
||||
main (["rich-control"])
|
||||
main (["icon"])
|
||||
details(["rich-control", "networkAddress"])
|
||||
}
|
||||
}
|
||||
@@ -72,7 +75,6 @@ def parse(description) {
|
||||
}
|
||||
else if (contentType?.contains("xml")) {
|
||||
log.debug "HUE BRIDGE ALREADY PRESENT"
|
||||
parent.hubVerification(device.hub.id, msg.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
|
||||
/**
|
||||
* Hue Bulb
|
||||
*
|
||||
* Philips Hue Type "Extended Color Light"
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
|
||||
// for the UI
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
@@ -13,14 +11,13 @@ metadata {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Color Control"
|
||||
capability "Color Temperature"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
command "setAdjustedColor"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -28,7 +25,7 @@ metadata {
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"rich-control", 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") {
|
||||
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"
|
||||
@@ -36,45 +33,34 @@ metadata {
|
||||
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)"
|
||||
}
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setAdjustedColor"
|
||||
}
|
||||
}
|
||||
|
||||
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("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
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") {
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["rich-control"])
|
||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "levelSliderControl", "rgbSelector", "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}")
|
||||
}
|
||||
@@ -82,17 +68,17 @@ def parse(description) {
|
||||
}
|
||||
|
||||
// handle commands
|
||||
void on() {
|
||||
def on() {
|
||||
log.trace parent.on(this)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
void off() {
|
||||
def off() {
|
||||
log.trace parent.off(this)
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
|
||||
void nextLevel() {
|
||||
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
|
||||
@@ -103,105 +89,55 @@ void nextLevel() {
|
||||
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")
|
||||
}
|
||||
def setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent)
|
||||
}
|
||||
|
||||
void setSaturation(percent) {
|
||||
log.debug "Executing 'setSaturation'"
|
||||
if (verifyPercent(percent)) {
|
||||
parent.setSaturation(this, percent)
|
||||
sendEvent(name: "saturation", value: percent, displayed: false)
|
||||
}
|
||||
def setSaturation(percent) {
|
||||
log.debug "Executing 'setSaturation'"
|
||||
parent.setSaturation(this, percent)
|
||||
sendEvent(name: "saturation", value: percent)
|
||||
}
|
||||
|
||||
void setHue(percent) {
|
||||
log.debug "Executing 'setHue'"
|
||||
if (verifyPercent(percent)) {
|
||||
parent.setHue(this, percent)
|
||||
sendEvent(name: "hue", value: percent, displayed: false)
|
||||
}
|
||||
def setHue(percent) {
|
||||
log.debug "Executing 'setHue'"
|
||||
parent.setHue(this, percent)
|
||||
sendEvent(name: "hue", value: percent)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
def setColor(value) {
|
||||
log.debug "setColor: ${value}, $this"
|
||||
parent.setColor(this, value)
|
||||
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)}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
def value = [level:100, saturation:56, hue:23]
|
||||
def reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
|
||||
setAdjustedColor(value)
|
||||
parent.poll()
|
||||
parent.poll()
|
||||
}
|
||||
|
||||
void setAdjustedColor(value) {
|
||||
if (value) {
|
||||
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
|
||||
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 refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
}
|
||||
|
||||
def adjustOutgoingHue(percent) {
|
||||
@@ -220,14 +156,3 @@ def adjustOutgoingHue(percent) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/**
|
||||
* Hue Lux Bulb
|
||||
*
|
||||
* Philips Hue Type "Dimmable Light"
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
// for the UI
|
||||
@@ -14,14 +12,14 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
command "refresh"
|
||||
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
@@ -33,19 +31,29 @@ metadata {
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
}
|
||||
|
||||
main(["rich-control"])
|
||||
main(["switch"])
|
||||
details(["rich-control", "refresh"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
@@ -66,28 +74,23 @@ def parse(description) {
|
||||
}
|
||||
|
||||
// handle commands
|
||||
void on() {
|
||||
log.trace parent.on(this)
|
||||
def on() {
|
||||
parent.on(this)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
void off() {
|
||||
log.trace parent.off(this)
|
||||
def off() {
|
||||
parent.off(this)
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
|
||||
void setLevel(percent) {
|
||||
def 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"
|
||||
}
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent)
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
def refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ def setHue(percentage) {
|
||||
log.error("Bad setHue result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setSaturation(percentage) {
|
||||
@@ -98,7 +97,6 @@ def setSaturation(percentage) {
|
||||
log.error("Bad setSaturation result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setColor(Map color) {
|
||||
@@ -124,15 +122,13 @@ def setColor(Map color) {
|
||||
parent.logErrors(logObject:log) {
|
||||
def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"])
|
||||
if (resp.status < 300) {
|
||||
if (color.hex)
|
||||
sendEvent(name: "color", value: color.hex)
|
||||
sendEvent(name: "color", value: color.hex)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
events.each { sendEvent(it) }
|
||||
} else {
|
||||
log.error("Bad setColor result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setLevel(percentage) {
|
||||
@@ -154,7 +150,6 @@ def setLevel(percentage) {
|
||||
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setColorTemperature(kelvin) {
|
||||
@@ -170,7 +165,6 @@ def setColorTemperature(kelvin) {
|
||||
}
|
||||
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def on() {
|
||||
@@ -180,7 +174,6 @@ def on() {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def off() {
|
||||
@@ -190,7 +183,6 @@ def off() {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def poll() {
|
||||
|
||||
@@ -84,7 +84,6 @@ def setLevel(percentage) {
|
||||
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def setColorTemperature(kelvin) {
|
||||
@@ -100,7 +99,6 @@ def setColorTemperature(kelvin) {
|
||||
log.error("Bad setColorTemperature result: [${resp.status}] ${resp.data}")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def on() {
|
||||
@@ -110,7 +108,6 @@ def on() {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def off() {
|
||||
@@ -120,7 +117,6 @@ def off() {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
def poll() {
|
||||
|
||||
@@ -1,22 +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.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Set Device Image'''.ko=기기 이미지 설정
|
||||
# Events / Notifications
|
||||
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
|
||||
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
|
||||
'''present'''.ko=집안
|
||||
'''not present'''.ko=외출
|
||||
@@ -1,19 +1,16 @@
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
/**
|
||||
* 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:
|
||||
* 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.
|
||||
* 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: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Presence Sensor"
|
||||
@@ -44,7 +41,6 @@ def parse(String description) {
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
def results = [
|
||||
translatable: true,
|
||||
name: name,
|
||||
value: value,
|
||||
unit: null,
|
||||
@@ -76,8 +72,8 @@ private String parseValue(String description) {
|
||||
|
||||
private parseDescriptionText(String linkText, String value, String description) {
|
||||
switch(value) {
|
||||
case "present": return "{{ linkText }} has arrived"
|
||||
case "not present": return "{{ linkText }} has left"
|
||||
case "present": return "$linkText has arrived"
|
||||
case "not present": return "$linkText has left"
|
||||
default: return value
|
||||
}
|
||||
}
|
||||
@@ -88,4 +84,4 @@ private getState(String value) {
|
||||
case "not present": return "left"
|
||||
default: return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* Navien Thermostat
|
||||
*
|
||||
* Author: Navien Within
|
||||
* Date: 2015-11-02
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Navien Room Controller", namespace: "smartthings", author: "Navien") {
|
||||
capability "Thermostat"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
|
||||
command "generateEvent"
|
||||
command "powerONOFF"
|
||||
command "setRoomTemp"
|
||||
command "setOndolTemp"
|
||||
command "controlMode1"
|
||||
command "controlMode2"
|
||||
|
||||
attribute "setRoomSlider", "NUMBER"
|
||||
attribute "setOndolSlider", "NUMBER"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator { }
|
||||
|
||||
//tiles(scale: 2) {
|
||||
tiles {
|
||||
/*
|
||||
multiAttributeTile(name: "temperature", type: "thermostat", canChangeIcon: true, canChangeBackground: true) {
|
||||
tileAttribute("temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState "temperature", label: '${currentValue}°', icon:"st.Home.home1", backgroundColor:"#FFA81E"
|
||||
}
|
||||
tileAttribute ("statusText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "statusText", label: 'statusText'
|
||||
}
|
||||
tileAttribute("button", key: "VALUE_CONTROL"){
|
||||
attributeState "setUp"
|
||||
attributeState "setDown"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
valueTile("thermostatStatus", "device.thermostatStatus", width: 3, height: 2) {
|
||||
state "전원 OFF", label:'${currentValue}', icon:"st.Navien.bgs_power_off", backgroundColor:"#BDBDBD"
|
||||
state "외출 ON", label:'${currentValue}', icon:"st.Navien.bgs_out", backgroundColor:"#FF8C17"
|
||||
state "실내난방", label:'${currentValue}', icon:"st.Navien.bgs_indoor", backgroundColor:"#FF8C17"
|
||||
state "온돌난방", label:'${currentValue}', icon:"st.Navien.bgs_ondol", backgroundColor:"#FF8C17"
|
||||
state "반복예약난방", label:'${currentValue}', icon:"st.Navien.bgs_heating_again", backgroundColor:"#FF8C17"
|
||||
state "24시간예약난방", label:'${currentValue}', icon:"st.Navien.bgs_24heat", backgroundColor:"#FF8C17"
|
||||
state "간편예약난방", label:'${currentValue}', icon:"st.Navien.bgs_heat", backgroundColor:"#FF8C17"
|
||||
state "온수전용", label:'${currentValue}', icon:"st.Navien.bgs_water", backgroundColor:"#FF8C17"
|
||||
state "빠른온수", label:'${currentValue}', icon:"st.Navien.bgs_water_fast", backgroundColor:"#FF8C17"
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", width: 1, height: 1, inactiveLabel: false) {
|
||||
state "OFF", label:'', unit:"C", icon:"st.Navien.bg_recent_off"
|
||||
state "default", label:'${currentValue}°', unit:"C", icon:"st.Navien.bg_recent"
|
||||
}
|
||||
|
||||
valueTile("hotWater", "device.hotWater", width: 1, height: 1) {
|
||||
state "OFF", label:'', unit:"C", icon:"st.Navien.bg_water_off"
|
||||
state "default", label:'${currentValue}°', unit:"C", icon:"st.Navien.bg_water"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", , width: 1, height: 1, inactiveLabel: false) {
|
||||
state "default", action:"refresh.refresh", icon:"st.Navien.but_refresh"
|
||||
}
|
||||
|
||||
valueTile("setRoomTemp", "device.setRoomTemp", width: 1, height:1) {
|
||||
state "OFF", label:'', icon:"st.Navien.bg_indoor_off"
|
||||
state "default", label:'${currentValue}°', icon:"st.Navien.bg_indoor"
|
||||
}
|
||||
|
||||
controlTile("setRoomSlider", "device.setRoomSlider", "slider", height: 1, width: 2, inactiveLabel: false, range:"(10..40)") {
|
||||
state "default", action:"setRoomTemp", backgroundColor:"#F08C00"
|
||||
}
|
||||
|
||||
valueTile("setOndolTemp", "device.setOndolTemp", width: 1, height:1) {
|
||||
state "OFF", label:'', icon:"st.Navien.bg_ondol"
|
||||
state "default", label:'${currentValue}°', icon:"st.Navien.bg_ondol_off"
|
||||
}
|
||||
|
||||
controlTile("setOndolSlider", "device.setOndolSlider", "slider", height: 1, width: 2, inactiveLabel: false, range:"(40..83)") {
|
||||
state "default", action:"setOndolTemp", backgroundColor:"#F08C00"
|
||||
}
|
||||
|
||||
standardTile("power", "device.power", width: 1, height: 1, inactiveLabel: false) {
|
||||
state "ON", label:'', action:"powerONOFF", icon:"st.Navien.but_power"
|
||||
state "default", label:'', action:"powerONOFF", icon:"st.Navien.but_power_off"
|
||||
}
|
||||
|
||||
standardTile("controlMode1", "device.controlMode1", width: 1, height: 1, inactiveLabel: false) {
|
||||
state "실내난방 ON", label:'', action:"controlMode1", icon:"st.Navien.but_indoor"
|
||||
state "온돌난방 ON", label:'', action:"controlMode1", icon:"st.Navien.but_ondol"
|
||||
state "default", label:'', icon:"st.Navien.but_indoor_off"
|
||||
}
|
||||
|
||||
standardTile("controlMode2", "device.controlMode2", width: 1, height: 1, inactiveLabel: false) {
|
||||
state "외출해제", label:'', action:"controlMode2", icon:"st.Navien.but_out_off"
|
||||
state "외출설정", label:'', action:"controlMode2", icon:"st.Navien.but_out"
|
||||
state "default", label:'', icon:"st.Navien.but_out_dis"
|
||||
}
|
||||
|
||||
valueTile("herotile", "device.herotile", width: 3, height:1) {
|
||||
state "default", label:'', icon:"st.Navien.bg_herotile"
|
||||
}
|
||||
|
||||
main "thermostatStatus"
|
||||
details(["thermostatStatus", "temperature", "hotWater", "refresh", "setRoomTemp", "setRoomSlider", "setOndolTemp", "setOndolSlider", "power", "controlMode1", "controlMode2", "herotile"])
|
||||
}
|
||||
}
|
||||
|
||||
def powerONOFF()
|
||||
{
|
||||
log.debug "powerONOFF called"
|
||||
def powerStatus = device.currentValue("power")
|
||||
def results
|
||||
if(powerStatus == "ON")
|
||||
{
|
||||
results = parent.childRequest(this, "1", "33", "0")
|
||||
}
|
||||
else if(powerStatus == "전원 OFF")
|
||||
{
|
||||
results = parent.childRequest(this, "1", "34", "0")
|
||||
}
|
||||
generateEvent(results)
|
||||
log.debug "powerONOFF ended"
|
||||
}
|
||||
|
||||
def setRoomTemp(degrees)
|
||||
{
|
||||
def degreesInteger = degrees as Integer
|
||||
sendEvent("name":"setRoomSlider", "value":degreesInteger)
|
||||
|
||||
def status = device.currentValue("thermostatStatus")
|
||||
if(status == "실내난방")
|
||||
{
|
||||
def results = parent.childRequest(this, "1", "44", degreesInteger*2)
|
||||
generateEvent(results)
|
||||
}
|
||||
}
|
||||
|
||||
def setOndolTemp(degrees)
|
||||
{
|
||||
def degreesInteger = degrees as Integer
|
||||
sendEvent("name":"setOndolSlider", "value":degreesInteger)
|
||||
|
||||
def status = device.currentValue("thermostatStatus")
|
||||
if(status == "온돌난방")
|
||||
{
|
||||
def results = parent.childRequest(this, "1", "36", degreesInteger*2)
|
||||
generateEvent(results)
|
||||
}
|
||||
}
|
||||
|
||||
def controlMode1()
|
||||
{
|
||||
log.debug "controlMode1"
|
||||
def control = device.currentValue("controlMode1")
|
||||
controlMode(control)
|
||||
}
|
||||
|
||||
def controlMode2()
|
||||
{
|
||||
log.debug "controlMode2"
|
||||
def control = device.currentValue("controlMode2")
|
||||
controlMode(control)
|
||||
}
|
||||
|
||||
def controlMode(control)
|
||||
{
|
||||
if(control == "실내난방 ON")
|
||||
{
|
||||
log.debug "실내난방 ON : 제어"
|
||||
def value = device.currentValue("setRoomSlider")*2
|
||||
def results = parent.childRequest(this, "1", "43", value)
|
||||
generateEvent(results)
|
||||
|
||||
}
|
||||
else if(control == "온돌난방 ON")
|
||||
{
|
||||
log.debug "온돌난방 ON : 제어"
|
||||
def value = device.currentValue("setOndolSlider")*2
|
||||
def results = parent.childRequest(this, "1", "35", value)
|
||||
generateEvent(results)
|
||||
|
||||
}
|
||||
else if(control == "외출해제")
|
||||
{
|
||||
log.debug "외출해제 : 제어"
|
||||
def results = parent.childRequest(this, "1", "46", "0")
|
||||
generateEvent(results)
|
||||
|
||||
}
|
||||
else if(control == "외출설정")
|
||||
{
|
||||
log.debug "외출설정 : 제어"
|
||||
def results = parent.childRequest(this, "1", "46", "1")
|
||||
generateEvent(results)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh()
|
||||
{
|
||||
log.debug "refresh called"
|
||||
poll()
|
||||
log.debug "refresh ended"
|
||||
}
|
||||
|
||||
void poll() {
|
||||
log.debug "Executing 'poll' using parent SmartApp"
|
||||
|
||||
def results = parent.pollChild(this)
|
||||
generateEvent(results)
|
||||
}
|
||||
|
||||
void generateEvent(Map results)
|
||||
{
|
||||
log.debug "parsing data $results"
|
||||
|
||||
if(results)
|
||||
{
|
||||
results.each { name, value ->
|
||||
def linkText = getLinkText(device)
|
||||
def isChange = true
|
||||
def isDisplayed = true
|
||||
|
||||
if (name=="temperature" || name=="hotWater")
|
||||
{
|
||||
//isChange = isTemperatureStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
|
||||
sendEvent(
|
||||
name: name,
|
||||
value: value,
|
||||
unit: "C",
|
||||
linkText: linkText,
|
||||
descriptionText: getThermostatDescriptionText(name, value, linkText),
|
||||
handlerName: name,
|
||||
isStateChange: isChange,
|
||||
displayed: isDisplayed)
|
||||
}
|
||||
else if (name=="thermostatStatus")
|
||||
{
|
||||
isChange = isStateChange(device, name, value.toString())
|
||||
isDisplayed = isChange
|
||||
|
||||
sendEvent(
|
||||
name: name,
|
||||
value: value.toString(),
|
||||
linkText: linkText,
|
||||
descriptionText: getThermostatDescriptionText(name, value, linkText),
|
||||
handlerName: name,
|
||||
isStateChange: isChange,
|
||||
displayed: isDisplayed)
|
||||
}
|
||||
}
|
||||
generateStatusEvent(results)
|
||||
}
|
||||
}
|
||||
|
||||
private getThermostatDescriptionText(name, value, linkText)
|
||||
{
|
||||
if(name == "temperature")
|
||||
{
|
||||
return "$linkText was $value°C"
|
||||
}
|
||||
else if(name == "roomTemp")
|
||||
{
|
||||
return "latest roomTemp setpoint was $value°C"
|
||||
}
|
||||
else if(name == "ondolTemp")
|
||||
{
|
||||
return "latest ondolTemp setpoint was $value°C"
|
||||
}
|
||||
else if (name == "thermostatMode")
|
||||
{
|
||||
return "thermostat mode is ${value}"
|
||||
}
|
||||
else
|
||||
{
|
||||
return "${name} = ${value}"
|
||||
}
|
||||
}
|
||||
|
||||
def generateStatusEvent(Map results) {
|
||||
log.debug "generateStatusEvent"
|
||||
|
||||
def status = results.thermostatStatus
|
||||
def setRoomTemp = results.roomTemp
|
||||
def setOndolTemp = results.ondolTemp
|
||||
|
||||
log.debug "status ===> ${status}"
|
||||
|
||||
if (status == "전원 OFF") {
|
||||
sendEvent("name":"temperature", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"hotWater", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"ON", "description":"전원 ON", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
|
||||
|
||||
}else if(status == "온수전용"){
|
||||
sendEvent("name":"setRoomTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"실내 ON", "description":"ON", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"온돌 ON", "description":"ON", displayed: true, isStateChange: true)
|
||||
|
||||
}else if(status == "외출 ON"){
|
||||
sendEvent("name":"setRoomTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":"0", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"외출해제", "description":"외출해제", displayed: true, isStateChange: true)
|
||||
|
||||
}else if(status == "실내난방"){
|
||||
sendEvent("name":"setRoomTemp", "value":"${setRoomTemp}", "description":"설정온도", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":setRoomTemp, displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"${setOndolTemp}", "description":"설정온도", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":setOndolTemp, displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"온돌난방 ON", "description":"온돌난방 ON", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"외출설정", "description":"외출설정", displayed: true, isStateChange: true)
|
||||
|
||||
}
|
||||
else if(status == "온돌난방"){
|
||||
sendEvent("name":"setRoomTemp", "value":"${setRoomTemp}", "description":"설정온도", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setRoomSlider", "value":setRoomTemp, displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolTemp", "value":"${setOndolTemp}", "description":"설정온도", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"setOndolSlider", "value":setOndolTemp, displayed: true, isStateChange: true)
|
||||
sendEvent("name":"power", "value":"전원 OFF", "description":"OFF", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode1", "value":"실내난방 ON", "description":"실내난방 ON", displayed: true, isStateChange: true)
|
||||
sendEvent("name":"controlMode2", "value":"외출설정", "description":"외출설정", displayed: true, isStateChange: true)
|
||||
|
||||
}
|
||||
|
||||
log.debug "Generate Status Event = ${status}"
|
||||
}
|
||||
@@ -24,7 +24,6 @@ metadata {
|
||||
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,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"
|
||||
|
||||
@@ -19,6 +19,11 @@ metadata {
|
||||
capability "Sensor"
|
||||
|
||||
attribute "colorName", "string"
|
||||
|
||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||
attribute "heartbeat", "string"
|
||||
|
||||
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -70,6 +75,9 @@ metadata {
|
||||
def parse(String 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?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
||||
{
|
||||
@@ -124,6 +132,7 @@ def off() {
|
||||
}
|
||||
|
||||
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} 8 0", "delay 500",
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Samsung TV
|
||||
*
|
||||
* Author: SmartThings (juano23@gmail.com)
|
||||
* Date: 2015-01-08
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Samsung Smart TV", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "switch"
|
||||
|
||||
command "mute"
|
||||
command "source"
|
||||
command "menu"
|
||||
command "tools"
|
||||
command "HDMI"
|
||||
command "Sleep"
|
||||
command "Up"
|
||||
command "Down"
|
||||
command "Left"
|
||||
command "Right"
|
||||
command "chup"
|
||||
command "chdown"
|
||||
command "prech"
|
||||
command "volup"
|
||||
command "voldown"
|
||||
command "Enter"
|
||||
command "Return"
|
||||
command "Exit"
|
||||
command "Info"
|
||||
command "Size"
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
||||
state "default", label:'TV', action:"switch.off", icon:"st.Electronics.electronics15", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("power", "device.switch", width: 1, height: 1, canChangeIcon: false) {
|
||||
state "default", label:'', action:"switch.off", decoration: "flat", icon:"st.thermostat.heating-cooling-off", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("mute", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Mute', action:"mute", icon:"st.custom.sonos.muted", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("source", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Source', action:"source", icon:"st.Electronics.electronics15"
|
||||
}
|
||||
standardTile("tools", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Tools', action:"tools", icon:"st.secondary.tools"
|
||||
}
|
||||
standardTile("menu", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Menu', action:"menu", icon:"st.vents.vent"
|
||||
}
|
||||
standardTile("HDMI", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Source', action:"HDMI", icon:"st.Electronics.electronics15"
|
||||
}
|
||||
standardTile("Sleep", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Sleep', action:"Sleep", icon:"st.Bedroom.bedroom10"
|
||||
}
|
||||
standardTile("Up", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Up', action:"Up", icon:"st.thermostat.thermostat-up"
|
||||
}
|
||||
standardTile("Down", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Down', action:"Down", icon:"st.thermostat.thermostat-down"
|
||||
}
|
||||
standardTile("Left", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Left', action:"Left", icon:"st.thermostat.thermostat-left"
|
||||
}
|
||||
standardTile("Right", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Right', action:"Right", icon:"st.thermostat.thermostat-right"
|
||||
}
|
||||
standardTile("chup", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'CH Up', action:"chup", icon:"st.thermostat.thermostat-up"
|
||||
}
|
||||
standardTile("chdown", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'CH Down', action:"chdown", icon:"st.thermostat.thermostat-down"
|
||||
}
|
||||
standardTile("prech", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Pre CH', action:"prech", icon:"st.secondary.refresh-icon"
|
||||
}
|
||||
standardTile("volup", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Vol Up', action:"volup", icon:"st.thermostat.thermostat-up"
|
||||
}
|
||||
standardTile("voldown", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Vol Down', action:"voldown", icon:"st.thermostat.thermostat-down"
|
||||
}
|
||||
standardTile("Enter", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Enter', action:"Enter", icon:"st.illuminance.illuminance.dark"
|
||||
}
|
||||
standardTile("Return", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Return', action:"Return", icon:"st.secondary.refresh-icon"
|
||||
}
|
||||
standardTile("Exit", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Exit', action:"Exit", icon:"st.locks.lock.unlocked"
|
||||
}
|
||||
standardTile("Info", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Info', action:"Info", icon:"st.motion.acceleration.active"
|
||||
}
|
||||
standardTile("Size", "device.switch", decoration: "flat", canChangeIcon: false) {
|
||||
state "default", label:'Picture Size', action:"Size", icon:"st.contact.contact.open"
|
||||
}
|
||||
main "switch"
|
||||
details (["power","HDMI","Sleep","chup","prech","volup","chdown","mute","voldown", "menu", "Up", "tools", "Left", "Enter", "Right", "Return", "Down", "Exit", "Info","Size"])
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
return null
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "Turning TV OFF"
|
||||
parent.tvAction("POWEROFF",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Power Off", displayed: true)
|
||||
}
|
||||
|
||||
def mute() {
|
||||
log.trace "MUTE pressed"
|
||||
parent.tvAction("MUTE",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Mute", displayed: true)
|
||||
}
|
||||
|
||||
def source() {
|
||||
log.debug "SOURCE pressed"
|
||||
parent.tvAction("SOURCE",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Source", displayed: true)
|
||||
}
|
||||
|
||||
def menu() {
|
||||
log.debug "MENU pressed"
|
||||
parent.tvAction("MENU",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def tools() {
|
||||
log.debug "TOOLS pressed"
|
||||
parent.tvAction("TOOLS",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Tools", displayed: true)
|
||||
}
|
||||
|
||||
def HDMI() {
|
||||
log.debug "HDMI pressed"
|
||||
parent.tvAction("HDMI",device.deviceNetworkId)
|
||||
sendEvent(name:"Command sent", value: "Source", displayed: true)
|
||||
}
|
||||
|
||||
def Sleep() {
|
||||
log.debug "SLEEP pressed"
|
||||
parent.tvAction("SLEEP",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Sleep", displayed: true)
|
||||
}
|
||||
|
||||
def Up() {
|
||||
log.debug "UP pressed"
|
||||
parent.tvAction("UP",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Down() {
|
||||
log.debug "DOWN pressed"
|
||||
parent.tvAction("DOWN",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Left() {
|
||||
log.debug "LEFT pressed"
|
||||
parent.tvAction("LEFT",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Right() {
|
||||
log.debug "RIGHT pressed"
|
||||
parent.tvAction("RIGHT",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def chup() {
|
||||
log.debug "CHUP pressed"
|
||||
parent.tvAction("CHUP",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Channel Up", displayed: true)
|
||||
}
|
||||
|
||||
def chdown() {
|
||||
log.debug "CHDOWN pressed"
|
||||
parent.tvAction("CHDOWN",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Channel Down", displayed: true)
|
||||
}
|
||||
|
||||
def prech() {
|
||||
log.debug "PRECH pressed"
|
||||
parent.tvAction("PRECH",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Prev Channel", displayed: true)
|
||||
}
|
||||
|
||||
def Exit() {
|
||||
log.debug "EXIT pressed"
|
||||
parent.tvAction("EXIT",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def volup() {
|
||||
log.debug "VOLUP pressed"
|
||||
parent.tvAction("VOLUP",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Volume Up", displayed: true)
|
||||
}
|
||||
|
||||
def voldown() {
|
||||
log.debug "VOLDOWN pressed"
|
||||
parent.tvAction("VOLDOWN",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Volume Down", displayed: true)
|
||||
}
|
||||
|
||||
def Enter() {
|
||||
log.debug "ENTER pressed"
|
||||
parent.tvAction("ENTER",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Return() {
|
||||
log.debug "RETURN pressed"
|
||||
parent.tvAction("RETURN",device.deviceNetworkId)
|
||||
}
|
||||
|
||||
def Info() {
|
||||
log.debug "INFO pressed"
|
||||
parent.tvAction("INFO",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Info", displayed: true)
|
||||
}
|
||||
|
||||
def Size() {
|
||||
log.debug "PICTURE_SIZE pressed"
|
||||
parent.tvAction("PICTURE_SIZE",device.deviceNetworkId)
|
||||
sendEvent(name:"Command", value: "Picture Size", displayed: true)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0006, 0004, 0003, 0000, 0005", outClusters: "0019", manufacturer: "Compacta International, Ltd", model: "ZBMPlug15", deviceJoinName: "SmartPower Outlet V1"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0006", outClusters: "0019"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -1,26 +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.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Outlet'''.ko= 스마트 플러그
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} is On'''.ko={{ device.displayName }} 켜짐
|
||||
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }} 꺼짐
|
||||
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전력은 {{ value }}와트입니다.
|
||||
'''On'''.ko= 켜짐
|
||||
'''Off'''.ko=꺼짐
|
||||
'''Turning On'''.ko=켜는 중
|
||||
'''Turning Off'''.ko=끄는 중
|
||||
#==============================================================================
|
||||
@@ -1,29 +1,32 @@
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
/**
|
||||
* 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:
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* SmartPower Outlet (CentraLite)
|
||||
*
|
||||
* Author: SmartThings
|
||||
* Date: 2015-08-23
|
||||
*/
|
||||
|
||||
metadata {
|
||||
// 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 "Switch"
|
||||
capability "Power Meter"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
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-Sgb", deviceJoinName: "Outlet"
|
||||
@@ -55,10 +58,10 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label: 'On', 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 "turningOn", label: 'Turning On', 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 "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 ("power", key: "SECONDARY_CONTROL") {
|
||||
attributeState "power", label:'${currentValue} W'
|
||||
@@ -78,6 +81,9 @@ metadata {
|
||||
def parse(String 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)
|
||||
|
||||
//TODO: Remove this after getKnownDescription can parse it automatically
|
||||
@@ -85,22 +91,22 @@ def parse(String description) {
|
||||
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
|
||||
|
||||
if (finalResult) {
|
||||
log.info "final result = $finalResult"
|
||||
log.info finalResult
|
||||
if (finalResult.type == "update") {
|
||||
log.info "$device updates: ${finalResult.value}"
|
||||
}
|
||||
else if (finalResult.type == "power") {
|
||||
def powerValue = (finalResult.value as Integer)/10
|
||||
sendEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true )
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
/*
|
||||
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
||||
|
||||
power level is an integer. The exact power level with correct units needs to be handled in the device type
|
||||
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
|
||||
*/
|
||||
}
|
||||
else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -118,11 +124,11 @@ def on() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 1200, displayed: false)
|
||||
zigbee.onOffConfig() + powerConfig() + refresh()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +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.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Dry'''.ko=건조
|
||||
'''Wet'''.ko=누수
|
||||
'''dry'''.ko=건조
|
||||
'''wet'''.ko=누수
|
||||
'''battery'''.ko=배터리
|
||||
'''Temperature Offset'''.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=온도
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Water Leak Sensor'''.ko=누수감지 센서
|
||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} is dry'''.ko={{ device.displayName }}가 건조
|
||||
'''{{ device.displayName }} is wet'''.ko={{ device.displayName }}누수
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
|
||||
'''{{ 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 was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
|
||||
#==============================================================================
|
||||
@@ -1,27 +1,25 @@
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
/**
|
||||
* SmartSense Moisture Sensor
|
||||
*
|
||||
* 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:
|
||||
* Copyright 2014 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.
|
||||
* 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: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Water Sensor"
|
||||
capability "Health Check"
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
@@ -45,7 +43,7 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input title: "Temperature Offset", 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 title: "Temperature Offset", 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 "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
@@ -118,16 +116,16 @@ private Map parseCatchAllMessage(String description) {
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
case 0x0402:
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
case 0x0402:
|
||||
// temp is last 2 data values. reverse to swap endian
|
||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||
def value = getTemperature(temp)
|
||||
resultMap = getTemperatureResult(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
@@ -222,8 +220,7 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--',
|
||||
translatable: true
|
||||
value: '--'
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
@@ -231,7 +228,7 @@ private Map getBatteryResult(rawValue) {
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
@@ -248,7 +245,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -256,7 +253,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,37 +263,27 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText
|
||||
if ( temperatureScale == 'C' )
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
private Map getMoistureResult(value) {
|
||||
log.debug "water"
|
||||
def descriptionText
|
||||
if ( value == "wet" )
|
||||
descriptionText = '{{ device.displayName }} is wet'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} is dry'
|
||||
log.debug 'water'
|
||||
String descriptionText = "${device.displayName} is ${value}"
|
||||
return [
|
||||
name: 'water',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
@@ -311,8 +298,6 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
def configCmds = [
|
||||
|
||||
@@ -16,7 +16,6 @@ metadata {
|
||||
capability "Water Sensor"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x9D,0x85,0x80,0x72,0x31,0x84,0x86"
|
||||
fingerprint deviceId: "0x2101", inClusters: "0x71,0x70,0x85,0x80,0x72,0x31,0x84,0x86"
|
||||
@@ -40,29 +39,17 @@ metadata {
|
||||
attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
|
||||
}
|
||||
}
|
||||
standardTile("temperatureState", "device.temperature", width: 2, height: 2) {
|
||||
standardTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state "normal", icon:"st.alarm.temperature.normal", backgroundColor:"#ffffff"
|
||||
state "freezing", icon:"st.alarm.temperature.freeze", backgroundColor:"#53a7c0"
|
||||
state "overheated", icon:"st.alarm.temperature.overheat", backgroundColor:"#F80000"
|
||||
}
|
||||
valueTile("temperature", "device.temperature", 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", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
main (["water", "temperatureState"])
|
||||
details(["water", "temperatureState", "temperature", "battery"])
|
||||
|
||||
main (["water", "temperature"])
|
||||
details(["water", "temperature", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +115,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
|
||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
||||
}
|
||||
if(cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_HEAT) {
|
||||
map.name = "temperatureState"
|
||||
map.name = "temperature"
|
||||
if(cmd.zwaveAlarmEvent == 1) { map.value = "overheated"}
|
||||
if(cmd.zwaveAlarmEvent == 2) { map.value = "overheated"}
|
||||
if(cmd.zwaveAlarmEvent == 3) { map.value = "changing temperature rapidly"}
|
||||
@@ -142,30 +129,17 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
|
||||
map
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd)
|
||||
{
|
||||
def map = [:]
|
||||
if(cmd.sensorType == 1) {
|
||||
map.name = "temperature"
|
||||
if(cmd.scale == 0) {
|
||||
map.value = getTemperature(cmd.scaledSensorValue)
|
||||
} else {
|
||||
map.value = cmd.scaledSensorValue
|
||||
}
|
||||
map.unit = location.temperatureScale
|
||||
}
|
||||
map.name = "water"
|
||||
map.value = cmd.value ? "wet" : "dry"
|
||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
||||
map
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
if(location.temperatureScale == "C"){
|
||||
return value
|
||||
} else {
|
||||
return Math.round(celsiusToFahrenheit(value))
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd)
|
||||
{
|
||||
log.debug "COMMAND CLASS: $cmd"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +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.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''battery'''.ko=배터리
|
||||
'''Temperature Offset'''.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=온도
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Motion Sensor'''.ko=모션 센서
|
||||
'''motion'''.ko= 동작 감지
|
||||
'''no motion'''.ko=동작 없음
|
||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} detected motion'''.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 }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
|
||||
#==============================================================================
|
||||
@@ -1,27 +1,26 @@
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
/**
|
||||
* SmartSense Motion/Temp Sensor
|
||||
*
|
||||
* 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:
|
||||
* Copyright 2014 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.
|
||||
* 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: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Motion Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Temperature Measurement"
|
||||
capability "Refresh"
|
||||
capability "Health Check"
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
@@ -30,7 +29,6 @@ metadata {
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326-L", deviceJoinName: "Iris Motion Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
|
||||
}
|
||||
|
||||
@@ -48,7 +46,7 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input title: "Temperature Offset", 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 title: "Temperature Offset", 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 "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
@@ -237,8 +235,7 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--',
|
||||
translatable: true
|
||||
value: '--'
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
@@ -246,7 +243,7 @@ private Map getBatteryResult(rawValue) {
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
@@ -263,8 +260,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
def value = pct
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -272,7 +268,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,33 +278,28 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText
|
||||
if ( temperatureScale == 'C' )
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
private Map getMotionResult(value) {
|
||||
log.debug 'motion'
|
||||
String descriptionText = value == 'active' ? "{{ device.displayName }} detected motion" : "{{ device.displayName }} motion has stopped"
|
||||
String linkText = getLinkText(device)
|
||||
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
|
||||
return [
|
||||
name: 'motion',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
@@ -323,8 +314,6 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Motion Sensor"
|
||||
@@ -27,6 +25,10 @@ metadata {
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305-S"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -231,7 +233,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
if (rawValue == 0) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
|
||||
@@ -1,41 +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.
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Yes'''.ko=예
|
||||
'''No'''.ko=아니요
|
||||
'''battery'''.ko=배터리
|
||||
'''Temperature Offset'''.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=온도
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
|
||||
'''Tap to set'''.ko=눌러서 설정
|
||||
'''Give your device a name'''.ko=기기 이름 설정
|
||||
'''Multipurpose Sensor'''.ko=문 및 창 센서
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}에서 열림이 감지되었습니다.
|
||||
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}에서 닫힘이 감지되었습니다.
|
||||
'''{{ device.displayName }} was active'''.ko={{ device.displayName }} 활성화
|
||||
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }} 비활성화
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}에서 {{ value }}°C 감지
|
||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}의 남은 배터리 {{ value }}%
|
||||
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
|
||||
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
|
||||
'''Inactive'''.ko=비활성 상태
|
||||
'''Active'''.ko=활성 상태
|
||||
'''Open'''.ko= 열림이 감지될 때
|
||||
'''Closed'''.ko=닫힘
|
||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||
@@ -1,21 +1,21 @@
|
||||
/*
|
||||
* Copyright 2016 SmartThings
|
||||
/**
|
||||
* SmartSense Multi
|
||||
*
|
||||
* 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:
|
||||
* 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.
|
||||
* 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: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Three Axis"
|
||||
capability "Battery"
|
||||
@@ -25,7 +25,6 @@ metadata {
|
||||
capability "Acceleration Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Health Check"
|
||||
|
||||
command "enrollResponse"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||
@@ -63,30 +62,30 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input title: "Temperature Offset", 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 title: "Temperature Offset", 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 "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
section {
|
||||
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", description: "Tap to set", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
|
||||
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
|
||||
}
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
||||
attributeState "open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||
attributeState "closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||
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-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
||||
}
|
||||
}
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state("open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
}
|
||||
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
||||
state("active", label:'Active', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
||||
state("inactive", label:'Inactive', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
||||
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
||||
state("inactive", label:'${name}', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state("temperature", label:'${currentValue}°',
|
||||
@@ -101,6 +100,9 @@ metadata {
|
||||
]
|
||||
)
|
||||
}
|
||||
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
|
||||
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
@@ -110,7 +112,7 @@ metadata {
|
||||
|
||||
|
||||
main(["status", "acceleration", "temperature"])
|
||||
details(["status", "acceleration", "temperature", "battery", "refresh"])
|
||||
details(["status", "acceleration", "temperature", "3axis", "battery", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,19 +276,19 @@ def updated() {
|
||||
if (garageSensor == "Yes") {
|
||||
def descriptionText = "Updating device to garage sensor"
|
||||
if (device.latestValue("status") == "open") {
|
||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
|
||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText)
|
||||
}
|
||||
else if (device.latestValue("status") == "closed") {
|
||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
|
||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText)
|
||||
}
|
||||
}
|
||||
else {
|
||||
def descriptionText = "Updating device to open/close sensor"
|
||||
if (device.latestValue("status") == "garage-open") {
|
||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
|
||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText)
|
||||
}
|
||||
else if (device.latestValue("status") == "garage-closed") {
|
||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
|
||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,11 +304,11 @@ def getTemperature(value) {
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--',
|
||||
translatable: true
|
||||
value: '--'
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
@@ -314,7 +316,7 @@ private Map getBatteryResult(rawValue) {
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
@@ -331,7 +333,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -339,7 +341,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,50 +351,40 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature"
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C':
|
||||
'{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
private Map getContactResult(value) {
|
||||
log.debug "Contact: ${device.displayName} value = ${value}"
|
||||
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
|
||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
|
||||
log.debug "Contact"
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
|
||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
|
||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
|
||||
}
|
||||
|
||||
private getAccelerationResult(numValue) {
|
||||
log.debug "Acceleration"
|
||||
def name = "acceleration"
|
||||
def value
|
||||
def descriptionText
|
||||
|
||||
if ( numValue.endsWith("1") ) {
|
||||
value = "active"
|
||||
descriptionText = '{{ device.displayName }} was active'
|
||||
} else {
|
||||
value = "inactive"
|
||||
descriptionText = '{{ device.displayName }} was inactive'
|
||||
}
|
||||
|
||||
def value = numValue.endsWith("1") ? "active" : "inactive"
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "$linkText was $value"
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
return [
|
||||
[
|
||||
name: name,
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
isStateChange: isStateChange,
|
||||
translatable: true
|
||||
isStateChange: isStateChange
|
||||
]
|
||||
}
|
||||
|
||||
@@ -441,8 +433,6 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
@@ -451,28 +441,28 @@ def configure() {
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||
"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",
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}",
|
||||
"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",
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}",
|
||||
"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",
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}",
|
||||
"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",
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
|
||||
@@ -491,12 +481,17 @@ def enrollResponse() {
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
//Enroll Response
|
||||
"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||
"raw 0x500 {01 23 00 00 00}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||
]
|
||||
}
|
||||
|
||||
private Map parseAxis(String description) {
|
||||
def hexToSignedInt = { hexVal ->
|
||||
def unsignedVal = hexToInt(hexVal)
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
def z = hexToSignedInt(description[0..3])
|
||||
def y = hexToSignedInt(description[10..13])
|
||||
def x = hexToSignedInt(description[20..23])
|
||||
@@ -523,11 +518,6 @@ private Map parseAxis(String description) {
|
||||
getXyzResult(xyzResults, description)
|
||||
}
|
||||
|
||||
private hexToSignedInt(hexVal) {
|
||||
def unsignedVal = hexToInt(hexVal)
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
def garageEvent(zValue) {
|
||||
def absValue = zValue.abs()
|
||||
def contactValue = null
|
||||
@@ -541,9 +531,10 @@ def garageEvent(zValue) {
|
||||
garageValue = 'garage-open'
|
||||
}
|
||||
if (contactValue != null){
|
||||
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true)
|
||||
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true)
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "${linkText} was ${contactValue == 'open' ? 'opened' : 'closed'}"
|
||||
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false)
|
||||
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,6 @@ metadata {
|
||||
capability "Battery"
|
||||
|
||||
fingerprint profileId: "FC01", deviceId: "0139"
|
||||
|
||||
attribute "status", "string"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -74,12 +72,15 @@ metadata {
|
||||
]
|
||||
)
|
||||
}
|
||||
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
|
||||
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main(["contact", "acceleration", "temperature"])
|
||||
details(["contact", "acceleration", "temperature", "battery"])
|
||||
details(["contact", "acceleration", "temperature", "3axis", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@ def parse(String description) {
|
||||
|
||||
}
|
||||
|
||||
private List parseSingleMessage(description) {
|
||||
private Map parseSingleMessage(description) {
|
||||
|
||||
def name = parseName(description)
|
||||
def value = parseValue(description)
|
||||
@@ -110,9 +111,8 @@ private List parseSingleMessage(description) {
|
||||
def handlerName = value == 'open' ? 'opened' : value
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
def results = []
|
||||
results << createEvent(
|
||||
name: "contact",
|
||||
def results = [
|
||||
name: name,
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
@@ -120,18 +120,8 @@ private List parseSingleMessage(description) {
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
)
|
||||
|
||||
results << createEvent(
|
||||
name: "status",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
)
|
||||
]
|
||||
log.debug "Parse results for $device: $results"
|
||||
|
||||
results
|
||||
}
|
||||
@@ -203,7 +193,7 @@ private List parseContactMessage(String description) {
|
||||
parts.each { part ->
|
||||
part = part.trim()
|
||||
if (part.startsWith('contactState:')) {
|
||||
results.addAll(getContactResult(part, description))
|
||||
results << getContactResult(part, description)
|
||||
}
|
||||
else if (part.startsWith('accelerationState:')) {
|
||||
results << getAccelerationResult(part, description)
|
||||
@@ -282,7 +272,7 @@ private List parseRssiLqiMessage(String description) {
|
||||
results
|
||||
}
|
||||
|
||||
private List getContactResult(part, description) {
|
||||
private getContactResult(part, description) {
|
||||
def name = "contact"
|
||||
def value = part.endsWith("1") ? "open" : "closed"
|
||||
def handlerName = value == 'open' ? 'opened' : value
|
||||
@@ -290,33 +280,19 @@ private List getContactResult(part, description) {
|
||||
def descriptionText = "$linkText was $handlerName"
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
def results = []
|
||||
results << createEvent(
|
||||
name: "contact",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed:false
|
||||
)
|
||||
|
||||
results << createEvent(
|
||||
name: "status",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
)
|
||||
|
||||
results
|
||||
[
|
||||
name: name,
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
]
|
||||
}
|
||||
|
||||
private Map getAccelerationResult(part, description) {
|
||||
private getAccelerationResult(part, description) {
|
||||
def name = "acceleration"
|
||||
def value = part.endsWith("1") ? "active" : "inactive"
|
||||
def linkText = getLinkText(device)
|
||||
@@ -335,7 +311,7 @@ private Map getAccelerationResult(part, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private Map getTempResult(part, description) {
|
||||
private getTempResult(part, description) {
|
||||
def name = "temperature"
|
||||
def temperatureScale = getTemperatureScale()
|
||||
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
|
||||
@@ -360,7 +336,7 @@ private Map getTempResult(part, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private Map getXyzResult(results, description) {
|
||||
private getXyzResult(results, description) {
|
||||
def name = "threeAxis"
|
||||
def value = "${results.x},${results.y},${results.z}"
|
||||
def linkText = getLinkText(device)
|
||||
@@ -379,7 +355,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 name = "battery"
|
||||
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
|
||||
@@ -400,7 +376,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 valueString = part.split(":")[1].trim()
|
||||
def value = (Integer.parseInt(valueString) - 128).toString()
|
||||
@@ -431,7 +407,7 @@ private Map getRssiResult(part, description, lastHop=false) {
|
||||
* Note: To make the signal strength indicator more accurate, we could combine
|
||||
* LQI with RSSI.
|
||||
*/
|
||||
private Map getLqiResult(part, description, lastHop=false) {
|
||||
private getLqiResult(part, description, lastHop=false) {
|
||||
def name = lastHop ? "lastHopLqi" : "lqi"
|
||||
def valueString = part.split(":")[1].trim()
|
||||
def percentageOf = 255
|
||||
@@ -476,7 +452,6 @@ private Boolean isOrientationMessage(String description) {
|
||||
description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/
|
||||
}
|
||||
|
||||
//Note: Not using this method anymore
|
||||
private String parseName(String description) {
|
||||
if (isSupportedDescription(description)) {
|
||||
return "contact"
|
||||
|
||||
@@ -13,19 +13,18 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
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 "Configuration"
|
||||
capability "Contact Sensor"
|
||||
capability "Acceleration Sensor"
|
||||
capability "Refresh"
|
||||
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: "3321"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -226,8 +225,7 @@ def getTemperature(value) {
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else if (volts > 3.5) {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
@@ -300,7 +298,6 @@ def getTemperature(value) {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
@@ -16,18 +16,17 @@
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Battery"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
capability "Contact Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
command "enrollResponse"
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -220,8 +219,7 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else if (volts > 3.5) {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
*
|
||||
*/
|
||||
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 "Battery"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
|
||||
}
|
||||
@@ -197,8 +196,7 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else if (volts > 3.5) {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
@@ -252,7 +250,6 @@ def refresh()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def configCmds = [
|
||||
|
||||
@@ -32,15 +32,14 @@ metadata {
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("VALUE_UP", action: "tempUp")
|
||||
attributeState("VALUE_DOWN", action: "tempDown")
|
||||
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:"#ea5462")
|
||||
attributeState("heating", backgroundColor:"#ffa81e")
|
||||
attributeState("cooling", backgroundColor:"#269bd2")
|
||||
}
|
||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
||||
|
||||
@@ -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()"
|
||||
}
|
||||
@@ -33,7 +33,7 @@ metadata {
|
||||
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") {
|
||||
state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh"
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
/**
|
||||
* Iris Smart Fob
|
||||
*
|
||||
* Copyright 2015 Mitch Pond
|
||||
* Presence code adapted from SmartThings Arrival Sensor HA device type
|
||||
*
|
||||
* 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 "Battery"
|
||||
capability "Button"
|
||||
capability "Configuration"
|
||||
capability "Presence Sensor"
|
||||
capability "Sensor"
|
||||
|
||||
//fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0001,0003,0007,0020,0B05", outClusters: "0003,0006,0019", model:"3450-L", manufacturer: "CentraLite"
|
||||
}
|
||||
|
||||
preferences{
|
||||
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"",
|
||||
defaultValue: 3, displayDuringSetup: false)
|
||||
input "checkInterval", "enum", title: "Presence timeout (minutes)",
|
||||
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
|
||||
input "logging", "bool", title: "Enable debug logging",
|
||||
defaultValue: false, displayDuringSetup: false
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
standardTile("presence", "device.presence", width: 4, height: 4, canChangeBackground: true) {
|
||||
state "present", label: "Present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
|
||||
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("button", "device.button", decoration: "flat", width: 2, height: 2) {
|
||||
state "default", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
|
||||
}
|
||||
valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main (["presence"])
|
||||
details(["presence","button","battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||
logIt descMap
|
||||
state.lastCheckin = now()
|
||||
logIt "lastCheckin = ${state.lastCheckin}"
|
||||
handlePresenceEvent(true)
|
||||
|
||||
def results = []
|
||||
if (description?.startsWith('catchall:'))
|
||||
results = parseCatchAllMessage(descMap)
|
||||
else if (description?.startsWith('read attr -'))
|
||||
results = parseReportAttributeMessage(descMap)
|
||||
else logIt(descMap, "trace")
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
def updated() {
|
||||
startTimer()
|
||||
configure()
|
||||
}
|
||||
|
||||
def configure(){
|
||||
logIt "Configuring Smart Fob..."
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 200"
|
||||
] +
|
||||
zigbee.configureReporting(0x0001,0x0020,0x20,20,20,0x01)
|
||||
}
|
||||
|
||||
def parseCatchAllMessage(descMap) {
|
||||
if (descMap?.clusterId == "0006" && descMap?.command == "01") //button pressed
|
||||
handleButtonPress(descMap.sourceEndpoint as int)
|
||||
else if (descMap?.clusterId == "0006" && descMap?.command == "00") //button released
|
||||
handleButtonRelease(descMap.sourceEndpoint as int)
|
||||
else logIt("Parse: Unhandled message: ${descMap}","trace")
|
||||
}
|
||||
|
||||
def parseReportAttributeMessage(descMap) {
|
||||
if (descMap?.cluster == "0001" && descMap?.attrId == "0020") createBatteryEvent(getBatteryLevel(descMap.value))
|
||||
else logIt descMap
|
||||
}
|
||||
|
||||
private createBatteryEvent(percent) {
|
||||
logIt "Battery level at " + percent
|
||||
return createEvent([name: "battery", value: percent])
|
||||
}
|
||||
|
||||
//this method determines if a press should count as a push or a hold and returns the relevant event type
|
||||
private handleButtonRelease(button) {
|
||||
logIt "lastPress state variable: ${state.lastPress}"
|
||||
def sequenceError = {logIt("Uh oh...missed a message? Dropping this event.", "error"); state.lastPress = null; return []}
|
||||
|
||||
if (!state.lastPress) return sequenceError()
|
||||
else if (state.lastPress.button != button) return sequenceError()
|
||||
|
||||
def currentTime = now()
|
||||
def startOfPress = state.lastPress?.time
|
||||
def timeDif = currentTime - startOfPress
|
||||
def holdTimeMillisec = (settings.holdTime?:3).toInteger() * 1000
|
||||
|
||||
state.lastPress = null //we're done with this. clear it to make error conditions easier to catch
|
||||
|
||||
if (timeDif < 0)
|
||||
//likely a message sequence issue or dropped packet. Drop this press and wait for another.
|
||||
return sequenceError()
|
||||
else if (timeDif < holdTimeMillisec)
|
||||
return createButtonEvent(button,"pushed")
|
||||
else
|
||||
return createButtonEvent(button,"held")
|
||||
}
|
||||
|
||||
private handleButtonPress(button) {
|
||||
state.lastPress = [button: button, time: now()]
|
||||
}
|
||||
|
||||
private createButtonEvent(button,action) {
|
||||
logIt "Button ${button} ${action}"
|
||||
return createEvent([
|
||||
name: "button",
|
||||
value: action,
|
||||
data:[buttonNumber: button],
|
||||
descriptionText: "${device.displayName} button ${button} was ${action}",
|
||||
isStateChange: true,
|
||||
displayed: true])
|
||||
}
|
||||
|
||||
private getBatteryLevel(rawValue) {
|
||||
def intValue = Integer.parseInt(rawValue,16)
|
||||
def min = 2.1
|
||||
def max = 3.0
|
||||
def vBatt = intValue / 10
|
||||
return ((vBatt - min) / (max - min) * 100) as int
|
||||
}
|
||||
|
||||
private handlePresenceEvent(present) {
|
||||
def wasPresent = device.currentState("presence")?.value == "present"
|
||||
if (!wasPresent && present) {
|
||||
logIt "Sensor is present"
|
||||
startTimer()
|
||||
} else if (!present) {
|
||||
logIt "Sensor is not present"
|
||||
stopTimer()
|
||||
}
|
||||
def linkText = getLinkText(device)
|
||||
def eventMap = [
|
||||
name: "presence",
|
||||
value: present ? "present" : "not present",
|
||||
linkText: linkText,
|
||||
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
|
||||
]
|
||||
logIt "Creating presence event: ${eventMap}"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
|
||||
private startTimer() {
|
||||
logIt "Scheduling periodic timer"
|
||||
schedule("0 * * * * ?", checkPresenceCallback)
|
||||
}
|
||||
|
||||
private stopTimer() {
|
||||
logIt "Stopping periodic timer"
|
||||
unschedule()
|
||||
}
|
||||
|
||||
def checkPresenceCallback() {
|
||||
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
|
||||
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
|
||||
logIt "Sensor checked in ${timeSinceLastCheckin} seconds ago"
|
||||
if (timeSinceLastCheckin >= theCheckInterval) {
|
||||
handlePresenceEvent(false)
|
||||
}
|
||||
}
|
||||
|
||||
// ****** Utility functions ******
|
||||
|
||||
private logIt(str, logLevel = 'debug') {if (settings.logging) log."$logLevel"(str) }
|
||||
@@ -44,7 +44,7 @@ metadata {
|
||||
attributeState "power", label:'${currentValue} W'
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
@@ -56,17 +56,21 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
log.info event
|
||||
if (event.name == "power") {
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
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
|
||||
event.value = (event.value as Integer) / 10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(event)
|
||||
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendEvent(event)
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -39,7 +39,7 @@ metadata {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
@@ -51,9 +51,15 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
sendEvent(event)
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
//@Deprecated - Moved to zll-rgbw-bulb
|
||||
|
||||
/* Philips Hue (via Zigbee)
|
||||
|
||||
Capabilities:
|
||||
@@ -25,10 +22,10 @@ Capabilities:
|
||||
Sensor
|
||||
Switch
|
||||
Switch Level
|
||||
|
||||
|
||||
Custom Commands:
|
||||
setAdjustedColor
|
||||
|
||||
|
||||
*/
|
||||
|
||||
metadata {
|
||||
@@ -44,7 +41,7 @@ metadata {
|
||||
|
||||
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
|
||||
@@ -66,7 +63,7 @@ metadata {
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
|
||||
|
||||
@@ -23,14 +23,22 @@
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_5", deviceJoinName: "Kwikset 5-Button Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_LEVER_5", deviceJoinName: "Kwikset 5-Button Lever"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10T", deviceJoinName: "Kwikset 10-Button Touch Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale Touch Screen Lever Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_5", deviceJoinName: "Kwikset 5-Button Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||
manufacturer: "Kwikset", model: "SMARTCODE_LEVER_5", deviceJoinName: "Kwikset 5-Button Lever"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10T", deviceJoinName: "Kwikset 10-Button Touch Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||
manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale Touch Screen Lever Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||
manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||
manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||
manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
@@ -52,7 +60,7 @@
|
||||
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) {
|
||||
standardTile("refresh", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
@@ -83,19 +91,32 @@ def uninstalled() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
/*
|
||||
def cmds =
|
||||
zigbee.configureReporting(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE,
|
||||
TYPE_ENUM8, 0, 3600, null) +
|
||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
|
||||
TYPE_U8, 600, 21600, 0x01)
|
||||
zigbee.configSetup("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}",
|
||||
"${TYPE_ENUM8}", 0, 3600, "{01}") +
|
||||
zigbee.configSetup("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}",
|
||||
"${TYPE_U8}", 600, 21600, "{01}")
|
||||
*/
|
||||
def zigbeeId = device.zigbeeId
|
||||
def cmds =
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_DOORLOCK} {$zigbeeId} {}", "delay 200",
|
||||
"zcl global send-me-a-report ${CLUSTER_DOORLOCK} ${DOORLOCK_ATTR_LOCKSTATE} ${TYPE_ENUM8} 0 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_POWER} {$zigbeeId} {}", "delay 200",
|
||||
"zcl global send-me-a-report ${CLUSTER_POWER} ${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING} ${TYPE_U8} 600 21600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
|
||||
]
|
||||
log.info "configure() --- cmds: $cmds"
|
||||
return cmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
def cmds =
|
||||
zigbee.readAttribute(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE) +
|
||||
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
|
||||
zigbee.refreshData("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}") +
|
||||
zigbee.refreshData("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}")
|
||||
log.info "refresh() --- cmds: $cmds"
|
||||
return cmds
|
||||
}
|
||||
@@ -108,27 +129,34 @@ def parse(String description) {
|
||||
map = parseReportAttributeMessage(description)
|
||||
}
|
||||
|
||||
log.debug "parse() --- Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
log.debug "parse() --- returned: $result"
|
||||
return result
|
||||
}
|
||||
|
||||
// Lock capability commands
|
||||
def lock() {
|
||||
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_LOCK_DOOR)
|
||||
log.info "lock() -- cmds: $cmds"
|
||||
return cmds
|
||||
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
|
||||
//log.info "lock() -- cmds: $cmds"
|
||||
//return cmds
|
||||
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_LOCK_DOOR} {}"
|
||||
}
|
||||
|
||||
def unlock() {
|
||||
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_UNLOCK_DOOR)
|
||||
log.info "unlock() -- cmds: $cmds"
|
||||
return cmds
|
||||
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
|
||||
//log.info "unlock() -- cmds: $cmds"
|
||||
//return cmds
|
||||
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_UNLOCK_DOOR} {}"
|
||||
}
|
||||
|
||||
// Private methods
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
log.trace "parseReportAttributeMessage() --- description: $description"
|
||||
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
|
||||
log.debug "parseReportAttributeMessage() --- descMap: $descMap"
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
|
||||
resultMap.name = "battery"
|
||||
@@ -136,24 +164,18 @@ private Map parseReportAttributeMessage(String description) {
|
||||
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
|
||||
resultMap.value = Integer.parseInt(descMap.value, 16)
|
||||
}
|
||||
log.info "parseReportAttributeMessage() --- battery: ${resultMap.value}"
|
||||
}
|
||||
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {
|
||||
def value = Integer.parseInt(descMap.value, 16)
|
||||
def linkText = getLinkText(device)
|
||||
resultMap.name = "lock"
|
||||
if (value == 0) {
|
||||
resultMap.value = "unknown"
|
||||
resultMap.descriptionText = "${linkText} is not fully locked"
|
||||
} else if (value == 1) {
|
||||
resultMap.value = "locked"
|
||||
resultMap.descriptionText = "${linkText} is locked"
|
||||
} else if (value == 2) {
|
||||
resultMap.value = "unlocked"
|
||||
resultMap.descriptionText = "${linkText} is unlocked"
|
||||
} else {
|
||||
resultMap.value = "unknown"
|
||||
resultMap.descriptionText = "${linkText} is in unknown lock state"
|
||||
}
|
||||
resultMap.putAll([0:["value":"unknown",
|
||||
"descriptionText":"Not fully locked"],
|
||||
1:["value":"locked"],
|
||||
2:["value":"unlocked"]].get(value,
|
||||
["value":"unknown",
|
||||
"descriptionText":"Unknown lock state"]))
|
||||
log.info "parseReportAttributeMessage() --- lock: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
log.debug "parseReportAttributeMessage() --- ignoring attribute"
|
||||
|
||||
@@ -57,7 +57,7 @@ metadata {
|
||||
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) {
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ metadata {
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "0003, 000A, 0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 000F, 0B04", outClusters: "0019", manufacturer: "SmartThings", model: "outletv4", deviceJoinName: "Outlet"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
@@ -40,7 +39,7 @@ metadata {
|
||||
attributeState "power", label:'${currentValue} W'
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
@@ -51,15 +50,22 @@ metadata {
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
if (event.name == "power") {
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else if (resultMap.type == "power") {
|
||||
def powerValue
|
||||
powerValue = (event.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendEvent(event)
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -42,7 +42,7 @@ metadata {
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
@@ -53,9 +53,16 @@ metadata {
|
||||
// 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)
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
|
||||
@@ -54,7 +54,7 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
@@ -73,9 +73,16 @@ metadata {
|
||||
// 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)
|
||||
|
||||
def finalResult = zigbee.getKnownDescription(description)
|
||||
if (finalResult) {
|
||||
log.info finalResult
|
||||
if (finalResult.type == "update") {
|
||||
log.info "$device updates: ${finalResult.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: finalResult.type, value: finalResult.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -25,10 +25,7 @@ metadata {
|
||||
|
||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x80,0x84,0x85,0x86,0x72"
|
||||
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,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -243,25 +240,17 @@ def batteryGetCommand() {
|
||||
def retypeBasedOnMSR() {
|
||||
switch (state.MSR) {
|
||||
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")
|
||||
break
|
||||
case "011F-0001-0001": // Schlage motion
|
||||
case "014A-0001-0001": // Ecolink motion
|
||||
case "014A-0004-0001": // Ecolink motion +
|
||||
case "0060-0001-0002": // Everspring SP814
|
||||
case "0060-0001-0003": // Everspring HSP02
|
||||
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")
|
||||
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 Door / Window Sensor Plus (SG)"
|
||||
setDeviceType("Door / Window Sensor Plus (SG)")
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +275,6 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
|
||||
case 32:
|
||||
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
||||
allCodesDeleted()
|
||||
break
|
||||
case 33:
|
||||
map = [ name: "codeReport", value: cmd.alarmLevel, data: [ code: "" ], isStateChange: true ]
|
||||
map.descriptionText = "$device.displayName code $cmd.alarmLevel was deleted"
|
||||
@@ -342,14 +341,14 @@ def zwaveEvent(UserCodeReport cmd) {
|
||||
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: code ] ]
|
||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier is set"
|
||||
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
||||
map.isStateChange = true
|
||||
map.isStateChange = (code != decrypt(state[name]))
|
||||
}
|
||||
result << createEvent(map)
|
||||
} else {
|
||||
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: "" ] ]
|
||||
if (state.blankcodes && state["reset$name"]) { // we deleted this code so we can tell that our new code gets set
|
||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier was reset"
|
||||
map.displayed = map.isStateChange = true
|
||||
map.displayed = map.isStateChange = false
|
||||
result << createEvent(map)
|
||||
state["set$name"] = state["reset$name"]
|
||||
result << response(setCode(cmd.userIdentifier, state["reset$name"]))
|
||||
@@ -361,7 +360,7 @@ def zwaveEvent(UserCodeReport cmd) {
|
||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier is not set"
|
||||
}
|
||||
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
||||
map.isStateChange = true
|
||||
map.isStateChange = state[name] as Boolean
|
||||
result << createEvent(map)
|
||||
}
|
||||
code = ""
|
||||
|
||||
@@ -57,7 +57,7 @@ def parse(String description) {
|
||||
return result
|
||||
}
|
||||
|
||||
def sensorValueEvent(value) {
|
||||
def sensorValueEvent(Short value) {
|
||||
if (value) {
|
||||
createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
|
||||
} else {
|
||||
@@ -94,24 +94,24 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
||||
{
|
||||
def result = []
|
||||
if (cmd.notificationType == 0x07) {
|
||||
if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice ensors
|
||||
result << sensorValueEvent(cmd.v1AlarmLevel)
|
||||
} else if (cmd.event == 0x01 || cmd.event == 0x02 || cmd.event == 0x07 || cmd.event == 0x08) {
|
||||
if (cmd.event == 0x01 || cmd.event == 0x02) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x00) {
|
||||
result << sensorValueEvent(0)
|
||||
} else if (cmd.event == 0x03) {
|
||||
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
result << response(zwave.batteryV1.batteryGet())
|
||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
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) {
|
||||
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) {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
@@ -119,10 +119,6 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
{
|
||||
def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
|
||||
|
||||
if (state.MSR == "011A-0601-0901" && device.currentState('motion') == null) { // Enerwave motion doesn't always get the associationSet that the hub sends on join
|
||||
result << response(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
|
||||
}
|
||||
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
||||
result << response(zwave.batteryV1.batteryGet())
|
||||
result << response("delay 1200")
|
||||
@@ -183,4 +179,4 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
||||
|
||||
result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,44 +61,37 @@ def parse(String description) {
|
||||
zwaveEvent(cmd, results)
|
||||
}
|
||||
}
|
||||
log.debug "'$description' parsed to ${results.inspect()}"
|
||||
// log.debug "\"$description\" parsed to ${results.inspect()}"
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
def createSmokeOrCOEvents(name, results) {
|
||||
def text = null
|
||||
switch (name) {
|
||||
case "smoke":
|
||||
text = "$device.displayName smoke was detected!"
|
||||
// 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)
|
||||
break
|
||||
case "carbonMonoxide":
|
||||
text = "$device.displayName carbon monoxide was detected!"
|
||||
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
|
||||
break
|
||||
case "tested":
|
||||
text = "$device.displayName was tested"
|
||||
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
|
||||
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
|
||||
break
|
||||
case "smokeClear":
|
||||
text = "$device.displayName smoke is clear"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||
name = "clear"
|
||||
break
|
||||
case "carbonMonoxideClear":
|
||||
text = "$device.displayName carbon monoxide is clear"
|
||||
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
|
||||
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
|
||||
if (name == "smoke") {
|
||||
text = "$device.displayName smoke was detected!"
|
||||
// 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)
|
||||
} else if (name == "carbonMonoxide") {
|
||||
text = "$device.displayName carbon monoxide was detected!"
|
||||
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
|
||||
} else if (name == "tested") {
|
||||
text = "$device.displayName was tested"
|
||||
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
|
||||
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
|
||||
} else if (name == "smokeClear") {
|
||||
text = "$device.displayName smoke is clear"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||
name = "clear"
|
||||
} else if (name == "carbonMonoxideClear") {
|
||||
text = "$device.displayName carbon monoxide is clear"
|
||||
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
|
||||
name = "clear"
|
||||
} else if (name == "testClear") {
|
||||
text = "$device.displayName smoke is clear"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
|
||||
name = "clear"
|
||||
}
|
||||
// This composite event is used for updating the tile
|
||||
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)
|
||||
break
|
||||
case 13: // sent every hour -- not sure what this means, just a wake up notification?
|
||||
if (cmd.alarmLevel == 255) {
|
||||
results << createEvent(descriptionText: "$device.displayName checked in", isStateChange: false)
|
||||
} else {
|
||||
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", isStateChange:true, displayed:false)
|
||||
if (cmd.alarmLevel != 255) {
|
||||
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", displayed: true)
|
||||
}
|
||||
|
||||
// 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
|
||||
if (!state.lastbatt || (now() - state.lastbatt) >= 48*60*60*1000) {
|
||||
results << response(zwave.batteryV1.batteryGet())
|
||||
def prevBattery = device.currentState("battery")
|
||||
if (!prevBattery || (new Date().time - prevBattery.date.time)/60000 >= 60 * 53) {
|
||||
results << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
|
||||
}
|
||||
break
|
||||
default:
|
||||
@@ -166,17 +158,12 @@ def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd,
|
||||
}
|
||||
|
||||
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)
|
||||
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 map = [ name: "battery", unit: "%", isStateChange: true ]
|
||||
state.lastbatt = now()
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "$device.displayName battery is low!"
|
||||
|
||||
@@ -95,17 +95,11 @@ def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||
}
|
||||
|
||||
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])
|
||||
if (state.manufacturer != cmd.manufacturerName) {
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
// Handles all Z-Wave commands we aren't interested in
|
||||
[:]
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +0,0 @@
|
||||
#Thu Feb 25 08:56:06 CST 2016
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip
|
||||
160
gradlew
vendored
160
gradlew
vendored
@@ -1,160 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
90
gradlew.bat
vendored
90
gradlew.bat
vendored
@@ -1,90 +0,0 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* This settings file was auto generated by the Gradle buildInit task
|
||||
* by 'jblaisdell' at '2/25/16 8:56 AM' with Gradle 2.10
|
||||
*
|
||||
* The settings file is used to specify which projects to include in your build.
|
||||
* In a single project build this file can be empty or even removed.
|
||||
*
|
||||
* Detailed information about configuring a multi-project build in Gradle can be found
|
||||
* in the user guide at https://docs.gradle.org/2.10/userguide/multi_project_builds.html
|
||||
*/
|
||||
|
||||
/*
|
||||
// To declare projects as part of a multi-project build use the 'include' method
|
||||
include 'shared'
|
||||
include 'api'
|
||||
include 'services:webservice'
|
||||
*/
|
||||
|
||||
rootProject.name = 'SmartThingsPublic'
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* %253Cscript%253Ealert('XSS')%253C%252Fscript%253E
|
||||
*
|
||||
* Copyright 2016 Julie Stg <script>alert('WAT!')</script>
|
||||
*
|
||||
* 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: "<script>alert('WAT!')</script>",
|
||||
namespace: "<script>alert('WAT!')</script>",
|
||||
author: "Julie Stg <script>alert('WAT!')</script>",
|
||||
description: "Some description",
|
||||
category: "",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
|
||||
|
||||
|
||||
preferences {
|
||||
section("Title") {
|
||||
// TODO: put inputs here
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// TODO: subscribe to attributes, devices, locations, etc.
|
||||
}
|
||||
|
||||
// TODO: implement event handlers
|
||||
@@ -1,147 +0,0 @@
|
||||
/**
|
||||
* BeaconThing Manager
|
||||
*
|
||||
* Copyright 2015 obycode
|
||||
*
|
||||
* 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: "BeaconThings Manager",
|
||||
namespace: "com.obycode",
|
||||
author: "obycode",
|
||||
description: "SmartApp to interact with the BeaconThings iOS app. Use this app to integrate iBeacons into your smart home.",
|
||||
category: "Convenience",
|
||||
iconUrl: "http://beaconthingsapp.com/images/Icon-60.png",
|
||||
iconX2Url: "http://beaconthingsapp.com/images/Icon-60@2x.png",
|
||||
iconX3Url: "http://beaconthingsapp.com/images/Icon-60@3x.png",
|
||||
oauth: true)
|
||||
|
||||
|
||||
preferences {
|
||||
section("Allow BeaconThings to talk to your home") {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
}
|
||||
|
||||
def uninstalled() {
|
||||
removeChildDevices(getChildDevices())
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/beacons") {
|
||||
action: [
|
||||
DELETE: "clearBeacons",
|
||||
POST: "addBeacon"
|
||||
]
|
||||
}
|
||||
|
||||
path("/beacons/:id") {
|
||||
action: [
|
||||
PUT: "updateBeacon",
|
||||
DELETE: "deleteBeacon"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
void clearBeacons() {
|
||||
removeChildDevices(getChildDevices())
|
||||
}
|
||||
|
||||
void addBeacon() {
|
||||
def beacon = request.JSON?.beacon
|
||||
if (beacon) {
|
||||
def beaconId = "BeaconThings"
|
||||
if (beacon.major) {
|
||||
beaconId = "$beaconId-${beacon.major}"
|
||||
if (beacon.minor) {
|
||||
beaconId = "$beaconId-${beacon.minor}"
|
||||
}
|
||||
}
|
||||
log.debug "adding beacon $beaconId"
|
||||
def d = addChildDevice("com.obycode", "BeaconThing", beaconId, null, [label:beacon.name, name:"BeaconThing", completedSetup: true])
|
||||
log.debug "addChildDevice returned $d"
|
||||
|
||||
if (beacon.present) {
|
||||
d.arrive(beacon.present)
|
||||
}
|
||||
else if (beacon.presence) {
|
||||
d.setPresence(beacon.presence)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateBeacon() {
|
||||
log.debug "updating beacon ${params.id}"
|
||||
def beaconDevice = getChildDevice(params.id)
|
||||
// def children = getChildDevices()
|
||||
// def beaconDevice = children.find{ d -> d.deviceNetworkId == "${params.id}" }
|
||||
if (!beaconDevice) {
|
||||
log.debug "Beacon not found directly"
|
||||
def children = getChildDevices()
|
||||
beaconDevice = children.find{ d -> d.deviceNetworkId == "${params.id}" }
|
||||
if (!beaconDevice) {
|
||||
log.debug "Beacon not found in list either"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// This could be just updating the presence
|
||||
def presence = request.JSON?.presence
|
||||
if (presence) {
|
||||
log.debug "Setting ${beaconDevice.label} to $presence"
|
||||
beaconDevice.setPresence(presence)
|
||||
}
|
||||
|
||||
// It could be someone arriving
|
||||
def arrived = request.JSON?.arrived
|
||||
if (arrived) {
|
||||
log.debug "$arrived arrived at ${beaconDevice.label}"
|
||||
beaconDevice.arrived(arrived)
|
||||
}
|
||||
|
||||
// It could be someone left
|
||||
def left = request.JSON?.left
|
||||
if (left) {
|
||||
log.debug "$left left ${beaconDevice.label}"
|
||||
beaconDevice.left(left)
|
||||
}
|
||||
|
||||
// or it could be updating the name
|
||||
def beacon = request.JSON?.beacon
|
||||
if (beacon) {
|
||||
beaconDevice.label = beacon.name
|
||||
}
|
||||
}
|
||||
|
||||
void deleteBeacon() {
|
||||
log.debug "deleting beacon ${params.id}"
|
||||
deleteChildDevice(params.id)
|
||||
// def children = getChildDevices()
|
||||
// def beaconDevice = children.find{ d -> d.deviceNetworkId == "${params.id}" }
|
||||
// if (beaconDevice) {
|
||||
// deleteChildDevice(beaconDevice.deviceNetworkId)
|
||||
// }
|
||||
}
|
||||
|
||||
private removeChildDevices(delete) {
|
||||
delete.each {
|
||||
deleteChildDevice(it.deviceNetworkId)
|
||||
}
|
||||
}
|
||||
@@ -67,11 +67,11 @@ mappings {
|
||||
def listAllDevices() {
|
||||
def resp = []
|
||||
switches.each {
|
||||
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub?.name]
|
||||
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub.name]
|
||||
}
|
||||
|
||||
locks.each {
|
||||
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub?.name]
|
||||
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub.name]
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -4,33 +4,29 @@
|
||||
import java.text.DecimalFormat
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
private getApiUrl() { "https://api.netatmo.com" }
|
||||
private getVendorName() { "netatmo" }
|
||||
private getVendorAuthPath() { "${apiUrl}/oauth2/authorize?" }
|
||||
private getVendorTokenPath(){ "${apiUrl}/oauth2/token" }
|
||||
private apiUrl() { "https://api.netatmo.com" }
|
||||
private getVendorName() { "netatmo" }
|
||||
private getVendorAuthPath() { "https://api.netatmo.com/oauth2/authorize?" }
|
||||
private getVendorTokenPath(){ "https://api.netatmo.com/oauth2/token" }
|
||||
private getVendorIcon() { "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png" }
|
||||
private getClientId() { appSettings.clientId }
|
||||
private getClientSecret() { appSettings.clientSecret }
|
||||
private getServerUrl() { appSettings.serverUrl }
|
||||
private getShardUrl() { return getApiServerUrl() }
|
||||
private getCallbackUrl() { "${serverUrl}/oauth/callback" }
|
||||
private getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${shardUrl}" }
|
||||
private getClientId() { appSettings.clientId }
|
||||
private getClientSecret() { appSettings.clientSecret }
|
||||
private getServerUrl() { "https://graph.api.smartthings.com" }
|
||||
|
||||
// Automatically generated. Make future change here.
|
||||
definition(
|
||||
name: "Netatmo (Connect)",
|
||||
namespace: "dianoga",
|
||||
author: "Brian Steere",
|
||||
description: "Netatmo Integration",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
|
||||
oauth: true,
|
||||
singleInstance: true
|
||||
name: "Netatmo (Connect)",
|
||||
namespace: "dianoga",
|
||||
author: "Brian Steere",
|
||||
description: "Netatmo Integration",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
|
||||
oauth: true,
|
||||
singleInstance: true
|
||||
){
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
appSetting "serverUrl"
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -39,52 +35,35 @@ preferences {
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
|
||||
path("/oauth/callback") {action: [GET: "callback"]}
|
||||
path("/receivedToken"){action: [POST: "receivedToken", GET: "receivedToken"]}
|
||||
path("/receiveToken"){action: [POST: "receiveToken", GET: "receiveToken"]}
|
||||
path("/auth"){action: [GET: "auth"]}
|
||||
}
|
||||
|
||||
def authPage() {
|
||||
log.debug "In authPage"
|
||||
if(canInstallLabs()) {
|
||||
def description = null
|
||||
|
||||
def description
|
||||
def uninstallAllowed = false
|
||||
def oauthTokenProvided = false
|
||||
if (state.vendorAccessToken == null) {
|
||||
log.debug "About to create access token."
|
||||
|
||||
if (!state.accessToken) {
|
||||
log.debug "About to create access token."
|
||||
state.accessToken = createAccessToken()
|
||||
}
|
||||
createAccessToken()
|
||||
description = "Tap to enter Credentials."
|
||||
|
||||
if (canInstallLabs()) {
|
||||
|
||||
def redirectUrl = getBuildRedirectUrl()
|
||||
log.debug "Redirect url = ${redirectUrl}"
|
||||
|
||||
if (state.authToken) {
|
||||
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 }
|
||||
}
|
||||
} else {
|
||||
description = "Tap 'Next' to proceed"
|
||||
uninstallAllowed = true
|
||||
oauthTokenProvided = true
|
||||
} else {
|
||||
description = "Click to enter Credentials."
|
||||
}
|
||||
|
||||
if (!oauthTokenProvided) {
|
||||
log.debug "Show the login page"
|
||||
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)
|
||||
}
|
||||
return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: true, install:false) {
|
||||
section { href url: buildRedirectUrl("receivedToken"), 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.
|
||||
|
||||
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() {
|
||||
log.debug "In oauthInitUrl"
|
||||
|
||||
/* OAuth Step 1: Request access code with our client ID */
|
||||
|
||||
state.oauthInitState = UUID.randomUUID().toString()
|
||||
|
||||
def oauthParams = [
|
||||
response_type: "code",
|
||||
def oauthParams = [ response_type: "code",
|
||||
client_id: getClientId(),
|
||||
client_secret: getClientSecret(),
|
||||
state: state.oauthInitState,
|
||||
redirect_uri: getCallbackUrl(),
|
||||
redirect_uri: buildRedirectUrl("receiveToken") ,
|
||||
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)}"
|
||||
|
||||
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"
|
||||
}
|
||||
return getVendorAuthPath() + toQueryString(oauthParams)
|
||||
}
|
||||
|
||||
def success() {
|
||||
log.debug "in success"
|
||||
def message = """
|
||||
<p>We have located your """ + getVendorName() + """ account.</p>
|
||||
<p>Tap 'Done' to continue to Devices.</p>
|
||||
"""
|
||||
connectionStatus(message)
|
||||
def buildRedirectUrl(endPoint) {
|
||||
log.debug "In buildRedirectUrl"
|
||||
|
||||
return getServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/${endPoint}"
|
||||
}
|
||||
|
||||
def fail() {
|
||||
log.debug "in fail"
|
||||
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 receiveToken() {
|
||||
log.debug "In receiveToken"
|
||||
|
||||
def oauthParams = [
|
||||
client_secret: getClientSecret(),
|
||||
client_id: getClientId(),
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: state.refreshToken
|
||||
]
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: buildRedirectUrl('receiveToken'),
|
||||
code: params.code,
|
||||
scope: "read_station"
|
||||
]
|
||||
|
||||
def tokenUrl = getVendorTokenPath()
|
||||
def params = [
|
||||
@@ -285,7 +124,201 @@ def refreshToken() {
|
||||
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 {
|
||||
httpPost(params) { response ->
|
||||
def slurper = new JsonSlurper();
|
||||
@@ -294,9 +327,9 @@ def refreshToken() {
|
||||
def data = slurper.parseText(key);
|
||||
log.debug "Data: $data"
|
||||
|
||||
state.refreshToken = data.refresh_token
|
||||
state.accessToken = data.access_token
|
||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||
state.vendorRefreshToken = data.refresh_token
|
||||
state.vendorAccessToken = data.access_token
|
||||
state.vendorTokenExpires = now() + (data.expires_in * 1000)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -305,8 +338,9 @@ def refreshToken() {
|
||||
log.debug "Error: $e"
|
||||
}
|
||||
|
||||
// We didn't get an access token
|
||||
if ( !state.accessToken ) {
|
||||
log.debug "State: $state"
|
||||
|
||||
if ( !state.vendorAccessToken ) { //We didn't get an access token
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -448,13 +482,13 @@ def listDevices() {
|
||||
}
|
||||
|
||||
def apiGet(String path, Map query, Closure callback) {
|
||||
if(now() >= state.tokenExpires) {
|
||||
if(now() >= state.vendorTokenExpires) {
|
||||
refreshToken();
|
||||
}
|
||||
|
||||
query['access_token'] = state.accessToken
|
||||
query['access_token'] = state.vendorAccessToken
|
||||
def params = [
|
||||
uri: getApiUrl(),
|
||||
uri: apiUrl(),
|
||||
path: path,
|
||||
'query': query
|
||||
]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Initial State Event Streamer
|
||||
*
|
||||
* Copyright 2016 David Sulpy
|
||||
* Copyright 2015 David Sulpy
|
||||
*
|
||||
* 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:
|
||||
@@ -77,62 +77,6 @@ mappings {
|
||||
}
|
||||
}
|
||||
|
||||
def getAccessKey() {
|
||||
log.trace "get access key"
|
||||
if (atomicState.accessKey == null) {
|
||||
httpError(404, "Access Key Not Found")
|
||||
} else {
|
||||
[
|
||||
accessKey: atomicState.accessKey
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def getBucketKey() {
|
||||
log.trace "get bucket key"
|
||||
if (atomicState.bucketKey == null) {
|
||||
httpError(404, "Bucket key Not Found")
|
||||
} else {
|
||||
[
|
||||
bucketKey: atomicState.bucketKey,
|
||||
bucketName: atomicState.bucketName
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def setBucketKey() {
|
||||
log.trace "set bucket key"
|
||||
def newBucketKey = request.JSON?.bucketKey
|
||||
def newBucketName = request.JSON?.bucketName
|
||||
|
||||
log.debug "bucket name: $newBucketName"
|
||||
log.debug "bucket key: $newBucketKey"
|
||||
|
||||
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
|
||||
atomicState.bucketKey = "$newBucketKey"
|
||||
atomicState.bucketName = "$newBucketName"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
tryCreateBucket()
|
||||
}
|
||||
|
||||
def setAccessKey() {
|
||||
log.trace "set access key"
|
||||
def newAccessKey = request.JSON?.accessKey
|
||||
def newGrokerSubdomain = request.JSON?.grokerSubdomain
|
||||
|
||||
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
|
||||
atomicState.grokerSubdomain = "$newGrokerSubdomain"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
if (newAccessKey && newAccessKey != atomicState.accessKey) {
|
||||
atomicState.accessKey = "$newAccessKey"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
}
|
||||
|
||||
def subscribeToEvents() {
|
||||
if (accelerometers != null) {
|
||||
subscribe(accelerometers, "acceleration", genericHandler)
|
||||
@@ -225,27 +169,85 @@ def subscribeToEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
def getAccessKey() {
|
||||
log.trace "get access key"
|
||||
if (atomicState.accessKey == null) {
|
||||
httpError(404, "Access Key Not Found")
|
||||
} else {
|
||||
[
|
||||
accessKey: atomicState.accessKey
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def getBucketKey() {
|
||||
log.trace "get bucket key"
|
||||
if (atomicState.bucketKey == null) {
|
||||
httpError(404, "Bucket key Not Found")
|
||||
} else {
|
||||
[
|
||||
bucketKey: atomicState.bucketKey,
|
||||
bucketName: atomicState.bucketName
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def setBucketKey() {
|
||||
log.trace "set bucket key"
|
||||
def newBucketKey = request.JSON?.bucketKey
|
||||
def newBucketName = request.JSON?.bucketName
|
||||
|
||||
log.debug "bucket name: $newBucketName"
|
||||
log.debug "bucket key: $newBucketKey"
|
||||
|
||||
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
|
||||
atomicState.bucketKey = "$newBucketKey"
|
||||
atomicState.bucketName = "$newBucketName"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
tryCreateBucket()
|
||||
}
|
||||
|
||||
def setAccessKey() {
|
||||
log.trace "set access key"
|
||||
def newAccessKey = request.JSON?.accessKey
|
||||
def newGrokerSubdomain = request.JSON?.grokerSubdomain
|
||||
|
||||
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
|
||||
atomicState.grokerSubdomain = "$newGrokerSubdomain"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
if (newAccessKey && newAccessKey != atomicState.accessKey) {
|
||||
atomicState.accessKey = "$newAccessKey"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
atomicState.version = "1.1.0"
|
||||
|
||||
atomicState.isBucketCreated = false
|
||||
atomicState.grokerSubdomain = "groker"
|
||||
|
||||
atomicState.version = "1.0.18"
|
||||
subscribeToEvents()
|
||||
|
||||
atomicState.isBucketCreated = false
|
||||
atomicState.grokerSubdomain = "groker"
|
||||
atomicState.eventBuffer = []
|
||||
|
||||
runEvery15Minutes(flushBuffer)
|
||||
|
||||
log.debug "installed (version $atomicState.version)"
|
||||
}
|
||||
|
||||
def updated() {
|
||||
atomicState.version = "1.1.0"
|
||||
atomicState.version = "1.0.18"
|
||||
unsubscribe()
|
||||
|
||||
if (atomicState.bucketKey != null && atomicState.accessKey != null) {
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
if (atomicState.eventBuffer == null) {
|
||||
atomicState.eventBuffer = []
|
||||
}
|
||||
if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
|
||||
atomicState.grokerSubdomain = "groker"
|
||||
}
|
||||
@@ -325,17 +327,37 @@ def genericHandler(evt) {
|
||||
eventHandler(key, value)
|
||||
}
|
||||
|
||||
def eventHandler(name, value) {
|
||||
def epoch = now() / 1000
|
||||
|
||||
def event = new JsonSlurper().parseText("{\"key\": \"$name\", \"value\": \"$value\", \"epoch\": \"$epoch\"}")
|
||||
|
||||
tryShipEvents(event)
|
||||
|
||||
log.debug "Shipped Event: " + event
|
||||
// This is a handler function for flushing the event buffer
|
||||
// after a specified amount of time to reduce the load on ST servers
|
||||
def flushBuffer() {
|
||||
def eventBuffer = atomicState.eventBuffer
|
||||
log.trace "About to flush the buffer on schedule"
|
||||
if (eventBuffer != null && eventBuffer.size() > 0) {
|
||||
atomicState.eventBuffer = []
|
||||
tryShipEvents(eventBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
def tryShipEvents(event) {
|
||||
def eventHandler(name, value) {
|
||||
def epoch = now() / 1000
|
||||
def eventBuffer = atomicState.eventBuffer ?: []
|
||||
eventBuffer << [key: "$name", value: "$value", epoch: "$epoch"]
|
||||
|
||||
if (eventBuffer.size() >= 10) {
|
||||
// Clear eventBuffer right away since we've already pulled it off of atomicState to reduce the risk of missing
|
||||
// events. This assumes the grokerSubdomain, accessKey, and bucketKey are set correctly to avoid the eventBuffer
|
||||
// from growing unbounded.
|
||||
atomicState.eventBuffer = []
|
||||
tryShipEvents(eventBuffer)
|
||||
} else {
|
||||
// Make sure we persist the updated eventBuffer with the new event added back to atomicState
|
||||
atomicState.eventBuffer = eventBuffer
|
||||
}
|
||||
log.debug "Event added to buffer: " + eventBuffer
|
||||
}
|
||||
|
||||
// a helper function for shipping the atomicState.eventBuffer to Initial State
|
||||
def tryShipEvents(eventBuffer) {
|
||||
|
||||
def grokerSubdomain = atomicState.grokerSubdomain
|
||||
// can't ship events if there is no grokerSubdomain
|
||||
@@ -358,7 +380,7 @@ def tryShipEvents(event) {
|
||||
"X-IS-AccessKey": "${accessKey}",
|
||||
"Accept-Version": "0.0.2"
|
||||
],
|
||||
body: event
|
||||
body: eventBuffer
|
||||
]
|
||||
|
||||
try {
|
||||
|
||||
@@ -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 results = [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,345 +0,0 @@
|
||||
/**
|
||||
* SmartThings service for Prempoint
|
||||
*
|
||||
* Author: Prempoint Inc. (c) 2016
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "Prempoint",
|
||||
namespace: "prempoint.com",
|
||||
author: "Prempoint Inc.",
|
||||
description: "SmartThings service for Prempoint",
|
||||
category: "Connections",
|
||||
iconUrl: "http://www.prempoint.com/images/social_app_emblem_50x50.png",
|
||||
iconX2Url: "http://www.prempoint.com/images/social_app_emblem_100x100.png",
|
||||
iconX3Url: "http://www.prempoint.com/images/social_app_emblem_150x150.png",
|
||||
oauth: [displayName: "Prempoint", displayLink: "http://www.prempoint.com/"])
|
||||
|
||||
preferences {
|
||||
section("Allow Prempoint to Control & Access These Things...") {
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "garagedoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false
|
||||
//input "doors", "capability.doorControl", title: "Which Doors?", multiple: true, required: false
|
||||
input "cameras", "capability.imageCapture", title: "Which Cameras?", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/list") {
|
||||
action: [
|
||||
GET: "listDevices"
|
||||
]
|
||||
}
|
||||
path("/switches") {
|
||||
action: [
|
||||
GET: "listSwitches"
|
||||
]
|
||||
}
|
||||
path("/switches/:id") {
|
||||
action: [
|
||||
GET: "showSwitch"
|
||||
]
|
||||
}
|
||||
path("/switches/:id/:command") {
|
||||
action: [
|
||||
GET: "updateSwitch"
|
||||
]
|
||||
}
|
||||
path("/switches/:id/:command/:level") {
|
||||
action: [
|
||||
GET: "updateSwitch"
|
||||
]
|
||||
}
|
||||
path("/locks") {
|
||||
action: [
|
||||
GET: "listLocks"
|
||||
]
|
||||
}
|
||||
path("/locks/:id") {
|
||||
action: [
|
||||
GET: "showLock"
|
||||
]
|
||||
}
|
||||
path("/locks/:id/:command") {
|
||||
action: [
|
||||
GET: "updateLock"
|
||||
]
|
||||
}
|
||||
path("/doors/:id") {
|
||||
action: [
|
||||
GET: "showDoor"
|
||||
]
|
||||
}
|
||||
path("/doors/:id/:command") {
|
||||
action: [
|
||||
GET: "updateDoor"
|
||||
]
|
||||
}
|
||||
path("/garagedoors/:id") {
|
||||
action: [
|
||||
GET: "showGarageDoor"
|
||||
]
|
||||
}
|
||||
path("/garagedoors/:id/:command") {
|
||||
action: [
|
||||
GET: "updateGarageDoor"
|
||||
]
|
||||
}
|
||||
path("/cameras/:id") {
|
||||
action: [
|
||||
GET: "showCamera"
|
||||
]
|
||||
}
|
||||
path("/cameras/:id/:command") {
|
||||
action: [
|
||||
GET: "updateCamera"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {}
|
||||
|
||||
def updated() {}
|
||||
|
||||
def listDevices() {
|
||||
log.debug "entering listDevices"
|
||||
//return listSwitches() + listLocks() + listGarageDoors() + listDoors() + listCameras()
|
||||
return listSwitches() + listLocks() + listGarageDoors() + listCameras()
|
||||
}
|
||||
|
||||
//switches
|
||||
def listSwitches() {
|
||||
log.debug "entering listSwitches"
|
||||
switches.collect{showDevice(it,"switch")}
|
||||
}
|
||||
|
||||
def showSwitch() {
|
||||
log.debug "entering showSwitches"
|
||||
show(switches, "switch")
|
||||
}
|
||||
|
||||
def updateSwitch() {
|
||||
log.debug "entering updateSwitches"
|
||||
update(switches, "switch")
|
||||
}
|
||||
|
||||
//locks
|
||||
def listLocks() {
|
||||
log.debug "entering listLocks"
|
||||
locks.collect{showDevice(it,"lock")}
|
||||
}
|
||||
|
||||
def showLock() {
|
||||
log.debug "entering showLock"
|
||||
show(locks, "lock")
|
||||
}
|
||||
|
||||
def updateLock() {
|
||||
log.debug "entering updateLock"
|
||||
update(locks, "lock")
|
||||
}
|
||||
|
||||
//doors
|
||||
def listDoors() {
|
||||
log.debug "entering listDoors"
|
||||
locks.collect{showDevice(it,"door")}
|
||||
}
|
||||
|
||||
def showDoor() {
|
||||
log.debug "entering showDoors"
|
||||
show(doors, "door")
|
||||
}
|
||||
|
||||
def updateDoor() {
|
||||
log.debug "entering updateDoor"
|
||||
update(doors, "door")
|
||||
}
|
||||
|
||||
//garagedoors
|
||||
def listGarageDoors() {
|
||||
log.debug "entering listGarageDoors"
|
||||
locks.collect{showDevice(it,"garagedoor")}
|
||||
}
|
||||
|
||||
def showGarageDoor() {
|
||||
log.debug "entering showGarageDoors"
|
||||
show(garagedoors, "garagedoor")
|
||||
}
|
||||
|
||||
def updateGarageDoor() {
|
||||
log.debug "entering updateGarageDoor"
|
||||
update(gargedoors, "garagedoor")
|
||||
}
|
||||
|
||||
//cameras
|
||||
def listCameras() {
|
||||
log.debug "entering listCameras"
|
||||
cameras.collect{showDevice(it,"image")}
|
||||
}
|
||||
|
||||
def showCamera() {
|
||||
log.debug "entering showCameras"
|
||||
show(cameras, "camera")
|
||||
}
|
||||
|
||||
def updateCamera() {
|
||||
log.debug "entering updateCamera"
|
||||
update(cameras, "camera")
|
||||
}
|
||||
|
||||
def deviceHandler(evt) {}
|
||||
|
||||
private update(devices, type) {
|
||||
def rc = null
|
||||
|
||||
//def command = request.JSON?.command
|
||||
def command = params.command
|
||||
|
||||
log.debug "update, request: params: ${params}, devices: $devices.id type=$type command=$command"
|
||||
|
||||
// Process the command.
|
||||
if (command)
|
||||
{
|
||||
def dev = devices.find { it.id == params.id }
|
||||
if (!dev) {
|
||||
httpError(404, "Device not found: $params.id")
|
||||
} else if (type == "switch") {
|
||||
switch(command) {
|
||||
case "on":
|
||||
rc = dev.on()
|
||||
break
|
||||
case "off":
|
||||
rc = dev.off()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
} else if (type == "lock") {
|
||||
switch(command) {
|
||||
case "lock":
|
||||
rc = dev.lock()
|
||||
break
|
||||
case "unlock":
|
||||
rc = dev.unlock()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device:=$it.id $dev")
|
||||
}
|
||||
} else if (type == "door") {
|
||||
switch(command) {
|
||||
case "open":
|
||||
rc = dev.open()
|
||||
break
|
||||
case "close":
|
||||
rc = dev.close()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
} else if (type == "garagedoor") {
|
||||
switch(command) {
|
||||
case "open":
|
||||
rc = dev.open()
|
||||
break
|
||||
case "close":
|
||||
rc = dev.close()
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
} else if (type == "camera") {
|
||||
switch(command) {
|
||||
case "take":
|
||||
rc = dev.take()
|
||||
log.debug "Device command=$command device=$it.id $dev current image=$it.currentImage"
|
||||
break
|
||||
default:
|
||||
httpError(400, "Device command=$command is not a valid for device=$it.id $dev")
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "executed device=$it.id $dev command=$command rc=$rc"
|
||||
|
||||
// Check that the device is a switch that is currently on, supports 'setLevel"
|
||||
// and that a level was specified.
|
||||
int level = params.level ? params.level as int : -1;
|
||||
if ((type == "switch") && (dev.currentValue('switch') == "on") && hasLevel(dev) && (level != -1)) {
|
||||
log.debug "device about to setLevel=$level"
|
||||
dev.setLevel(level);
|
||||
}
|
||||
|
||||
// Show the device info if necessary.
|
||||
if (rc == null) {
|
||||
rc = showDevice(dev, type)
|
||||
}
|
||||
}
|
||||
|
||||
return rc
|
||||
}
|
||||
|
||||
private show(devices, type) {
|
||||
def dev = devices.find { it.id == params.id }
|
||||
if (!dev) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
// Show the device info.
|
||||
showDevice(dev, type)
|
||||
}
|
||||
}
|
||||
|
||||
private showDevice(it, type) {
|
||||
def props = null
|
||||
|
||||
// Get the current state for the device type.
|
||||
def state = [it.currentState(type)]
|
||||
|
||||
// Check that whether the a switch device with level support is located and update the returned device type.
|
||||
def devType = type
|
||||
|
||||
if (type == "switch" && hasLevel(it)) {
|
||||
// Assign "switchWithLevel" to device type.
|
||||
devType = "switchWithLevel"
|
||||
// Add the level state.
|
||||
def levelState = it.currentState("level")
|
||||
if (levelState) {
|
||||
state.add(levelState)
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "device label=$it.label type=$devType"
|
||||
|
||||
// Assign the device item properties if appropriate.
|
||||
if (it) {
|
||||
props = [id: it.id, label: it.label, type: devType, state: state]
|
||||
// Add the hub information to the device properties
|
||||
// if appropriate.
|
||||
if (it.hub) {
|
||||
props.put("location", it.hub.hub.location)
|
||||
}
|
||||
if (it.currentImage) {
|
||||
props.put("currentImage", it.currentImage)
|
||||
}
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
private hasLevel(device) {
|
||||
// Default return value.
|
||||
def rc = false;
|
||||
|
||||
// Get the device supported commands.
|
||||
def supportedCommands = device.supportedCommands
|
||||
|
||||
// Check to see if the "setLevel" was found and assign
|
||||
// the appropriate return value.
|
||||
if (supportedCommands) {
|
||||
// Find the "setLevel" command.
|
||||
rc = supportedCommands.toString().indexOf("setLevel") != -1
|
||||
}
|
||||
|
||||
log.debug "hasLevel device label=$device.label supportedCommands=$supportedCommands rc=$rc"
|
||||
|
||||
return rc
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
/**
|
||||
* Simple Sync Connect
|
||||
*
|
||||
* Copyright 2015 Roomie Remote, Inc.
|
||||
*
|
||||
* Date: 2015-09-22
|
||||
*/
|
||||
|
||||
definition(
|
||||
name: "Simple Sync Connect",
|
||||
namespace: "roomieremote-raconnect",
|
||||
author: "Roomie Remote, Inc.",
|
||||
description: "Integrate SmartThings with your Simple Control activities via Simple Sync.",
|
||||
category: "My Apps",
|
||||
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
|
||||
|
||||
preferences()
|
||||
{
|
||||
page(name: "mainPage", title: "Simple Sync Setup", content: "mainPage", refreshTimeout: 5)
|
||||
page(name:"agentDiscovery", title:"Simple Sync Discovery", content:"agentDiscovery", refreshTimeout:5)
|
||||
page(name:"manualAgentEntry")
|
||||
page(name:"verifyManualEntry")
|
||||
}
|
||||
|
||||
def mainPage()
|
||||
{
|
||||
if (canInstallLabs())
|
||||
{
|
||||
return agentDiscovery()
|
||||
}
|
||||
else
|
||||
{
|
||||
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"."""
|
||||
|
||||
return dynamicPage(name:"mainPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
|
||||
section("Upgrade")
|
||||
{
|
||||
paragraph "$upgradeNeeded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def agentDiscovery(params=[:])
|
||||
{
|
||||
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
|
||||
state.refreshCount = refreshCount + 1
|
||||
def refreshInterval = refreshCount == 0 ? 2 : 5
|
||||
|
||||
if (!state.subscribe)
|
||||
{
|
||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||
state.subscribe = true
|
||||
}
|
||||
|
||||
//ssdp request every fifth refresh
|
||||
if ((refreshCount % 5) == 0)
|
||||
{
|
||||
discoverAgents()
|
||||
}
|
||||
|
||||
def agentsDiscovered = agentsDiscovered()
|
||||
|
||||
return dynamicPage(name:"agentDiscovery", title:"Pair with Simple Sync", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
|
||||
section("Pair with Simple Sync")
|
||||
{
|
||||
input "selectedAgent", "enum", required:true, title:"Select Simple Sync\n(${agentsDiscovered.size() ?: 0} found)", multiple:false, options:agentsDiscovered
|
||||
href(name:"manualAgentEntry",
|
||||
title:"Manually Configure Simple Sync",
|
||||
required:false,
|
||||
page:"manualAgentEntry")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def manualAgentEntry()
|
||||
{
|
||||
dynamicPage(name:"manualAgentEntry", title:"Manually Configure Simple Sync", nextPage:"verifyManualEntry", install:false, uninstall:true) {
|
||||
section("Manually Configure Simple Sync")
|
||||
{
|
||||
paragraph "In the event that Simple Sync cannot be automatically discovered by your SmartThings hub, you may enter Simple Sync's IP address here."
|
||||
input(name: "manualIPAddress", type: "text", title: "IP Address", required: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def verifyManualEntry()
|
||||
{
|
||||
def hexIP = convertIPToHexString(manualIPAddress)
|
||||
def hexPort = convertToHexString(47147)
|
||||
def uuid = "593C03D2-1DA9-4CDB-A335-6C6DC98E56C3"
|
||||
def hubId = ""
|
||||
|
||||
for (hub in location.hubs)
|
||||
{
|
||||
if (hub.localIP != null)
|
||||
{
|
||||
hubId = hub.id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def manualAgent = [deviceType: "04",
|
||||
mac: "unknown",
|
||||
ip: hexIP,
|
||||
port: hexPort,
|
||||
ssdpPath: "/upnp/Roomie.xml",
|
||||
ssdpUSN: "uuid:$uuid::urn:roomieremote-com:device:roomie:1",
|
||||
hub: hubId,
|
||||
verified: true,
|
||||
name: "Simple Sync $manualIPAddress"]
|
||||
|
||||
state.agents[uuid] = manualAgent
|
||||
|
||||
addOrUpdateAgent(state.agents[uuid])
|
||||
|
||||
dynamicPage(name: "verifyManualEntry", title: "Manual Configuration Complete", nextPage: "", install:true, uninstall:true) {
|
||||
section("")
|
||||
{
|
||||
paragraph("Tap Done to complete the installation process.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def discoverAgents()
|
||||
{
|
||||
def urn = getURN()
|
||||
|
||||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery $urn", physicalgraph.device.Protocol.LAN))
|
||||
}
|
||||
|
||||
def agentsDiscovered()
|
||||
{
|
||||
def gAgents = getAgents()
|
||||
def agents = gAgents.findAll { it?.value?.verified == true }
|
||||
def map = [:]
|
||||
agents.each
|
||||
{
|
||||
map["${it.value.uuid}"] = it.value.name
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
def getAgents()
|
||||
{
|
||||
if (!state.agents)
|
||||
{
|
||||
state.agents = [:]
|
||||
}
|
||||
|
||||
state.agents
|
||||
}
|
||||
|
||||
def installed()
|
||||
{
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize()
|
||||
{
|
||||
if (state.subscribe)
|
||||
{
|
||||
unsubscribe()
|
||||
state.subscribe = false
|
||||
}
|
||||
|
||||
if (selectedAgent)
|
||||
{
|
||||
addOrUpdateAgent(state.agents[selectedAgent])
|
||||
}
|
||||
}
|
||||
|
||||
def addOrUpdateAgent(agent)
|
||||
{
|
||||
def children = getChildDevices()
|
||||
def dni = agent.ip + ":" + agent.port
|
||||
def found = false
|
||||
|
||||
children.each
|
||||
{
|
||||
if ((it.getDeviceDataByName("mac") == agent.mac))
|
||||
{
|
||||
found = true
|
||||
|
||||
if (it.getDeviceNetworkId() != dni)
|
||||
{
|
||||
it.setDeviceNetworkId(dni)
|
||||
}
|
||||
}
|
||||
else if (it.getDeviceNetworkId() == dni)
|
||||
{
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
addChildDevice("roomieremote-agent", "Simple Sync", dni, agent.hub, [label: "Simple Sync"])
|
||||
}
|
||||
}
|
||||
|
||||
def locationHandler(evt)
|
||||
{
|
||||
def description = evt?.description
|
||||
def urn = getURN()
|
||||
def hub = evt?.hubId
|
||||
def parsedEvent = parseEventMessage(description)
|
||||
|
||||
parsedEvent?.putAt("hub", hub)
|
||||
|
||||
//SSDP DISCOVERY EVENTS
|
||||
if (parsedEvent?.ssdpTerm?.contains(urn))
|
||||
{
|
||||
def agent = parsedEvent
|
||||
def ip = convertHexToIP(agent.ip)
|
||||
def agents = getAgents()
|
||||
|
||||
agent.verified = true
|
||||
agent.name = "Simple Sync $ip"
|
||||
|
||||
if (!agents[agent.uuid])
|
||||
{
|
||||
state.agents[agent.uuid] = agent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def parseEventMessage(String description)
|
||||
{
|
||||
def event = [:]
|
||||
def parts = description.split(',')
|
||||
|
||||
parts.each
|
||||
{ part ->
|
||||
part = part.trim()
|
||||
if (part.startsWith('devicetype:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
event.devicetype = valueString
|
||||
}
|
||||
else if (part.startsWith('mac:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.mac = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('networkAddress:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.ip = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('deviceAddress:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.port = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpPath:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.ssdpPath = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpUSN:'))
|
||||
{
|
||||
part -= "ssdpUSN:"
|
||||
def valueString = part.trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.ssdpUSN = valueString
|
||||
|
||||
def uuid = getUUIDFromUSN(valueString)
|
||||
|
||||
if (uuid)
|
||||
{
|
||||
event.uuid = uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpTerm:'))
|
||||
{
|
||||
part -= "ssdpTerm:"
|
||||
def valueString = part.trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.ssdpTerm = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('headers'))
|
||||
{
|
||||
part -= "headers:"
|
||||
def valueString = part.trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.headers = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('body'))
|
||||
{
|
||||
part -= "body:"
|
||||
def valueString = part.trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.body = valueString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event
|
||||
}
|
||||
|
||||
def getURN()
|
||||
{
|
||||
return "urn:roomieremote-com:device:roomie:1"
|
||||
}
|
||||
|
||||
def getUUIDFromUSN(usn)
|
||||
{
|
||||
def parts = usn.split(":")
|
||||
|
||||
for (int i = 0; i < parts.size(); ++i)
|
||||
{
|
||||
if (parts[i] == "uuid")
|
||||
{
|
||||
return parts[i + 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def String convertHexToIP(hex)
|
||||
{
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
def Integer convertHexToInt(hex)
|
||||
{
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
def String convertToHexString(n)
|
||||
{
|
||||
String hex = String.format("%X", n.toInteger())
|
||||
}
|
||||
|
||||
def String convertIPToHexString(ipString)
|
||||
{
|
||||
String hex = ipString.tokenize(".").collect {
|
||||
String.format("%02X", it.toInteger())
|
||||
}.join()
|
||||
}
|
||||
|
||||
def Boolean canInstallLabs()
|
||||
{
|
||||
return hasAllHubsOver("000.011.00603")
|
||||
}
|
||||
|
||||
def Boolean hasAllHubsOver(String desiredFirmware)
|
||||
{
|
||||
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
||||
}
|
||||
|
||||
def List getRealHubFirmwareVersions()
|
||||
{
|
||||
return location.hubs*.firmwareVersionString.findAll { it }
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
/**
|
||||
* Simple Sync Trigger
|
||||
*
|
||||
* Copyright 2015 Roomie Remote, Inc.
|
||||
*
|
||||
* Date: 2015-09-22
|
||||
*/
|
||||
definition(
|
||||
name: "Simple Sync Trigger",
|
||||
namespace: "roomieremote-ratrigger",
|
||||
author: "Roomie Remote, Inc.",
|
||||
description: "Trigger Simple Control activities when certain actions take place in your home.",
|
||||
category: "My Apps",
|
||||
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
|
||||
|
||||
|
||||
preferences {
|
||||
page(name: "agentSelection", title: "Select your Simple Sync")
|
||||
page(name: "refreshActivities", title: "Updating list of Simple Sync activities")
|
||||
page(name: "control", title: "Run a Simple Control activity when something happens")
|
||||
page(name: "timeIntervalInput", title: "Only during a certain time", install: true, uninstall: true) {
|
||||
section {
|
||||
input "starting", "time", title: "Starting", required: false
|
||||
input "ending", "time", title: "Ending", required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def agentSelection()
|
||||
{
|
||||
if (agent)
|
||||
{
|
||||
state.refreshCount = 0
|
||||
}
|
||||
|
||||
dynamicPage(name: "agentSelection", title: "Select your Simple Sync", nextPage: "control", install: false, uninstall: true) {
|
||||
section {
|
||||
input "agent", "capability.mediaController", title: "Simple Sync", required: true, multiple: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def control()
|
||||
{
|
||||
def activities = agent.latestValue('activities')
|
||||
|
||||
if (!activities || !state.refreshCount)
|
||||
{
|
||||
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
|
||||
state.refreshCount = refreshCount + 1
|
||||
def refreshInterval = refreshCount == 0 ? 2 : 4
|
||||
|
||||
// Request activities every 5th attempt
|
||||
if((refreshCount % 5) == 0)
|
||||
{
|
||||
agent.getAllActivities()
|
||||
}
|
||||
|
||||
dynamicPage(name: "control", title: "Updating list of Simple Control activities", nextPage: "", refreshInterval: refreshInterval, install: false, uninstall: true) {
|
||||
section("") {
|
||||
paragraph "Retrieving activities from Simple Sync"
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dynamicPage(name: "control", title: "Run a Simple Control activity when something happens", nextPage: "timeIntervalInput", install: false, uninstall: true) {
|
||||
def anythingSet = anythingSet()
|
||||
if (anythingSet) {
|
||||
section("When..."){
|
||||
ifSet "motion", "capability.motionSensor", title: "Motion Detected", required: false, multiple: true
|
||||
ifSet "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true
|
||||
ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
|
||||
ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
|
||||
ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
|
||||
ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
|
||||
ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
|
||||
ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
|
||||
ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
|
||||
ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
|
||||
ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
|
||||
ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
|
||||
}
|
||||
}
|
||||
section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
|
||||
ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
|
||||
ifUnset "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true
|
||||
ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
|
||||
ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
|
||||
ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
|
||||
ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
|
||||
ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
|
||||
ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
|
||||
ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
|
||||
ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
|
||||
ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
|
||||
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
|
||||
}
|
||||
section("Run this activity"){
|
||||
input "activity", "enum", title: "Activity?", required: true, options: new groovy.json.JsonSlurper().parseText(activities ?: "[]").activities?.collect { ["${it.uuid}": it.name] }
|
||||
}
|
||||
|
||||
section("More options", hideable: true, hidden: true) {
|
||||
input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
|
||||
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
|
||||
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
|
||||
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
||||
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
|
||||
input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
|
||||
}
|
||||
section([mobileOnly:true]) {
|
||||
label title: "Assign a name", required: false
|
||||
mode title: "Set for specific mode(s)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private anythingSet() {
|
||||
for (name in ["motion","motionInactive","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","button1","triggerModes","timeOfDay"]) {
|
||||
if (settings[name]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private ifUnset(Map options, String name, String capability) {
|
||||
if (!settings[name]) {
|
||||
input(options, name, capability)
|
||||
}
|
||||
}
|
||||
|
||||
private ifSet(Map options, String name, String capability) {
|
||||
if (settings[name]) {
|
||||
input(options, name, capability)
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
subscribeToEvents()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
unschedule()
|
||||
subscribeToEvents()
|
||||
}
|
||||
|
||||
def subscribeToEvents() {
|
||||
log.trace "subscribeToEvents()"
|
||||
subscribe(app, appTouchHandler)
|
||||
subscribe(contact, "contact.open", eventHandler)
|
||||
subscribe(contactClosed, "contact.closed", eventHandler)
|
||||
subscribe(acceleration, "acceleration.active", eventHandler)
|
||||
subscribe(motion, "motion.active", eventHandler)
|
||||
subscribe(motionInactive, "motion.inactive", eventHandler)
|
||||
subscribe(mySwitch, "switch.on", eventHandler)
|
||||
subscribe(mySwitchOff, "switch.off", eventHandler)
|
||||
subscribe(arrivalPresence, "presence.present", eventHandler)
|
||||
subscribe(departurePresence, "presence.not present", eventHandler)
|
||||
subscribe(button1, "button.pushed", eventHandler)
|
||||
|
||||
if (triggerModes) {
|
||||
subscribe(location, modeChangeHandler)
|
||||
}
|
||||
|
||||
if (timeOfDay) {
|
||||
schedule(timeOfDay, scheduledTimeHandler)
|
||||
}
|
||||
}
|
||||
|
||||
def eventHandler(evt) {
|
||||
if (allOk) {
|
||||
def lastTime = state[frequencyKey(evt)]
|
||||
if (oncePerDayOk(lastTime)) {
|
||||
if (frequency) {
|
||||
if (lastTime == null || now() - lastTime >= frequency * 60000) {
|
||||
startActivity(evt)
|
||||
}
|
||||
else {
|
||||
log.debug "Not taking action because $frequency minutes have not elapsed since last action"
|
||||
}
|
||||
}
|
||||
else {
|
||||
startActivity(evt)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.debug "Not taking action because it was already taken today"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def modeChangeHandler(evt) {
|
||||
log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
|
||||
if (evt.value in triggerModes) {
|
||||
eventHandler(evt)
|
||||
}
|
||||
}
|
||||
|
||||
def scheduledTimeHandler() {
|
||||
eventHandler(null)
|
||||
}
|
||||
|
||||
def appTouchHandler(evt) {
|
||||
startActivity(evt)
|
||||
}
|
||||
|
||||
private startActivity(evt) {
|
||||
agent.startActivity(activity)
|
||||
|
||||
if (frequency) {
|
||||
state.lastActionTimeStamp = now()
|
||||
}
|
||||
}
|
||||
|
||||
private frequencyKey(evt) {
|
||||
//evt.deviceId ?: evt.value
|
||||
"lastActionTimeStamp"
|
||||
}
|
||||
|
||||
private dayString(Date date) {
|
||||
def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
|
||||
if (location.timeZone) {
|
||||
df.setTimeZone(location.timeZone)
|
||||
}
|
||||
else {
|
||||
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
|
||||
}
|
||||
df.format(date)
|
||||
}
|
||||
|
||||
private oncePerDayOk(Long lastTime) {
|
||||
def result = true
|
||||
if (oncePerDay) {
|
||||
result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
|
||||
log.trace "oncePerDayOk = $result"
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
// TODO - centralize somehow
|
||||
private getAllOk() {
|
||||
modeOk && daysOk && timeOk
|
||||
}
|
||||
|
||||
private getModeOk() {
|
||||
def result = !modes || modes.contains(location.mode)
|
||||
log.trace "modeOk = $result"
|
||||
result
|
||||
}
|
||||
|
||||
private getDaysOk() {
|
||||
def result = true
|
||||
if (days) {
|
||||
def df = new java.text.SimpleDateFormat("EEEE")
|
||||
if (location.timeZone) {
|
||||
df.setTimeZone(location.timeZone)
|
||||
}
|
||||
else {
|
||||
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
|
||||
}
|
||||
def day = df.format(new Date())
|
||||
result = days.contains(day)
|
||||
}
|
||||
log.trace "daysOk = $result"
|
||||
result
|
||||
}
|
||||
|
||||
private getTimeOk() {
|
||||
def result = true
|
||||
if (starting && ending) {
|
||||
def currTime = now()
|
||||
def start = timeToday(starting).time
|
||||
def stop = timeToday(ending).time
|
||||
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
||||
}
|
||||
log.trace "timeOk = $result"
|
||||
result
|
||||
}
|
||||
|
||||
private hhmm(time, fmt = "h:mm a")
|
||||
{
|
||||
def t = timeToday(time, location.timeZone)
|
||||
def f = new java.text.SimpleDateFormat(fmt)
|
||||
f.setTimeZone(location.timeZone ?: timeZone(time))
|
||||
f.format(t)
|
||||
}
|
||||
|
||||
private timeIntervalLabel()
|
||||
{
|
||||
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
|
||||
}
|
||||
@@ -1,774 +0,0 @@
|
||||
/**
|
||||
* Simple Control
|
||||
*
|
||||
* Copyright 2015 Roomie Remote, Inc.
|
||||
*
|
||||
* Date: 2015-09-22
|
||||
*/
|
||||
|
||||
definition(
|
||||
name: "Simple Control",
|
||||
namespace: "roomieremote-roomieconnect",
|
||||
author: "Roomie Remote, Inc.",
|
||||
description: "Integrate SmartThings with your Simple Control activities.",
|
||||
category: "My Apps",
|
||||
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
|
||||
|
||||
preferences()
|
||||
{
|
||||
section("Allow Simple Control to Monitor and Control These Things...")
|
||||
{
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
|
||||
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
|
||||
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
|
||||
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
|
||||
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
|
||||
}
|
||||
|
||||
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
|
||||
page(name:"agentDiscovery", title:"Simple Sync Discovery", content:"agentDiscovery", refreshTimeout:5)
|
||||
page(name:"manualAgentEntry")
|
||||
page(name:"verifyManualEntry")
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/devices") {
|
||||
action: [
|
||||
GET: "getDevices"
|
||||
]
|
||||
}
|
||||
path("/:deviceType/devices") {
|
||||
action: [
|
||||
GET: "getDevices",
|
||||
POST: "handleDevicesWithIDs"
|
||||
]
|
||||
}
|
||||
path("/device/:deviceType/:id") {
|
||||
action: [
|
||||
GET: "getDevice",
|
||||
POST: "updateDevice"
|
||||
]
|
||||
}
|
||||
path("/subscriptions") {
|
||||
action: [
|
||||
GET: "listSubscriptions",
|
||||
POST: "addSubscription", // {"deviceId":"xxx", "attributeName":"xxx","callbackUrl":"http://..."}
|
||||
DELETE: "removeAllSubscriptions"
|
||||
]
|
||||
}
|
||||
path("/subscriptions/:id") {
|
||||
action: [
|
||||
DELETE: "removeSubscription"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private getAllDevices()
|
||||
{
|
||||
//log.debug("getAllDevices()")
|
||||
([] + switches + locks + thermostats + imageCaptures + relaySwitches + doorControls + colorControls + musicPlayers + speechSynthesizers + switchLevels + indicators + mediaControllers + tones + tvs + alarms + valves + motionSensors + presenceSensors + beacons + pushButtons + smokeDetectors + coDetectors + contactSensors + accelerationSensors + energyMeters + powerMeters + lightSensors + humiditySensors + temperatureSensors + speechRecognizers + stepSensors + touchSensors)?.findAll()?.unique { it.id }
|
||||
}
|
||||
|
||||
def getDevices()
|
||||
{
|
||||
//log.debug("getDevices, params: ${params}")
|
||||
allDevices.collect {
|
||||
//log.debug("device: ${it}")
|
||||
deviceItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
def getDevice()
|
||||
{
|
||||
//log.debug("getDevice, params: ${params}")
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (!device)
|
||||
{
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceItem(device)
|
||||
}
|
||||
}
|
||||
|
||||
def handleDevicesWithIDs()
|
||||
{
|
||||
//log.debug("handleDevicesWithIDs, params: ${params}")
|
||||
def data = request.JSON
|
||||
def ids = data?.ids?.findAll()?.unique()
|
||||
//log.debug("ids: ${ids}")
|
||||
def command = data?.command
|
||||
def arguments = data?.arguments
|
||||
def type = params?.deviceType
|
||||
//log.debug("device type: ${type}")
|
||||
if (command)
|
||||
{
|
||||
def statusCode = 404
|
||||
//log.debug("command ${command}, arguments ${arguments}")
|
||||
for (devId in ids)
|
||||
{
|
||||
def device = allDevices.find { it.id == devId }
|
||||
//log.debug("device: ${device}")
|
||||
// Check if we have a device that responds to the specified command
|
||||
if (validateCommand(device, type, command)) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
}
|
||||
else {
|
||||
device."$command"()
|
||||
}
|
||||
statusCode = 200
|
||||
} else {
|
||||
statusCode = 403
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
else
|
||||
{
|
||||
ids.collect {
|
||||
def currentId = it
|
||||
def device = allDevices.find { it.id == currentId }
|
||||
if (device)
|
||||
{
|
||||
deviceItem(device)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private deviceItem(device) {
|
||||
[
|
||||
id: device.id,
|
||||
label: device.displayName,
|
||||
currentState: device.currentStates,
|
||||
capabilities: device.capabilities?.collect {[
|
||||
name: it.name
|
||||
]},
|
||||
attributes: device.supportedAttributes?.collect {[
|
||||
name: it.name,
|
||||
dataType: it.dataType,
|
||||
values: it.values
|
||||
]},
|
||||
commands: device.supportedCommands?.collect {[
|
||||
name: it.name,
|
||||
arguments: it.arguments
|
||||
]},
|
||||
type: [
|
||||
name: device.typeName,
|
||||
author: device.typeAuthor
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
def updateDevice()
|
||||
{
|
||||
//log.debug("updateDevice, params: ${params}")
|
||||
def data = request.JSON
|
||||
def command = data?.command
|
||||
def arguments = data?.arguments
|
||||
def type = params?.deviceType
|
||||
//log.debug("device type: ${type}")
|
||||
|
||||
//log.debug("updateDevice, params: ${params}, request: ${data}")
|
||||
if (!command) {
|
||||
render status: 400, data: '{"msg": "command is required"}'
|
||||
} else {
|
||||
def statusCode = 404
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (device) {
|
||||
// Check if we have a device that responds to the specified command
|
||||
if (validateCommand(device, type, command)) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
}
|
||||
else {
|
||||
device."$command"()
|
||||
}
|
||||
statusCode = 200
|
||||
} else {
|
||||
statusCode = 403
|
||||
}
|
||||
}
|
||||
|
||||
def responseData = "{}"
|
||||
switch (statusCode)
|
||||
{
|
||||
case 403:
|
||||
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||
break
|
||||
case 404:
|
||||
responseData = '{"msg": "Device not found"}'
|
||||
break
|
||||
}
|
||||
render status: statusCode, data: responseData
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
//log.debug("validateCommand ${command}")
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
//log.debug("capabilityCommands: ${capabilityCommands}")
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
//log.debug("currentDeviceCapability: ${currentDeviceCapability}")
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
case "thermostats":
|
||||
return "Thermostat"
|
||||
case "doorControls":
|
||||
return "Door Control"
|
||||
case "colorControls":
|
||||
return "Color Control"
|
||||
case "musicPlayers":
|
||||
return "Music Player"
|
||||
case "switchLevels":
|
||||
return "Switch Level"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def listSubscriptions()
|
||||
{
|
||||
//log.debug "listSubscriptions()"
|
||||
app.subscriptions?.findAll { it.deviceId }?.collect {
|
||||
def deviceInfo = state[it.deviceId]
|
||||
def response = [
|
||||
id: it.id,
|
||||
deviceId: it.deviceId,
|
||||
attributeName: it.data,
|
||||
handler: it.handler
|
||||
]
|
||||
//if (!selectedAgent) {
|
||||
response.callbackUrl = deviceInfo?.callbackUrl
|
||||
//}
|
||||
response
|
||||
} ?: []
|
||||
}
|
||||
|
||||
def addSubscription() {
|
||||
def data = request.JSON
|
||||
def attribute = data.attributeName
|
||||
def callbackUrl = data.callbackUrl
|
||||
|
||||
//log.debug "addSubscription, params: ${params}, request: ${data}"
|
||||
if (!attribute) {
|
||||
render status: 400, data: '{"msg": "attributeName is required"}'
|
||||
} else {
|
||||
def device = allDevices.find { it.id == data.deviceId }
|
||||
if (device) {
|
||||
//if (!selectedAgent) {
|
||||
//log.debug "Adding callbackUrl: $callbackUrl"
|
||||
state[device.id] = [callbackUrl: callbackUrl]
|
||||
//}
|
||||
//log.debug "Adding subscription"
|
||||
def subscription = subscribe(device, attribute, deviceHandler)
|
||||
if (!subscription || !subscription.eventSubscription) {
|
||||
//log.debug("subscriptions: ${app.subscriptions}")
|
||||
//for (sub in app.subscriptions)
|
||||
//{
|
||||
//log.debug("subscription.id ${sub.id} subscription.handler ${sub.handler} subscription.deviceId ${sub.deviceId}")
|
||||
//log.debug(sub.properties.collect{it}.join('\n'))
|
||||
//}
|
||||
subscription = app.subscriptions?.find { it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' }
|
||||
}
|
||||
|
||||
def response = [
|
||||
id: subscription.id,
|
||||
deviceId: subscription.device?.id,
|
||||
attributeName: subscription.data,
|
||||
handler: subscription.handler
|
||||
]
|
||||
//if (!selectedAgent) {
|
||||
response.callbackUrl = callbackUrl
|
||||
//}
|
||||
response
|
||||
} else {
|
||||
render status: 400, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def removeSubscription()
|
||||
{
|
||||
def subscription = app.subscriptions?.find { it.id == params.id }
|
||||
def device = subscription?.device
|
||||
|
||||
//log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}"
|
||||
if (device) {
|
||||
//log.debug "Removing subscription for device: ${device.id}"
|
||||
state.remove(device.id)
|
||||
unsubscribe(device)
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
}
|
||||
|
||||
def removeAllSubscriptions()
|
||||
{
|
||||
for (sub in app.subscriptions)
|
||||
{
|
||||
//log.debug("Subscription: ${sub}")
|
||||
//log.debug(sub.properties.collect{it}.join('\n'))
|
||||
def handler = sub.handler
|
||||
def device = sub.device
|
||||
|
||||
if (device && handler == 'deviceHandler')
|
||||
{
|
||||
//log.debug(device.properties.collect{it}.join('\n'))
|
||||
//log.debug("Removing subscription for device: ${device}")
|
||||
state.remove(device.id)
|
||||
unsubscribe(device)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def deviceHandler(evt) {
|
||||
def deviceInfo = state[evt.deviceId]
|
||||
//if (selectedAgent) {
|
||||
// sendToRoomie(evt, agentCallbackUrl)
|
||||
//} else if (deviceInfo) {
|
||||
if (deviceInfo)
|
||||
{
|
||||
if (deviceInfo.callbackUrl) {
|
||||
sendToRoomie(evt, deviceInfo.callbackUrl)
|
||||
} else {
|
||||
log.warn "No callbackUrl set for device: ${evt.deviceId}"
|
||||
}
|
||||
} else {
|
||||
log.warn "No subscribed device found for device: ${evt.deviceId}"
|
||||
}
|
||||
}
|
||||
|
||||
def sendToRoomie(evt, String callbackUrl) {
|
||||
def callback = new URI(callbackUrl)
|
||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||
sendHubCommand(new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: path,
|
||||
headers: [
|
||||
"Host": host,
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
))
|
||||
}
|
||||
|
||||
def mainPage()
|
||||
{
|
||||
if (canInstallLabs())
|
||||
{
|
||||
return agentDiscovery()
|
||||
}
|
||||
else
|
||||
{
|
||||
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"."""
|
||||
|
||||
return dynamicPage(name:"mainPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
|
||||
section("Upgrade")
|
||||
{
|
||||
paragraph "$upgradeNeeded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def agentDiscovery(params=[:])
|
||||
{
|
||||
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
|
||||
state.refreshCount = refreshCount + 1
|
||||
def refreshInterval = refreshCount == 0 ? 2 : 5
|
||||
|
||||
if (!state.subscribe)
|
||||
{
|
||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||
state.subscribe = true
|
||||
}
|
||||
|
||||
//ssdp request every fifth refresh
|
||||
if ((refreshCount % 5) == 0)
|
||||
{
|
||||
discoverAgents()
|
||||
}
|
||||
|
||||
def agentsDiscovered = agentsDiscovered()
|
||||
|
||||
return dynamicPage(name:"agentDiscovery", title:"Pair with Simple Sync", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
|
||||
section("Pair with Simple Sync")
|
||||
{
|
||||
input "selectedAgent", "enum", required:false, title:"Select Simple Sync\n(${agentsDiscovered.size() ?: 0} found)", multiple:false, options:agentsDiscovered
|
||||
href(name:"manualAgentEntry",
|
||||
title:"Manually Configure Simple Sync",
|
||||
required:false,
|
||||
page:"manualAgentEntry")
|
||||
}
|
||||
section("Allow Simple Control to Monitor and Control These Things...")
|
||||
{
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
|
||||
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
|
||||
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
|
||||
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
|
||||
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def manualAgentEntry()
|
||||
{
|
||||
dynamicPage(name:"manualAgentEntry", title:"Manually Configure Simple Sync", nextPage:"verifyManualEntry", install:false, uninstall:true) {
|
||||
section("Manually Configure Simple Sync")
|
||||
{
|
||||
paragraph "In the event that Simple Sync cannot be automatically discovered by your SmartThings hub, you may enter Simple Sync's IP address here."
|
||||
input(name: "manualIPAddress", type: "text", title: "IP Address", required: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def verifyManualEntry()
|
||||
{
|
||||
def hexIP = convertIPToHexString(manualIPAddress)
|
||||
def hexPort = convertToHexString(47147)
|
||||
def uuid = "593C03D2-1DA9-4CDB-A335-6C6DC98E56C3"
|
||||
def hubId = ""
|
||||
|
||||
for (hub in location.hubs)
|
||||
{
|
||||
if (hub.localIP != null)
|
||||
{
|
||||
hubId = hub.id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def manualAgent = [deviceType: "04",
|
||||
mac: "unknown",
|
||||
ip: hexIP,
|
||||
port: hexPort,
|
||||
ssdpPath: "/upnp/Roomie.xml",
|
||||
ssdpUSN: "uuid:$uuid::urn:roomieremote-com:device:roomie:1",
|
||||
hub: hubId,
|
||||
verified: true,
|
||||
name: "Simple Sync $manualIPAddress"]
|
||||
|
||||
state.agents[uuid] = manualAgent
|
||||
|
||||
addOrUpdateAgent(state.agents[uuid])
|
||||
|
||||
dynamicPage(name: "verifyManualEntry", title: "Manual Configuration Complete", nextPage: "", install:true, uninstall:true) {
|
||||
section("")
|
||||
{
|
||||
paragraph("Tap Done to complete the installation process.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def discoverAgents()
|
||||
{
|
||||
def urn = getURN()
|
||||
|
||||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery $urn", physicalgraph.device.Protocol.LAN))
|
||||
}
|
||||
|
||||
def agentsDiscovered()
|
||||
{
|
||||
def gAgents = getAgents()
|
||||
def agents = gAgents.findAll { it?.value?.verified == true }
|
||||
def map = [:]
|
||||
agents.each
|
||||
{
|
||||
map["${it.value.uuid}"] = it.value.name
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
def getAgents()
|
||||
{
|
||||
if (!state.agents)
|
||||
{
|
||||
state.agents = [:]
|
||||
}
|
||||
|
||||
state.agents
|
||||
}
|
||||
|
||||
def installed()
|
||||
{
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize()
|
||||
{
|
||||
if (state.subscribe)
|
||||
{
|
||||
unsubscribe()
|
||||
state.subscribe = false
|
||||
}
|
||||
|
||||
if (selectedAgent)
|
||||
{
|
||||
addOrUpdateAgent(state.agents[selectedAgent])
|
||||
}
|
||||
}
|
||||
|
||||
def addOrUpdateAgent(agent)
|
||||
{
|
||||
def children = getChildDevices()
|
||||
def dni = agent.ip + ":" + agent.port
|
||||
def found = false
|
||||
|
||||
children.each
|
||||
{
|
||||
if ((it.getDeviceDataByName("mac") == agent.mac))
|
||||
{
|
||||
found = true
|
||||
|
||||
if (it.getDeviceNetworkId() != dni)
|
||||
{
|
||||
it.setDeviceNetworkId(dni)
|
||||
}
|
||||
}
|
||||
else if (it.getDeviceNetworkId() == dni)
|
||||
{
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
addChildDevice("roomieremote-agent", "Simple Sync", dni, agent.hub, [label: "Simple Sync"])
|
||||
}
|
||||
}
|
||||
|
||||
def locationHandler(evt)
|
||||
{
|
||||
def description = evt?.description
|
||||
def urn = getURN()
|
||||
def hub = evt?.hubId
|
||||
def parsedEvent = parseEventMessage(description)
|
||||
|
||||
parsedEvent?.putAt("hub", hub)
|
||||
|
||||
//SSDP DISCOVERY EVENTS
|
||||
if (parsedEvent?.ssdpTerm?.contains(urn))
|
||||
{
|
||||
def agent = parsedEvent
|
||||
def ip = convertHexToIP(agent.ip)
|
||||
def agents = getAgents()
|
||||
|
||||
agent.verified = true
|
||||
agent.name = "Simple Sync $ip"
|
||||
|
||||
if (!agents[agent.uuid])
|
||||
{
|
||||
state.agents[agent.uuid] = agent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def parseEventMessage(String description)
|
||||
{
|
||||
def event = [:]
|
||||
def parts = description.split(',')
|
||||
|
||||
parts.each
|
||||
{ part ->
|
||||
part = part.trim()
|
||||
if (part.startsWith('devicetype:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
event.devicetype = valueString
|
||||
}
|
||||
else if (part.startsWith('mac:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.mac = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('networkAddress:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.ip = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('deviceAddress:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.port = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpPath:'))
|
||||
{
|
||||
def valueString = part.split(":")[1].trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.ssdpPath = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpUSN:'))
|
||||
{
|
||||
part -= "ssdpUSN:"
|
||||
def valueString = part.trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.ssdpUSN = valueString
|
||||
|
||||
def uuid = getUUIDFromUSN(valueString)
|
||||
|
||||
if (uuid)
|
||||
{
|
||||
event.uuid = uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('ssdpTerm:'))
|
||||
{
|
||||
part -= "ssdpTerm:"
|
||||
def valueString = part.trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.ssdpTerm = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('headers'))
|
||||
{
|
||||
part -= "headers:"
|
||||
def valueString = part.trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.headers = valueString
|
||||
}
|
||||
}
|
||||
else if (part.startsWith('body'))
|
||||
{
|
||||
part -= "body:"
|
||||
def valueString = part.trim()
|
||||
if (valueString)
|
||||
{
|
||||
event.body = valueString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event
|
||||
}
|
||||
|
||||
def getURN()
|
||||
{
|
||||
return "urn:roomieremote-com:device:roomie:1"
|
||||
}
|
||||
|
||||
def getUUIDFromUSN(usn)
|
||||
{
|
||||
def parts = usn.split(":")
|
||||
|
||||
for (int i = 0; i < parts.size(); ++i)
|
||||
{
|
||||
if (parts[i] == "uuid")
|
||||
{
|
||||
return parts[i + 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def String convertHexToIP(hex)
|
||||
{
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
def Integer convertHexToInt(hex)
|
||||
{
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
def String convertToHexString(n)
|
||||
{
|
||||
String hex = String.format("%X", n.toInteger())
|
||||
}
|
||||
|
||||
def String convertIPToHexString(ipString)
|
||||
{
|
||||
String hex = ipString.tokenize(".").collect {
|
||||
String.format("%02X", it.toInteger())
|
||||
}.join()
|
||||
}
|
||||
|
||||
def Boolean canInstallLabs()
|
||||
{
|
||||
return hasAllHubsOver("000.011.00603")
|
||||
}
|
||||
|
||||
def Boolean hasAllHubsOver(String desiredFirmware)
|
||||
{
|
||||
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
||||
}
|
||||
|
||||
def List getRealHubFirmwareVersions()
|
||||
{
|
||||
return location.hubs*.firmwareVersionString.findAll { it }
|
||||
}
|
||||
|
||||
|
||||
668
smartapps/smartthings/-.src/-.groovy
Normal file
668
smartapps/smartthings/-.src/-.groovy
Normal file
@@ -0,0 +1,668 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Navien Service Manager
|
||||
*
|
||||
* Author: sangju
|
||||
* Date: 2015-11-01
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "나비엔 스마트톡 연동",
|
||||
namespace: "smartthings",
|
||||
author: "나비엔 스마트톡",
|
||||
description: "SmartThings에서 나비엔 스마트톡의 온도조절기를 연결합니다.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Navien/navien.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Navien/navien@2x.png",
|
||||
singleInstance: true
|
||||
) {
|
||||
appSetting "clientId"
|
||||
appSetting "serverUrl"
|
||||
appSetting "bcd"
|
||||
}
|
||||
|
||||
preferences {
|
||||
page(name: "loginPage", title: "나비엔 스마트톡 등록")
|
||||
page(name: "navienAuth", title: "나비엔 스마트톡 등록")
|
||||
page(name: "navienDeviceList", title: "나비엔 스마트톡 등록", install: true )
|
||||
}
|
||||
|
||||
def refreshToken
|
||||
def authToken
|
||||
def userName
|
||||
|
||||
def loginPage(){
|
||||
log.debug "authPage()"
|
||||
|
||||
def showUninstall = username != null && password != null
|
||||
return dynamicPage(name: "loginPage", title: "경동나비엔", nextPage:"navienAuth", uninstall:false) {
|
||||
section("나비엔 스마트톡 등록"){
|
||||
input "username", "text", title: "나비엔 스마트톡 아이디", required: true, autoCorrect:false
|
||||
input "password", "password", title: "나비엔 스마트톡 패스워드", required: true, autoCorrect:false
|
||||
}
|
||||
//section("To use Navien, SmartThings encrypts and securely stores your Navien credentials.") {}
|
||||
}
|
||||
}
|
||||
|
||||
def navienAuth(){
|
||||
log.debug "navienAuth()"
|
||||
|
||||
def loginResult = forceLogin()
|
||||
if(loginResult.success)
|
||||
{
|
||||
return dynamicPage(name: "navienAuth", title: "나비엔 스마트톡 인증", nextPage:"navienDeviceList", uninstall:false) {
|
||||
section(){
|
||||
paragraph "나비엔 스마트톡 인증 성공"
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return dynamicPage(name: "navienAuth", title: "나비엔 스마트톡 인증", nextPage:null, uninstall:false) {
|
||||
section("Login failed"){
|
||||
paragraph "나비엔 스마트톡 인증 실패"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def navienDeviceList(){
|
||||
log.debug "navienDeviceList()"
|
||||
|
||||
def connectResult = navienConnecting()
|
||||
def p
|
||||
if(connectResult.success)
|
||||
{
|
||||
statusSetting(state.status)
|
||||
|
||||
def stats = getNavienThermostats()
|
||||
log.debug "device list: $stats"
|
||||
|
||||
p = dynamicPage(name: "navienDeviceList", title: "나비엔 스마트톡 선택", install:true) {
|
||||
section(""){
|
||||
paragraph "나비엔 스마트톡 계정에서 사용할 수 있는 온도 조절 장치의 목록을 확인하고 SmartTings에 연결하려는 목록을 선택하려면 아래에서 선택해 주세요."
|
||||
input(name: "thermostats", title:"", type: "enum", required:true, multiple:true, description: "Tap to choose", metadata:[values:stats])
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p = dynamicPage(name: "navienDeviceList", title: "나비엔 스마트톡 선택", nextPage:null, uninstall:false) {
|
||||
section("나비엔 스마트톡 온도 조절 장치의 연결 상태를 확인하시기 바랍니다."){
|
||||
paragraph ""
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug "list p: $p"
|
||||
return p
|
||||
}
|
||||
|
||||
def getNavienThermostats() {
|
||||
def stats = [:]
|
||||
def dni = [getChildName()].join('.')
|
||||
|
||||
if(state.boilerType == "01") stats[dni] = "스마트톡"
|
||||
else if(state.boilerType == "02") stats[dni] = "콘덴싱톡"
|
||||
else stats[dni] = "----"
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def uninstalled() {
|
||||
def devices = getChildDevices()
|
||||
if(devices != null) log.trace "deleting ${devices.size()} device"
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
log.debug "initialize"
|
||||
|
||||
def d = getChildDevice(getChildName())
|
||||
|
||||
if(!d)
|
||||
{
|
||||
d = addChildDevice(getChildNamespace(), getChildName(), getChildName())
|
||||
log.debug "created ${d.displayName} with id $dni"
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug "found ${d.displayName} with id $dni already exists"
|
||||
}
|
||||
|
||||
def devices = d
|
||||
|
||||
log.debug "created ${devices.size()} thermostats"
|
||||
|
||||
def delete
|
||||
// Delete any that are no longer in settings
|
||||
|
||||
if(!thermostats)
|
||||
{
|
||||
log.debug "If delete thermostats"
|
||||
delete = getAllChildDevices()
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug "Else delete thermostats"
|
||||
if(it != null) delete = getChildDevices().findAll { !thermostats.contains(it.deviceNetworkId) }
|
||||
}
|
||||
|
||||
if(delete != null) log.debug "deleting ${delete.size()} thermostats"
|
||||
if(it != null) delete.each { deleteChildDevice(it.deviceNetworkId) }
|
||||
|
||||
atomicState.thermostatData = [:]
|
||||
|
||||
pollHandler()
|
||||
}
|
||||
|
||||
def pollHandler() {
|
||||
log.debug ("pollHandler.")
|
||||
pollChildren()
|
||||
|
||||
atomicState.thermostats.each {stat ->
|
||||
def dni = stat.key
|
||||
|
||||
log.debug ("DNI = ${dni}")
|
||||
debugEvent ("DNI = ${dni}")
|
||||
|
||||
def d = getChildDevice(dni)
|
||||
if(d)
|
||||
{
|
||||
log.debug ("Found Child Device.")
|
||||
debugEvent ("Found Child Device.")
|
||||
debugEvent("Event Data before generate event call = ${stat}")
|
||||
|
||||
d.generateEvent(atomicState.thermostats[dni].data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def pollChildren()
|
||||
{
|
||||
log.trace "polling children"
|
||||
|
||||
def pollParams = [
|
||||
uri: getServerUrl()+"/api/SmartTokApi?bcd="+getBCD()+"&mid=${state.mid}&did=36&subid=1&cmd=01&data=0",
|
||||
headers: ["Authorization": "Bearer ${state.authToken}"]
|
||||
]
|
||||
|
||||
log.trace "Before HTTPGET to navien."
|
||||
def jsonData
|
||||
try{
|
||||
httpGet(pollParams) { resp ->
|
||||
if (resp.status == 200)
|
||||
{
|
||||
log.debug "poll results returned"
|
||||
//atomicState.thermostats = resp.data.Status.inject([:]) { collector, stat ->
|
||||
atomicState.thermostats = "1".inject([:]) { collector, stat ->
|
||||
def dni = [getChildName()].join('.')
|
||||
|
||||
log.debug "updating dni $dni"
|
||||
|
||||
def data = statusSetting(resp.data.Status)
|
||||
|
||||
log.debug ("Event Data = ${data}")
|
||||
|
||||
collector[dni] = [data:data]
|
||||
return collector
|
||||
}
|
||||
|
||||
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error "polling children & got http status ${resp.status}"
|
||||
|
||||
//refresh the auth token
|
||||
if(resp.status == 400)
|
||||
{
|
||||
log.debug "Bad Request Description"
|
||||
}
|
||||
else if(resp.status == 401)
|
||||
{
|
||||
log.debug "Unauthorized Description"
|
||||
}
|
||||
else if(resp.status == 500)
|
||||
{
|
||||
log.debug "InternalServerError Description"
|
||||
atomicState.action = "pollChildren";
|
||||
refreshAuthToken()
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(all)
|
||||
{
|
||||
log.debug "___exception polling children: "
|
||||
//refreshAuthToken()
|
||||
}
|
||||
}
|
||||
|
||||
def getPollRateMillis() { return 2 * 60 * 1000 }
|
||||
|
||||
def pollChild( child )
|
||||
{
|
||||
log.debug "poll child"
|
||||
debugEvent ("poll child")
|
||||
def now = new Date().time
|
||||
log.debug "now ====> ${now}"
|
||||
|
||||
debugEvent ("Last Poll Millis = ${atomicState.lastPollMillis}")
|
||||
def last = atomicState.lastPollMillis ?: 0
|
||||
def next = last + pollRateMillis
|
||||
|
||||
log.debug "pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${atomicState.thermostats}"
|
||||
debugEvent ("pollChild( ${child.device.deviceNetworkId} ): $now > $next ?? w/ current state: ${atomicState.thermostats}")
|
||||
|
||||
// if( now > next )
|
||||
if( true ) // for now let's always poll/refresh
|
||||
{
|
||||
log.debug "polling children because $now > $next"
|
||||
debugEvent("polling children because $now > $next")
|
||||
|
||||
pollChildren()
|
||||
|
||||
log.debug "polled children and looking for ${child.device.deviceNetworkId} from ${atomicState.thermostats}"
|
||||
debugEvent ("polled children and looking for ${child.device.deviceNetworkId} from ${atomicState.thermostats}")
|
||||
|
||||
def currentTime = new Date().time
|
||||
debugEvent ("Current Time = ${currentTime}")
|
||||
atomicState.lastPollMillis = currentTime
|
||||
|
||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||
|
||||
if(!tData)
|
||||
{
|
||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
|
||||
|
||||
// TODO: flag device as in error state
|
||||
// child.errorState = true
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
tData.updated = currentTime
|
||||
|
||||
return tData.data
|
||||
}
|
||||
else if(atomicState.thermostats[child.device.deviceNetworkId] != null)
|
||||
{
|
||||
log.debug "not polling children, found child ${child.device.deviceNetworkId} "
|
||||
|
||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||
if(!tData.updated)
|
||||
{
|
||||
// we have pulled new data for this thermostat, but it has not asked us for it
|
||||
// track it and return the data
|
||||
tData.updated = new Date().time
|
||||
return tData.data
|
||||
}
|
||||
return null
|
||||
}
|
||||
else if(atomicState.thermostats[child.device.deviceNetworkId] == null)
|
||||
{
|
||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
|
||||
|
||||
// TODO: flag device as in error state
|
||||
// child.errorState = true
|
||||
|
||||
return null
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's not time to poll again and this thermostat already has its latest values
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
def childRequest( child, subid, cmd, data )
|
||||
{
|
||||
getControlSend(subid, cmd, data)
|
||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||
|
||||
if(!tData)
|
||||
{
|
||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
|
||||
return null
|
||||
}
|
||||
tData.updated = currentTime
|
||||
|
||||
return tData.data
|
||||
}
|
||||
|
||||
def getControlSend(subid, cmd, data)
|
||||
{
|
||||
log.trace "getParams"
|
||||
|
||||
def pollParams = [
|
||||
uri: getServerUrl()+"/api/SmartTokApi?bcd="+getBCD()+"&mid=${state.mid}&did=36&subid=${subid}&cmd=${cmd}&data=${data}",
|
||||
headers: ["Authorization": "Bearer ${state.authToken}"]
|
||||
]
|
||||
|
||||
log.trace "Before Control HTTPGET to navien."
|
||||
def jsonData
|
||||
try{
|
||||
httpGet(pollParams) { resp ->
|
||||
debugEvent ("Response (resp.data.Staus) : = ${resp.data.Staus}", true)
|
||||
if (resp.status == 200)
|
||||
{
|
||||
log.debug "poll results returned"
|
||||
//atomicState.thermostats = resp.data.Status.inject([:]) { collector, stat ->
|
||||
atomicState.thermostats = "1".inject([:]) { collector, stat ->
|
||||
def dni = [getChildName()].join('.')
|
||||
|
||||
log.debug "updating dni $dni"
|
||||
|
||||
def response = statusSetting(resp.data.Status)
|
||||
|
||||
log.debug ("Event Data = ${response}")
|
||||
|
||||
collector[dni] = [data:response]
|
||||
return collector
|
||||
}
|
||||
|
||||
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error "polling children & got http status ${resp.status}"
|
||||
|
||||
//refresh the auth token
|
||||
if(resp.status == 400)
|
||||
{
|
||||
log.debug "Bad Request Description"
|
||||
}
|
||||
else if(resp.status == 401)
|
||||
{
|
||||
log.debug "Unauthorized Description"
|
||||
}
|
||||
else if(resp.status == 500)
|
||||
{
|
||||
log.debug "InternalServerError Description"
|
||||
atomicState.action = "pollChildren";
|
||||
refreshAuthToken()
|
||||
}
|
||||
else
|
||||
{
|
||||
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(all)
|
||||
{
|
||||
log.debug "___exception polling children: "
|
||||
//refreshAuthToken()
|
||||
}
|
||||
}
|
||||
|
||||
private refreshAuthToken() {
|
||||
log.trace "refreshing auth token"
|
||||
|
||||
if(!atomicState.refreshToken) {
|
||||
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
|
||||
} else {
|
||||
def refreshParams = [
|
||||
uri: getServerUrl(),
|
||||
path: "/Token",
|
||||
headers: ['Content-Type': "application/x-www-form-urlencoded"],
|
||||
body: [grant_type: "refresh_token", refresh_token: "${state.refreshToken}"]
|
||||
]
|
||||
|
||||
log.debug refreshParams
|
||||
|
||||
try {
|
||||
def jsonMap
|
||||
httpPost(refreshParams) { resp ->
|
||||
|
||||
if(resp.status == 200) {
|
||||
log.debug "Token refreshed...calling saved RestAction now! ${resp}"
|
||||
|
||||
if(resp.data)
|
||||
{
|
||||
jsonMap = resp.data
|
||||
state.refreshToken = jsonMap.refresh_token
|
||||
state.authToken = jsonMap.access_token
|
||||
|
||||
if(atomicState.action && atomicState.action != "") {
|
||||
log.debug "Executing next action: ${atomicState.action}"
|
||||
"{atomicState.action}"()
|
||||
|
||||
//remove saved action
|
||||
atomicState.action = ""
|
||||
}
|
||||
}
|
||||
atomicState.action = ""
|
||||
}
|
||||
else
|
||||
{
|
||||
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e) {
|
||||
log.debug "caught exception refreshing auth token: " + e.getStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def statusSetting(status){
|
||||
/*
|
||||
log.debug "state.status ====> ${state.status}"
|
||||
log.debug "제품아이디 ====> ${state.status.substring(0, 16)}" // 제품아이디
|
||||
log.debug "보일러모델타입 ====> ${state.status.substring(26, 28)}" // 보일러모델타입
|
||||
log.debug "에러코드 ====> ${state.status.substring(32, 36)}" // 에러코드
|
||||
log.debug "온수설정온도 ====> ${state.status.substring(36, 38)}" // 온수설정온도
|
||||
log.debug "state.status ====> ${state.status.substring(38, 40)}" // 난방세기
|
||||
log.debug "state.status ====> ${state.status.substring(40, 42)}" // 옵션기능
|
||||
log.debug "작동모드 ====> ${state.status.substring(42, 44)}" // 작동모드
|
||||
log.debug "현재실내온도 ====> ${state.status.substring(44, 46)}" // 현재실내온도
|
||||
log.debug "실내난방설정온도 ====> ${state.status.substring(46, 48)}" // 실내난방설정온도
|
||||
log.debug "온돌난방설정온도 ====> ${state.status.substring(48, 50)}" // 온돌난방설정온도
|
||||
*/
|
||||
state.mid = status.substring(0, 16)
|
||||
state.boilerType = status.substring(26, 28)
|
||||
state.errorCode = status.substring(32, 36)
|
||||
state.hotWater = convertHexToInt(status.substring(36, 38))
|
||||
|
||||
def s = status.substring(42, 44)
|
||||
if(s == "01") state.thermostatStatus = "전원 OFF"
|
||||
else if(s == "02") state.thermostatStatus = "외출 ON"
|
||||
else if(s == "03") state.thermostatStatus = "실내난방"
|
||||
else if(s == "04") state.thermostatStatus = "온돌난방"
|
||||
else if(s == "05") state.thermostatStatus = "반복예약난방"
|
||||
else if(s == "06") state.thermostatStatus = "24시간예약난방"
|
||||
else if(s == "07") state.thermostatStatus = "간편예약난방"
|
||||
else if(s == "08") state.thermostatStatus = "온수전용"
|
||||
else if(s == "09") state.thermostatStatus = "빠른온수"
|
||||
else state.thermostatStatus = "---"
|
||||
|
||||
state.temperature = convertHexToInt(status.substring(44, 46))
|
||||
state.roomTemp = convertHexToInt(status.substring(46, 48))
|
||||
state.ondolTemp = convertHexToInt(status.substring(48, 50))
|
||||
|
||||
def data = [
|
||||
mid: state.mid,
|
||||
boilerType: state.boilerType,
|
||||
errorCode: state.errorCode,
|
||||
hotWater: state.hotWater,
|
||||
thermostatStatus: state.thermostatStatus,
|
||||
temperature: state.temperature,
|
||||
roomTemp: state.roomTemp,
|
||||
ondolTemp: state.ondolTemp
|
||||
]
|
||||
return data
|
||||
}
|
||||
|
||||
def navienConnecting(){
|
||||
log.debug "navienConnecting()"
|
||||
|
||||
def connectParams = [
|
||||
uri: getServerUrl()+"/api/SmartTokApi?bcd="+getBCD()+"&uid=${state.userName}&scd=2",
|
||||
headers: ["Authorization": "Bearer ${state.authToken}"]
|
||||
]
|
||||
|
||||
def result = [success:false]
|
||||
def jsonData
|
||||
|
||||
httpGet(connectParams) { resp ->
|
||||
if (resp.status == 200)
|
||||
{
|
||||
jsonData = resp.data
|
||||
result.success = true
|
||||
state.status = jsonData.Status
|
||||
}
|
||||
else if(resp.status == 400)
|
||||
{
|
||||
result.reason = "Bad Request"
|
||||
}
|
||||
else if(resp.status == 401)
|
||||
{
|
||||
result.reason = "Unauthorized"
|
||||
}
|
||||
else if(resp.status == 500)
|
||||
{
|
||||
result.reason = "Internal ServerError"
|
||||
}
|
||||
else
|
||||
{
|
||||
result.reason = "Bad Connect"
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private forceLogin(){
|
||||
log.debug "forceLogin()"
|
||||
|
||||
updateCookie(null)
|
||||
login()
|
||||
}
|
||||
|
||||
private updateCookie(String cookie){
|
||||
atomicState.cookie = cookie
|
||||
state.cookie = cookie
|
||||
}
|
||||
|
||||
private login(){
|
||||
if(getCookieValueIsValid())
|
||||
{
|
||||
return [success:true]
|
||||
}
|
||||
return doLogin()
|
||||
}
|
||||
|
||||
private doLogin() {
|
||||
log.debug "doLogin()"
|
||||
|
||||
def loginParams = [
|
||||
uri: getServerUrl(),
|
||||
path: "/Token",
|
||||
headers: ['Content-Type': "application/x-www-form-urlencoded"],
|
||||
body: [grant_type: "password", username: username, password: password]
|
||||
]
|
||||
|
||||
def result = [success:false]
|
||||
def jsonMap
|
||||
|
||||
try
|
||||
{
|
||||
httpPost(loginParams) { resp ->
|
||||
if (resp.status == 200 && resp.headers.'Content-Type'.contains("application/json"))
|
||||
{
|
||||
log.debug "login 200 json headers: " + resp.headers.collect { "${it.name}:${it.value}" }
|
||||
def cookie = resp?.headers?.'Set-Cookie'?.split(";")?.getAt(0)
|
||||
if (cookie) {
|
||||
log.debug "login setting cookie to $cookie"
|
||||
updateCookie(cookie)
|
||||
result.success = true
|
||||
|
||||
jsonMap = resp.data
|
||||
state.refreshToken = jsonMap.refresh_token
|
||||
state.authToken = jsonMap.access_token
|
||||
state.userName = jsonMap.userName
|
||||
}
|
||||
else
|
||||
{
|
||||
// ERROR: any more information we can give?
|
||||
result.reason = "Bad login"
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.reason = "Bad login"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(groovyx.net.http.HttpResponseException hre)
|
||||
{
|
||||
result.reason = "Exception"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private Boolean getCookieValueIsValid()
|
||||
{
|
||||
// TODO: make a call with the cookie to verify that it works
|
||||
return getCookieValue()
|
||||
}
|
||||
|
||||
private getCookieValue(){
|
||||
state.cookie
|
||||
}
|
||||
|
||||
def getChildNamespace() { "smartthings" }
|
||||
def getChildName() { "Navien Room Controller" }
|
||||
|
||||
def getChildDeviceIdsString()
|
||||
{
|
||||
log.debug "thermostats ====> ${thermostats}"
|
||||
return thermostats.collect { it.split(/\./).last() }.join(',')
|
||||
}
|
||||
|
||||
def getServerUrl() { return appSettings.serverUrl }
|
||||
def getSmartThingsClientId() { return appSettings.clientId }
|
||||
def getBCD() { return appSettings.bcd }
|
||||
|
||||
def debugEvent(message, displayEvent = true) {
|
||||
|
||||
def results = [
|
||||
name: "appdebug",
|
||||
descriptionText: message,
|
||||
displayed: displayEvent
|
||||
]
|
||||
log.debug "Generating AppDebug Event: ${results}"
|
||||
sendEvent (results)
|
||||
}
|
||||
|
||||
private convertHexToInt(hex) {
|
||||
return (Integer.parseInt(hex,16) / 2)
|
||||
}
|
||||
@@ -353,7 +353,7 @@ def onLocation(evt) {
|
||||
}
|
||||
else if (
|
||||
lanEvent.headers && lanEvent.body &&
|
||||
lanEvent.headers."content-type"?.contains("xml")
|
||||
lanEvent.headers."content-type".contains("xml")
|
||||
)
|
||||
{
|
||||
def parsers = getParsers()
|
||||
|
||||
@@ -297,8 +297,8 @@ private getTimeOk() {
|
||||
def result = true
|
||||
if (starting && ending) {
|
||||
def currTime = now()
|
||||
def start = timeToday(starting, location.timeZone).time
|
||||
def stop = timeToday(ending, location.timeZone).time
|
||||
def start = timeToday(starting).time
|
||||
def stop = timeToday(ending).time
|
||||
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
||||
}
|
||||
log.trace "timeOk = $result"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user