Compare commits

..

4 Commits

Author SHA1 Message Date
Vinay Rao
62a965d90b Merge pull request #599 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-03-10 Release
2016-03-10 16:26:26 -08:00
Vinay Rao
515b268374 Merge pull request #549 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-02-25 Release
2016-02-26 10:51:42 -08:00
Vinay Rao
a103d437c2 Merge pull request #523 from SmartThingsCommunity/staging
Deploy to production 2/18
2016-02-18 09:28:02 -08:00
Vinay Rao
bdd88deb99 Merge pull request #504 from SmartThingsCommunity/staging
Merging changes from staging to production
2016-02-11 22:40:38 -08:00
46 changed files with 547 additions and 2716 deletions

View File

@@ -52,9 +52,6 @@ hipchatShareFile {
hipchatSendNotification { hipchatSendNotification {
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown' String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
message = "Began executable deploy of SmartThingsPublic(${branch})." message = "Began executable deploy of SmartThingsPublic(${branch})."
if (branch == 'master') {
message += ' (dev shards)'
}
color = branch == 'master' ? 'yellow' : 'red' color = branch == 'master' ? 'yellow' : 'red'
notify = true notify = true
} }

View File

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

View File

@@ -1,254 +0,0 @@
/**
* Fibaro Wall Plug 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 Wall Plug ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
capability "Actuator"
capability "Configuration"
capability "Energy Meter"
capability "Power Meter"
capability "Refresh"
capability "Sensor"
capability "Switch"
command "reset"
fingerprint deviceId: "0x1001", inClusters: "0x5E, 0x22, 0x85, 0x59, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x32, 0x8E, 0x71, 0x73, 0x98, 0x31, 0x25, 0x86", outClusters: ""
}
simulator {
}
tiles(scale: 2) {
multiAttributeTile(name:"FGWP", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
tileAttribute("device.switch", key:"PRIMARY_CONTROL") {
attributeState("on", label: '${name}', action: "switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821")
attributeState("off", label: '${name}', action: "switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff")
}
tileAttribute("device.power", key:"SECONDARY_CONTROL") {
attributeState("default", label:'${currentValue} W', backgroundColor:"#ffffff")
}
}
valueTile("energy", "device.energy", width: 2, height: 2) {
state "default", label:'${currentValue} kWh'
}
standardTile("reset", "device.energy", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'reset kWh', action:"reset"
}
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "FGWP"
details(["FGWP", "energy", "reset", "refresh"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parsing '${description}'"
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, [0x25: 1, 0x31: 5, 0x32: 1, 0x5A: 1, 0x71: 3, 0x72: 2, 0x86: 1])
if (cmd) {
log.debug "Parsed '${cmd}'"
zwaveEvent(cmd)
}
}
}
//security
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
def encapsulatedCommand = cmd.encapsulatedCommand([0x25: 1, 0x5A: 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, 0x32: 1, 0x71: 3, 0x72: 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.meterv1.MeterReport cmd) {
if (cmd.scale == 0) {
createEvent(name: "energy", value: cmd.scaledMeterValue, unit: "kWh")
} else if (cmd.scale == 2) {
createEvent(name: "power", value: Math.round(cmd.scaledMeterValue), unit: "W")
}
}
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 = [ displayed: true ]
if (cmd.sensorType == 4) {
createEvent(name: "power", value: Math.round(cmd.scaledSensorValue), unit: "W")
}
}
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
createEvent(name: "switch", value: cmd.value ? "on" : "off", type: "digital")
}
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
log.info "${device.displayName}: received command: $cmd - device has reset itself"
}
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd)
{
if (cmd.notificationType == 0x08) {
if (cmd.event == 0x06) {
createEvent(descriptionText: "Warning: $device.displayName detected over-current", isStateChange: true)
} else if (cmd.event == 0x08) {
createEvent(descriptionText: "Warning: $device.displayName detected over-load", isStateChange: true)
}
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.debug "$device.displayName: Unhandled: $cmd"
[:]
}
// handle commands
def configure() {
log.debug "Executing 'configure'"
def cmds = []
cmds += zwave.manufacturerSpecificV1.manufacturerSpecificGet()
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
cmds += zwave.versionV1.versionGet()
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId])
cmds += zwave.meterV2.meterGet(scale:0)
cmds += zwave.meterV2.meterGet(scale:2)
cmds += zwave.switchBinaryV1.switchBinaryGet()
encapSequence(cmds, 500)
}
def refresh() {
log.debug "Executing 'refresh'"
def cmds = []
cmds += zwave.meterV2.meterGet(scale:0)
cmds += zwave.meterV2.meterGet(scale:2)
cmds += zwave.switchBinaryV1.switchBinaryGet()
encapSequence(cmds, 500)
}
def on() {
log.debug "Executing 'on'"
encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF))
}
def off() {
log.debug "Executing 'off'"
encap(zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00))
}
def reset() {
log.debug "Executing 'reset'"
encap(zwave.meterV2.meterReset())
}
private secure(physicalgraph.zwave.Command cmd) {
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 = [0x25, 0x5A, 0x70, 0x85, 0x8E]
//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)
}
}

View File

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

View File

@@ -11,17 +11,6 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
/*
* Purpose: Arrival Sensor HA DTH File
*
* Filename: Arrival-Sensor-HA.src/Arrival-Sensor-HA.groovy
*
* Change History:
* 1. 20160115 TW - Update/Edit to support i18n translations
* 2. 20160121 TW - Update to V4 battery calcs, added pref's page title translations
*/
metadata { metadata {
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") { definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
capability "Tone" capability "Tone"
@@ -43,7 +32,7 @@ metadata {
]) ])
} }
section { 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 defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
} }
} }
@@ -93,6 +82,7 @@ def parse(String description) {
private handleReportAttributeMessage(String description) { private handleReportAttributeMessage(String description) {
def descMap = zigbee.parseDescriptionAsMap(description) def descMap = zigbee.parseDescriptionAsMap(description)
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) { if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
handleBatteryEvent(Integer.parseInt(descMap.value, 16)) handleBatteryEvent(Integer.parseInt(descMap.value, 16))
} }
@@ -104,7 +94,6 @@ private handleReportAttributeMessage(String description) {
* @param volts Battery voltage in .1V increments * @param volts Battery voltage in .1V increments
*/ */
private handleBatteryEvent(volts) { private handleBatteryEvent(volts) {
def descriptionText
if (volts == 0 || volts == 255) { if (volts == 0 || volts == 255) {
log.debug "Ignoring invalid value for voltage (${volts/10}V)" log.debug "Ignoring invalid value for voltage (${volts/10}V)"
} }
@@ -118,17 +107,15 @@ private handleBatteryEvent(volts) {
volts = minVolts volts = minVolts
else if (volts > maxVolts) else if (volts > maxVolts)
volts = maxVolts volts = maxVolts
def value = batteryMap[volts] def pct = batteryMap[volts]
if (value != null) { if (pct != null) {
def linkText = getLinkText(device) def linkText = getLinkText(device)
descriptionText = '{{ linkText }} battery was {{ value }}'
def eventMap = [ def eventMap = [
name: 'battery', name: 'battery',
value: value, value: pct,
descriptionText: descriptionText, descriptionText: "${linkText} battery was ${pct}%"
translatable: true
] ]
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) sendEvent(eventMap)
} }
} }
@@ -144,19 +131,13 @@ private handlePresenceEvent(present) {
stopTimer() stopTimer()
} }
def linkText = getLinkText(device) def linkText = getLinkText(device)
def descriptionText
if ( present )
descriptionText = "{{ linkText }} has arrived"
else
descriptionText = "{{ linkText }} has left"
def eventMap = [ def eventMap = [
name: "presence", name: "presence",
value: present ? "present" : "not present", value: present ? "present" : "not present",
linkText: linkText, linkText: linkText,
descriptionText: descriptionText, descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
translatable: true
] ]
log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value}" log.debug "Creating presence event: ${eventMap}"
sendEvent(eventMap) sendEvent(eventMap)
} }
@@ -177,4 +158,4 @@ def checkPresenceCallback() {
if (timeSinceLastCheckin >= theCheckInterval) { if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false) handlePresenceEvent(false)
} }
} }

View File

@@ -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.
#==============================================================================
# Purpose: Arrival Sensor HA i18n Translation File
#
# Filename: Arrival-Sensor-HA.src/i18n/messages.properties
#
# Change History:
# 1. 20160115 TW Initial release with informal Korean translation.
# 2. 20160121 TW Added def preference section titles.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기
'''Set Device Image'''.ko=기기 이미지 설정
'''Presence timeout (minutes)'''.ko=시간 초과. 스마트폰 위치 정보
'''Tap to set'''.ko=눌러서 설정
'''Arrival Sensor'''.ko=도착알림 센서
# Events / Notifications
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}남아있는 배터리는 {{ value }}입니다.
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다.
#==============================================================================

View File

@@ -1,7 +1,7 @@
/** /**
* Cree Bulb * 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 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at: * in compliance with the License. You may obtain a copy of the License at:
@@ -15,29 +15,29 @@
*/ */
metadata { metadata {
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") { definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Configuration" capability "Configuration"
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019" fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
} }
// simulator metadata // simulator metadata
simulator { simulator {
// status messages // status messages
status "on": "on/off: 1" status "on": "on/off: 1"
status "off": "on/off: 0" status "off": "on/off: 0"
// reply messages // reply messages
reply "zcl on-off on": "on/off: 1" reply "zcl on-off on": "on/off: 1"
reply "zcl on-off off": "on/off: 0" reply "zcl on-off off": "on/off: 0"
} }
// UI tile definitions // UI tile definitions
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
@@ -62,12 +62,18 @@ metadata {
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def resultMap = zigbee.getEvent(description) def resultMap = zigbee.getKnownDescription(description)
if (resultMap) { 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 { else {
log.debug "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description) log.debug zigbee.parseDescriptionAsMap(description)
} }
} }
@@ -81,7 +87,7 @@ def on() {
} }
def setLevel(value) { 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() { def refresh() {

View File

@@ -20,6 +20,7 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Motion Sensor" capability "Motion Sensor"
capability "Refresh" capability "Refresh"
capability "Polling"
} }
tiles { tiles {
@@ -47,8 +48,8 @@ metadata {
} }
standardTile("motion", "device.motion") { 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("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") { standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
@@ -67,6 +68,6 @@ def refresh() {
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.pollChild() parent.pollChild(this)
} }

View File

@@ -20,6 +20,7 @@ metadata {
capability "Actuator" capability "Actuator"
capability "Thermostat" capability "Thermostat"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Polling"
capability "Sensor" capability "Sensor"
capability "Refresh" capability "Refresh"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
@@ -133,7 +134,9 @@ def refresh() {
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" 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) { def generateEvent(Map results) {
@@ -379,16 +382,12 @@ def getDataByName(String name) {
state[name] ?: device.getDataValue(name) state[name] ?: device.getDataValue(name)
} }
def setThermostatMode(String mode) { def setThermostatMode(String value) {
log.debug "setThermostatMode($mode)" log.debug "setThermostatMode({$value})"
mode = mode.toLowerCase()
switchToMode(mode)
} }
def setThermostatFanMode(String mode) { def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode($mode)" log.debug "setThermostatFanMode({$value})"
mode = mode.toLowerCase()
switchToFanMode(mode)
} }
def generateModeEvent(mode) { def generateModeEvent(mode) {

View File

@@ -36,71 +36,155 @@
* Slider range from 0..100 * Slider range from 0..100
* Change 9: 2015-03-06 (Juan Risso) * Change 9: 2015-03-06 (Juan Risso)
* Setlevel -> value to integer (to prevent smartapp calling this function from not working). * 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 { 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 "Configuration"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
capability "Polling" capability "Polling"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
} }
// UI tile definitions // UI tile definitions
tiles(scale: 2) { tiles {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState:"turningOff"
attributeState "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"
attributeState "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"
attributeState "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"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" }
} standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
tileAttribute ("device.level", key: "SLIDER_CONTROL") { state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
attributeState "level", action:"switch level.setLevel" }
} controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
} state "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" valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
} state "level", label: 'Level ${currentValue}%'
main "switch" }
details(["switch", "refresh"])
} 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 // Parse incoming device messages to generate events
def parse(String description) { def parse(String description) {
def resultMap = zigbee.getEvent(description) log.trace 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 if (description?.startsWith("on/off:")) {
sendEvent(resultMap) 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" def msg = zigbee.parse(description)
log.debug zigbee.parseDescriptionAsMap(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 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() { def updated() {
@@ -186,63 +270,109 @@ def updated() {
} }
def on() { def on() {
state.lvl = "00"
state.trigger = "on/off" state.trigger = "on/off"
zigbee.on()
// log.debug "on()"
sendEvent(name: "switch", value: "on")
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
} }
def off() { def off() {
state.lvl = "00"
state.trigger = "on/off" 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 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 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" state.trigger = "setLevel"
def cmd state.lvl = "${level}"
def delayForRefresh = 500
if (dimRate && (state?.rate != null)) { if (dimRate && (state?.rate != null)) {
def computedRate = convertRateValue(state.rate) cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} ${state.rate}}"
cmd = zigbee.setLevel(value, computedRate)
delayForRefresh += computedRate * 100 //converting tenth of second to milliseconds
} }
else { else {
cmd = zigbee.setLevel(value, 20) cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 1500}"
delayForRefresh += 2000
} }
cmd + ["delay $delayForRefresh"] + zigbee.levelRefresh()
}
int convertRateValue(rate) { log.debug cmds
int convertedRate = 0 cmds
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
} }
def configure() { 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
}

View File

@@ -3,7 +3,6 @@
* *
* Author: SmartThings * Author: SmartThings
*/ */
// for the UI // for the UI
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
@@ -28,10 +27,10 @@ metadata {
tiles (scale: 2){ tiles (scale: 2){
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){ multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff" 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:"#C6C7CC", nextState:"turningOn" 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:"#00A0DC", nextState:"turningOff" 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:"#C6C7CC", nextState:"turningOn" attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
} }
tileAttribute ("device.level", key: "SLIDER_CONTROL") { tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)" attributeState "level", action:"switch level.setLevel", range:"(0..100)"
@@ -44,10 +43,16 @@ metadata {
} }
} }
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("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K' state "colorTemperature", label: '${currentValue} K'
} }
@@ -55,12 +60,29 @@ metadata {
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { 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" state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
} }
standardTile("refresh", "device.switch", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
state "saturation", action:"color control.setSaturation"
}
valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
state "saturation", label: 'Sat ${currentValue} '
}
controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
state "hue", action:"color control.setHue"
}
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
state "hue", label: 'Hue ${currentValue} '
}
main(["rich-control"]) main(["switch"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"]) details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
} }
} }
@@ -105,34 +127,34 @@ void nextLevel() {
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
parent.setLevel(this, percent) parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%") sendEvent(name: "level", value: percent)
} }
void setSaturation(percent) { void setSaturation(percent) {
log.debug "Executing 'setSaturation'" log.debug "Executing 'setSaturation'"
parent.setSaturation(this, percent) parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false) sendEvent(name: "saturation", value: percent)
} }
void setHue(percent) { void setHue(percent) {
log.debug "Executing 'setHue'" log.debug "Executing 'setHue'"
parent.setHue(this, percent) parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false) sendEvent(name: "hue", value: percent)
} }
void setColor(value) { void setColor(value) {
log.debug "setColor: ${value}, $this" log.debug "setColor: ${value}, $this"
parent.setColor(this, value) parent.setColor(this, value)
if (value.hue) { sendEvent(name: "hue", value: value.hue, displayed: false)} if (value.hue) { sendEvent(name: "hue", value: value.hue)}
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation, displayed: false)} if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
if (value.hex) { sendEvent(name: "color", value: value.hex)} if (value.hex) { sendEvent(name: "color", value: value.hex)}
if (value.level) { sendEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")} if (value.level) { sendEvent(name: "level", value: value.level)}
sendEvent(name: "switch", value: "on") if (value.switch) { sendEvent(name: "switch", value: value.switch)}
} }
void reset() { void reset() {
log.debug "Executing 'reset'" log.debug "Executing 'reset'"
def value = [level:100, saturation:56, hue:23] def value = [level:100, hex:"#90C638", saturation:56, hue:23]
setAdjustedColor(value) setAdjustedColor(value)
parent.poll() parent.poll()
} }

View File

@@ -36,6 +36,13 @@ metadata {
} }
} }
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)") { controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel" state "level", action:"switch level.setLevel"
} }
@@ -44,7 +51,7 @@ metadata {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main(["rich-control"]) main(["switch"])
details(["rich-control", "refresh"]) details(["rich-control", "refresh"])
} }
} }

View File

@@ -19,14 +19,13 @@
# #
# Change History: # Change History:
# 1. 20160205 TW Initial release with informal Korean translation. # 1. 20160205 TW Initial release with informal Korean translation.
# 2. 20160224 TW Updated with formal Korean translation.
#============================================================================== #==============================================================================
# Korean (ko) # Korean (ko)
# Device Preferences # Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기 '''Give your device a name'''.ko=기기 이름 바꾸기
'''Set Device Image'''.ko=기기 이미지 설정 '''Set Device Image'''.ko=디바이스 이미지 설정
# Events / Notifications # Events / Notifications
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다. '''{{ linkText }} has left'''.ko={{ linkText }}님이 나갔습니다
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다. '''{{ linkText }} has arrived'''.ko={{ linkText }}님이 도착했습니다
'''present'''.ko=집안 '''present'''.ko=집안
'''not present'''.ko=외출 '''not present'''.ko=부재중

View File

@@ -1,28 +1,16 @@
/* /**
=============================================================================== * Copyright 2015 SmartThings
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* use this file except in compliance with the License. You may obtain a copy * in compliance with the License. You may obtain a copy of the License at:
* of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: Mobile Presence DTH File
* *
* Filename: mobile-presence.src/mobile-presence.groovy
*
* Change History:
* 1. 20160205 TW - Update/Edit to support i18n translations
===============================================================================
*/ */
metadata { metadata {
definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") { definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
capability "Presence Sensor" capability "Presence Sensor"
@@ -53,7 +41,6 @@ def parse(String description) {
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
def results = [ def results = [
translatable: true,
name: name, name: name,
value: value, value: value,
unit: null, unit: null,
@@ -85,8 +72,8 @@ private String parseValue(String description) {
private parseDescriptionText(String linkText, String value, String description) { private parseDescriptionText(String linkText, String value, String description) {
switch(value) { switch(value) {
case "present": return "{{ linkText }} has arrived" case "present": return "$linkText has arrived"
case "not present": return "{{ linkText }} has left" case "not present": return "$linkText has left"
default: return value default: return value
} }
} }
@@ -97,4 +84,4 @@ private getState(String value) {
case "not present": return "left" case "not present": return "left"
default: return value default: return value
} }
} }

View File

@@ -1,31 +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.
#==============================================================================
# Purpose: SmartPower Outlet i18n Translation File
#
# Filename: SmartPower-Outlet.src/i18n/messages.properties
#
# Change History:
# 1. 20160116 TW Initial release with informal Korean translation.
#==============================================================================
# Korean (ko)
# 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 }}와트입니다
#==============================================================================

View File

@@ -1,26 +1,19 @@
/* /**
=============================================================================== * Copyright 2015 SmartThings
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* use this file except in compliance with the License. You may obtain a copy * in compliance with the License. You may obtain a copy of the License at:
* of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: SmartPower Outlet DTH File
* *
* Filename: SmartPower-Outlet.src/SmartPower-Outlet.groovy * SmartPower Outlet (CentraLite)
* *
* Change History: * Author: SmartThings
* 1. 20160117 TW - Update/Edit to support i18n translations * Date: 2015-08-23
===============================================================================
*/ */
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
@@ -98,22 +91,22 @@ def parse(String description) {
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description)) finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
if (finalResult) { if (finalResult) {
log.info "final result = $finalResult" log.info finalResult
if (finalResult.type == "update") { if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}" log.info "$device updates: ${finalResult.value}"
} }
else if (finalResult.type == "power") { else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10 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 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 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 to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
*/ */
} }
else { else {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' sendEvent(name: finalResult.type, value: finalResult.value)
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
} }
} }
else { else {

View File

@@ -1,41 +1,14 @@
#==============================================================================
# Copyright 2016 SmartThings # Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
# '''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
# Licensed under the Apache License, Version 2.0 (the "License"); you may not '''Degrees'''.ko=온도
# use this file except in compliance with the License. You may obtain a copy '''Temperature Offset'''.ko=온도 직접 설정
# of the License at: '''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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
# '''battery'''.ko=배터리
# 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.
#==============================================================================
# Purpose: SmartSense Moisture Sensor i18n Translation File
#
# Filename: SmartSense-Moisture-Sensor.src/i18n/messages.properties
#
# Change History:
# 1. 20160116 TW Initial release with formal Korean translation.
#==============================================================================
# Korean (ko)
# Device Preferences
'''dry'''.ko=건조 '''dry'''.ko=건조
'''wet'''.ko=누수 '''wet'''.ko=누수
'''battery'''.ko=배터리 '''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''Temperature Offset'''.ko=온도 직접 설정 '''{{ device.displayName }} battery was {{ value }}'''.ko={{ device.displayName }} 배터리가 {{ value }}였습니다
'''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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다. '''{{ device.displayName }} is {{ value | translate }}'''.ko={{ device.displayName }}이(가) {{ value | translate }}입니다
'''Degrees'''.ko=온도
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Give your device a name'''.ko=기기 이름 바꾸기
'''Water Leak Sensor'''.ko=누수센서
# 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 }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다 '''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
#==============================================================================

View File

@@ -1,29 +1,18 @@
/* /**
=============================================================================== * SmartSense Moisture Sensor
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Copyright 2014 SmartThings
* 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 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: SmartSense Moisture Sensor DTH File
* *
* Filename: SmartSense-Moisture-Sensor.src/SmartSense-Moisture-Sensor.groovy
*
* Change History:
* 1. 20160116 TW - Update/Edit to support i18n translations
* 2. 20160125 TW = Incorporated new battery mapping from TM
===============================================================================
*/ */
metadata { metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
capability "Configuration" capability "Configuration"
@@ -54,7 +43,7 @@ metadata {
]) ])
} }
section { 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 input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
} }
@@ -127,16 +116,16 @@ private Map parseCatchAllMessage(String description) {
resultMap = getBatteryResult(cluster.data.last()) resultMap = getBatteryResult(cluster.data.last())
break break
case 0x0402: case 0x0402:
// temp is last 2 data values. reverse to swap endian // temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp) def value = getTemperature(temp)
resultMap = getTemperatureResult(value) resultMap = getTemperatureResult(value)
break break
} }
} }
return resultMap return resultMap
} }
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
@@ -231,8 +220,7 @@ private Map getBatteryResult(rawValue) {
def result = [ def result = [
name: 'battery', name: 'battery',
value: '--', value: '--'
translatable: true
] ]
def volts = rawValue / 10 def volts = rawValue / 10
@@ -240,7 +228,7 @@ private Map getBatteryResult(rawValue) {
if (rawValue == 0 || rawValue == 255) {} if (rawValue == 0 || rawValue == 255) {}
else { else {
if (volts > 3.5) { 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 { else {
if (device.getDataValue("manufacturer") == "SmartThings") { if (device.getDataValue("manufacturer") == "SmartThings") {
@@ -257,7 +245,7 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts] def pct = batteryMap[volts]
if (pct != null) { if (pct != null) {
result.value = pct result.value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
else { else {
@@ -265,7 +253,7 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100) result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
} }
@@ -275,37 +263,27 @@ private Map getBatteryResult(rawValue) {
private Map getTemperatureResult(value) { private Map getTemperatureResult(value) {
log.debug 'TEMP' log.debug 'TEMP'
def linkText = getLinkText(device)
if (tempOffset) { if (tempOffset) {
def offset = tempOffset as int def offset = tempOffset as int
def v = value as int def v = value as int
value = v + offset value = v + offset
} }
def descriptionText def descriptionText = "${linkText} was ${value}°${temperatureScale}"
if ( temperatureScale == 'C' )
descriptionText = '{{ device.displayName }} was {{ value }}°C'
else
descriptionText = '{{ device.displayName }} was {{ value }}°F'
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }
private Map getMoistureResult(value) { private Map getMoistureResult(value) {
log.debug "water" log.debug 'water'
def descriptionText String descriptionText = "${device.displayName} is ${value}"
if ( value == "wet" )
descriptionText = '{{ device.displayName }} is wet'
else
descriptionText = '{{ device.displayName }} is dry'
return [ return [
name: 'water', name: 'water',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }

View File

@@ -1,40 +1,13 @@
#==============================================================================
# Copyright 2016 SmartThings # Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
#
# 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.
#==============================================================================
# Purpose: SmartSense Motion Sensor i18n Translation File
#
# Filename: SmartSense-Motion-Sensor.src/i18n/messages.properties
#
# Change History:
# 1. 20160116 TW Initial release with formal Korean translation.
# 2. 20160224 TW Updated formal Korean translations from Mike Stoller.
#==============================================================================
# Korean (ko)
# 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=몇 도씩 온도를 조절하십시오 '''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Give your device a name'''.ko=기기 이름 바꾸기 '''Degrees'''.ko=온도
'''Motion Sensor'''.ko=모션 센서 '''Temperature Offset'''.ko=온도 직접 설정
# Events descriptionText '''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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }} 가 움직임을 감지하였습니다. '''battery'''.ko=배터리
'''{{ 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 has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리 {{ value }}%입니다. '''{{ device.displayName }} battery was {{ value }}'''.ko={{ device.displayName }} 배터리 {{ value }}였습니다
#============================================================================== '''{{ 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였습니다

View File

@@ -1,27 +1,17 @@
/* /**
=============================================================================== * SmartSense Motion/Temp Sensor
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Copyright 2014 SmartThings
* 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 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: SmartSense Motion Sensor DTH File
* *
* Filename: SmartSense-Motion-Sensor.src/SmartSense-Motion-Sensor.groovy
*
* Change History:
* 1. 20160116 TW - Update/Edit to support i18n translations
* 2. 20160125 TW = Incorporated new battery mapping from TM
===============================================================================
*/ */
metadata { metadata {
@@ -57,7 +47,7 @@ metadata {
]) ])
} }
section { 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 input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
} }
@@ -246,8 +236,7 @@ private Map getBatteryResult(rawValue) {
def result = [ def result = [
name: 'battery', name: 'battery',
value: '--', value: '--'
translatable: true
] ]
def volts = rawValue / 10 def volts = rawValue / 10
@@ -255,7 +244,7 @@ private Map getBatteryResult(rawValue) {
if (rawValue == 0 || rawValue == 255) {} if (rawValue == 0 || rawValue == 255) {}
else { else {
if (volts > 3.5) { 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 { else {
if (device.getDataValue("manufacturer") == "SmartThings") { if (device.getDataValue("manufacturer") == "SmartThings") {
@@ -272,8 +261,7 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts] def pct = batteryMap[volts]
if (pct != null) { if (pct != null) {
result.value = pct result.value = pct
def value = pct result.descriptionText = "${linkText} battery was ${result.value}%"
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
} }
else { else {
@@ -281,7 +269,7 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100) result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
} }
@@ -291,33 +279,28 @@ private Map getBatteryResult(rawValue) {
private Map getTemperatureResult(value) { private Map getTemperatureResult(value) {
log.debug 'TEMP' log.debug 'TEMP'
def linkText = getLinkText(device)
if (tempOffset) { if (tempOffset) {
def offset = tempOffset as int def offset = tempOffset as int
def v = value as int def v = value as int
value = v + offset value = v + offset
} }
def descriptionText def descriptionText = "${linkText} was ${value}°${temperatureScale}"
if ( temperatureScale == 'C' )
descriptionText = '{{ device.displayName }} was {{ value }}°C'
else
descriptionText = '{{ device.displayName }} was {{ value }}°F'
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }
private Map getMotionResult(value) { private Map getMotionResult(value) {
log.debug 'motion' 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 [ return [
name: 'motion', name: 'motion',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }

View File

@@ -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 { metadata {
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Motion Sensor" capability "Motion Sensor"
@@ -27,6 +25,10 @@ metadata {
command "enrollResponse" 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 { simulator {
@@ -231,7 +233,7 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText def descriptionText
if (rawValue == 0 || rawValue == 255) {} if (rawValue == 0) {}
else { else {
if (volts > 3.5) { if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."

View File

@@ -1,45 +1,20 @@
#==============================================================================
# Copyright 2016 SmartThings # Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
# '''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
# Licensed under the Apache License, Version 2.0 (the "License"); you may not '''Degrees'''.ko=온도
# use this file except in compliance with the License. You may obtain a copy '''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
# 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.
#==============================================================================
# Purpose: SmartSense Multi Sensor i18n Translation File
#
# Filename: SmartSense-Multi-Sensor.src/i18n/messages.properties
#
# Change History:
# 1. 20160117 TW Initial release with informal Korean translation.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Yes'''.ko=
'''No'''.ko=아니요 '''No'''.ko=아니요
'''battery'''.ko=배터리 '''Tap to set'''.ko=눌러서 설정
'''Temperature Offset'''.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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다. '''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 garage sensor'''.ko=기기-차고 센서 업데이트 중
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중 '''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
'''Yes'''.ko=
'''{{ device.displayName }} status was closed'''.ko={{ device.displayName }}은(는) 닫힌 상태입니다
'''{{ device.displayName }} status was opened'''.ko={{ device.displayName }}은(는) 열린 상태입니다
'''{{ device.displayName }} was active'''.ko={{ device.displayName }}이(가) 활성화되었습니다
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}이(가) 닫혔습니다
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }}이(가) 비활성화되었습니다
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}이(가) 열렸습니다
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다

View File

@@ -1,27 +1,17 @@
/* /**
=============================================================================== * SmartSense Multi
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Copyright 2015 SmartThings
* 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 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: SmartSense Multi Sensor DTH File
* *
* Filename: SmartSense-Multi-Sensor.src/SmartSense-Multi-Sensor.groovy
*
* Change History:
* 1. 20160117 TW - Update/Edit to support i18n translations
* 2. 20160125 TW = Incorporated new battery mapping from TM
===============================================================================
*/ */
metadata { metadata {
@@ -72,11 +62,11 @@ metadata {
]) ])
} }
section { 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 input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
section { 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)
} }
} }
@@ -283,19 +273,19 @@ def updated() {
if (garageSensor == "Yes") { if (garageSensor == "Yes") {
def descriptionText = "Updating device to garage sensor" def descriptionText = "Updating device to garage sensor"
if (device.latestValue("status") == "open") { 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") { 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 { else {
def descriptionText = "Updating device to open/close sensor" def descriptionText = "Updating device to open/close sensor"
if (device.latestValue("status") == "garage-open") { 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") { else if (device.latestValue("status") == "garage-closed") {
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true) sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText)
} }
} }
} }
@@ -311,11 +301,11 @@ def getTemperature(value) {
private Map getBatteryResult(rawValue) { private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}" log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [ def result = [
name: 'battery', name: 'battery',
value: '--', value: '--'
translatable: true
] ]
def volts = rawValue / 10 def volts = rawValue / 10
@@ -323,7 +313,7 @@ private Map getBatteryResult(rawValue) {
if (rawValue == 0 || rawValue == 255) {} if (rawValue == 0 || rawValue == 255) {}
else { else {
if (volts > 3.5) { 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 { else {
if (device.getDataValue("manufacturer") == "SmartThings") { if (device.getDataValue("manufacturer") == "SmartThings") {
@@ -340,7 +330,7 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts] def pct = batteryMap[volts]
if (pct != null) { if (pct != null) {
result.value = pct result.value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
else { else {
@@ -348,7 +338,7 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100) result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
} }
@@ -358,50 +348,40 @@ private Map getBatteryResult(rawValue) {
private Map getTemperatureResult(value) { private Map getTemperatureResult(value) {
log.debug "Temperature" log.debug "Temperature"
def linkText = getLinkText(device)
if (tempOffset) { if (tempOffset) {
def offset = tempOffset as int def offset = tempOffset as int
def v = value as int def v = value as int
value = v + offset value = v + offset
} }
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C': def descriptionText = "${linkText} was ${value}°${temperatureScale}"
'{{ device.displayName }} was {{ value }}°F'
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }
private Map getContactResult(value) { private Map getContactResult(value) {
log.debug "Contact: ${device.displayName} value = ${value}" log.debug "Contact"
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed' def linkText = getLinkText(device)
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true) def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true) sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
} }
private getAccelerationResult(numValue) { private getAccelerationResult(numValue) {
log.debug "Acceleration" log.debug "Acceleration"
def name = "acceleration" def name = "acceleration"
def value def value = numValue.endsWith("1") ? "active" : "inactive"
def descriptionText def linkText = getLinkText(device)
def descriptionText = "$linkText was $value"
if ( numValue.endsWith("1") ) {
value = "active"
descriptionText = '{{ device.displayName }} was active'
} else {
value = "inactive"
descriptionText = '{{ device.displayName }} was inactive'
}
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
return [ [
name: name, name: name,
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText,
isStateChange: isStateChange, isStateChange: isStateChange
translatable: true
] ]
} }
@@ -471,15 +451,15 @@ def configure() {
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200", "zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200", "zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200", "zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
] ]
@@ -548,9 +528,10 @@ def garageEvent(zValue) {
garageValue = 'garage-open' garageValue = 'garage-open'
} }
if (contactValue != null){ if (contactValue != null){
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed' def linkText = getLinkText(device)
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true) def descriptionText = "${linkText} was ${contactValue == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true) sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText)
} }
} }

View File

@@ -22,8 +22,6 @@ metadata {
capability "Battery" capability "Battery"
fingerprint profileId: "FC01", deviceId: "0139" fingerprint profileId: "FC01", deviceId: "0139"
attribute "status", "string"
} }
simulator { simulator {
@@ -101,7 +99,7 @@ def parse(String description) {
} }
private List parseSingleMessage(description) { private Map parseSingleMessage(description) {
def name = parseName(description) def name = parseName(description)
def value = parseValue(description) def value = parseValue(description)
@@ -110,9 +108,8 @@ private List parseSingleMessage(description) {
def handlerName = value == 'open' ? 'opened' : value def handlerName = value == 'open' ? 'opened' : value
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
def results = [] def results = [
results << createEvent( name: name,
name: "contact",
value: value, value: value,
unit: null, unit: null,
linkText: linkText, linkText: linkText,
@@ -120,18 +117,8 @@ private List parseSingleMessage(description) {
handlerName: handlerName, handlerName: handlerName,
isStateChange: isStateChange, isStateChange: isStateChange,
displayed: displayed(description, isStateChange) displayed: displayed(description, isStateChange)
) ]
log.debug "Parse results for $device: $results"
results << createEvent(
name: "status",
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
)
results results
} }
@@ -282,7 +269,7 @@ private List parseRssiLqiMessage(String description) {
results results
} }
private List getContactResult(part, description) { private getContactResult(part, description) {
def name = "contact" def name = "contact"
def value = part.endsWith("1") ? "open" : "closed" def value = part.endsWith("1") ? "open" : "closed"
def handlerName = value == 'open' ? 'opened' : value def handlerName = value == 'open' ? 'opened' : value
@@ -290,30 +277,16 @@ private List getContactResult(part, description) {
def descriptionText = "$linkText was $handlerName" def descriptionText = "$linkText was $handlerName"
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
def results = [] [
results << createEvent( name: name,
name: "contact", value: value,
value: value, unit: null,
unit: null, linkText: linkText,
linkText: linkText, descriptionText: descriptionText,
descriptionText: descriptionText, handlerName: handlerName,
handlerName: handlerName, isStateChange: isStateChange,
isStateChange: isStateChange, displayed: displayed(description, isStateChange)
displayed:false ]
)
results << createEvent(
name: "status",
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
)
results
} }
private getAccelerationResult(part, description) { private getAccelerationResult(part, description) {
@@ -476,7 +449,6 @@ private Boolean isOrientationMessage(String description) {
description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/ description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/
} }
//Note: Not using this method anymore
private String parseName(String description) { private String parseName(String description) {
if (isSupportedDescription(description)) { if (isSupportedDescription(description)) {
return "contact" return "contact"

View File

@@ -13,7 +13,6 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
metadata { metadata {
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -24,7 +23,8 @@
capability "Refresh" capability "Refresh"
capability "Temperature Measurement" capability "Temperature Measurement"
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 { simulator {
@@ -225,8 +225,7 @@ def getTemperature(value) {
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText def descriptionText
if (rawValue == 0 || rawValue == 255) {} if (volts > 3.5) {
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else { else {

View File

@@ -220,8 +220,7 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText def descriptionText
if (rawValue == 0 || rawValue == 255) {} if (volts > 3.5) {
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else { else {

View File

@@ -196,8 +196,7 @@ private Map getBatteryResult(rawValue) {
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText def descriptionText
if (rawValue == 0 || rawValue == 255) {} if (volts > 3.5) {
else if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else { else {

View File

@@ -32,15 +32,14 @@ metadata {
attributeState("default", label:'${currentValue}', unit:"dF") attributeState("default", label:'${currentValue}', unit:"dF")
} }
tileAttribute("device.temperature", key: "VALUE_CONTROL") { tileAttribute("device.temperature", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "tempUp") attributeState("default", action: "setTemperature")
attributeState("VALUE_DOWN", action: "tempDown")
} }
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") { tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
attributeState("default", label:'${currentValue}%', unit:"%") attributeState("default", label:'${currentValue}%', unit:"%")
} }
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") { tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621") attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ea5462") attributeState("heating", backgroundColor:"#ffa81e")
attributeState("cooling", backgroundColor:"#269bd2") attributeState("cooling", backgroundColor:"#269bd2")
} }
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") { tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {

View File

@@ -44,7 +44,7 @@ metadata {
attributeState "power", label:'${currentValue} W' 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" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "switch" main "switch"

View File

@@ -39,7 +39,7 @@ metadata {
attributeState "level", action:"switch level.setLevel" 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" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "switch" main "switch"

View File

@@ -63,7 +63,7 @@ metadata {
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff" state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn" state "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" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) { controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {

View File

@@ -52,7 +52,7 @@
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) { valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
state "battery", label:'${currentValue}% battery', unit:"" 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" state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
} }

View File

@@ -57,7 +57,7 @@ metadata {
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K' 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" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }

View File

@@ -40,7 +40,7 @@ metadata {
attributeState "power", label:'${currentValue} W' 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" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "switch" main "switch"

View File

@@ -42,7 +42,7 @@ metadata {
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" 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" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main "switch" main "switch"

View File

@@ -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" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }

View File

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

View File

@@ -67,11 +67,11 @@ mappings {
def listAllDevices() { def listAllDevices() {
def resp = [] def resp = []
switches.each { 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 { 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 return resp
} }

View File

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

View File

@@ -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") : ""
}

View File

@@ -1,676 +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
}
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",
POST: "handleDevicesWithIDs"
]
}
path("/device/: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
if (command)
{
def success = false
//log.debug("command ${command}, arguments ${arguments}")
for (devId in ids)
{
def device = allDevices.find { it.id == devId }
if (device) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
}
success = true
} else {
//log.debug("device not found ${devId}")
}
}
if (success)
{
render status: 200, data: "{}"
}
else
{
render status: 404, data: '{"msg": "Device not found"}'
}
}
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
//log.debug("updateDevice, params: ${params}, request: ${data}")
if (!command) {
render status: 400, data: '{"msg": "command is required"}'
} else {
def device = allDevices.find { it.id == params.id }
if (device) {
if (arguments) {
device."$command"(*arguments)
} else {
device."$command"()
}
render status: 204, data: "{}"
} else {
render status: 404, data: '{"msg": "Device not found"}'
}
}
}
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
}
}
}
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 }
}

View File

@@ -463,32 +463,29 @@ def pollChildren(child = null) {
} }
// Poll Child is invoked from the Child Device itself as part of the Poll Capability // Poll Child is invoked from the Child Device itself as part of the Poll Capability
def pollChild(){ def pollChild(child){
def devices = getChildDevices() if (pollChildren(child)){
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
if (pollChildren()){ if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
devices.each { child -> def tData = atomicState.thermostats[child.device.deviceNetworkId]
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){ log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
if(atomicState.thermostats[child.device.deviceNetworkId] != null) { child.generateEvent(tData.data) //parse received message from parent
def tData = atomicState.thermostats[child.device.deviceNetworkId] } else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}" log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
child.generateEvent(tData.data) //parse received message from parent return null
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
return null
}
} }
} }
} else { } else {
log.info "ERROR: pollChildren()" log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling"
return null return null
} }
} }
void poll() { void poll() {
pollChild() def devices = getChildDevices()
devices.each {pollChild(it)}
} }
def availableModes(child) { def availableModes(child) {
@@ -548,15 +545,10 @@ def updateSensorData() {
def occupancy = "" def occupancy = ""
it.capability.each { it.capability.each {
if (it.type == "temperature") { if (it.type == "temperature") {
if (it.value == "unknown") { if (location.temperatureScale == "F") {
temperature = "--" temperature = Math.round(it.value.toDouble() / 10)
} else { } else {
if (location.temperatureScale == "F") { temperature = convertFtoC(it.value.toDouble() / 10)
temperature = Math.round(it.value.toDouble() / 10)
} else {
temperature = convertFtoC(it.value.toDouble() / 10)
}
} }
} else if (it.type == "occupancy") { } else if (it.type == "occupancy") {

View File

@@ -24,7 +24,7 @@ definition(
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
singleInstance: true //singleInstance: true
) )
preferences { preferences {
@@ -643,25 +643,21 @@ def setColorTemperature(childDevice, huesettings) {
def setColor(childDevice, huesettings) { def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'" log.debug "Executing 'setColor($huesettings)'"
def hue = null def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
def sat = null def sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
def xy = null
if (huesettings.hex) {
xy = getHextoXY(huesettings.hex)
} else if (huesettings.hue && huesettings.saturation) {
hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
}
def alert = huesettings.alert ? huesettings.alert : "none" def alert = huesettings.alert ? huesettings.alert : "none"
def transition = huesettings.transition ? huesettings.transition : 4 def transition = huesettings.transition ? huesettings.transition : 4
def value = [xy: xy, sat: sat, hue: hue, alert: alert, transitiontime: transition, on: true] def value = [sat: sat, hue: hue, alert: alert, transitiontime: transition]
if (huesettings.level != null) { if (huesettings.level != null) {
if (huesettings.level == 1) value.bri = 1 else value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255) if (huesettings.level == 1) value.bri = 1 else value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
value.on = value.bri > 0 value.on = value.bri > 0
} }
if (huesettings.switch) {
value.on = huesettings.switch == "on"
}
log.debug "sending command $value" log.debug "sending command $value"
put("lights/${getId(childDevice)}/state", value) put("lights/${getId(childDevice)}/state", value)
} }
@@ -747,59 +743,6 @@ private getBridgeIP() {
return host return host
} }
private getHextoXY(String colorStr) {
// For the hue bulb the corners of the triangle are:
// -Red: 0.675, 0.322
// -Green: 0.4091, 0.518
// -Blue: 0.167, 0.04
def cred = Integer.valueOf( colorStr.substring( 1, 3 ), 16 )
def cgreen = Integer.valueOf( colorStr.substring( 3, 5 ), 16 )
def cblue = Integer.valueOf( colorStr.substring( 5, 7 ), 16 )
double[] normalizedToOne = new double[3];
normalizedToOne[0] = (cred / 255);
normalizedToOne[1] = (cgreen / 255);
normalizedToOne[2] = (cblue / 255);
float red, green, blue;
// Make red more vivid
if (normalizedToOne[0] > 0.04045) {
red = (float) Math.pow(
(normalizedToOne[0] + 0.055) / (1.0 + 0.055), 2.4);
} else {
red = (float) (normalizedToOne[0] / 12.92);
}
// Make green more vivid
if (normalizedToOne[1] > 0.04045) {
green = (float) Math.pow((normalizedToOne[1] + 0.055)
/ (1.0 + 0.055), 2.4);
} else {
green = (float) (normalizedToOne[1] / 12.92);
}
// Make blue more vivid
if (normalizedToOne[2] > 0.04045) {
blue = (float) Math.pow((normalizedToOne[2] + 0.055)
/ (1.0 + 0.055), 2.4);
} else {
blue = (float) (normalizedToOne[2] / 12.92);
}
float X = (float) (red * 0.649926 + green * 0.103455 + blue * 0.197109);
float Y = (float) (red * 0.234327 + green * 0.743075 + blue * 0.022598);
float Z = (float) (red * 0.0000000 + green * 0.053077 + blue * 1.035763);
float x = X / (X + Y + Z);
float y = Y / (X + Y + Z);
double[] xy = new double[2];
xy[0] = x;
xy[1] = y;
return xy;
}
private Integer convertHexToInt(hex) { private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16) Integer.parseInt(hex,16)
} }

View File

@@ -1,31 +1,31 @@
'''Acceleration Detected'''.ko=가속 감지되었을 때 '''Acceleration Detected'''.ko=가속 감지
'''Arrival Of'''.ko=도착했을 때 '''Arrival Of'''.ko=도착
'''Both Push and SMS?'''.ko=푸시 알람과 SMS 모두 사용 '''Both Push and SMS?'''.ko=푸시 메시지와 SMS 모두 사용하시겠습니까?
'''Button Pushed'''.ko=버튼이 눌렸을 때 '''Button Pushed'''.ko=버튼이 눌렸습니다
'''Contact Closes'''.ko=닫힘이 감지되었을 때 '''Contact Closes'''.ko=접점 닫힘
'''Contact Opens'''.ko=열림이 감지되었을 때 '''Contact Opens'''.ko=접점 열림
'''Departure Of'''.ko=출발할 때 '''Departure Of'''.ko=출발
'''Message Text'''.ko=문자 메시지 '''Message Text'''.ko=문자 메시지
'''Minutes'''.ko=메시지 전송 간격(분) '''Minutes'''.ko=
'''Motion Here'''.ko=움직임이 감지되었을 때 '''Motion Here'''.ko=동작
'''Phone Number (for SMS, optional)'''.ko=전화번호 (옵션) '''Phone Number (for SMS, optional)'''.ko=휴대전화 번호(문자 메시지 - 옵션)
'''Receive notifications when anything happens in your home.'''.ko=집 안에 무슨 일이 일어나면 알림이 전송됩니다. '''Receive notifications when anything happens in your home.'''.ko=집 안에 무슨 일이 일어나면 알림이 전송됩니다.
'''Smoke Detected'''.ko=연기가 감지되었을 때 '''Smoke Detected'''.ko=연기가 감지되었습니다
'''Switch Turned Off'''.ko=스위치졌을 때 '''Switch Turned Off'''.ko=스위치 꺼
'''Switch Turned On'''.ko=스위치가 켜졌을 때 '''Switch Turned On'''.ko=스위치 꺼짐
'''Choose one or more, when...'''.ko=다음 상황 중 하나 이상 선택 '''Choose one or more, when...'''.ko=다음의 경우 하나 이상 선택
'''Yes'''.ko= '''Yes'''.ko=
'''No'''.ko=아니요 '''No'''.ko=아니요
'''Send this message (optional, sends standard status message if not specified)'''.ko=메시지 작성 (작성하지 않 경우 디폴트 메시지 전송) '''Send this message (optional, sends standard status message if not specified)'''.ko=메시지 전송(선택적, 지정되지 않 경우 표준 상태 메시지를 보냅니다)
'''Via a push notification and/or an SMS message'''.ko=푸시 알 SMS 설정 '''Via a push notification and/or an SMS message'''.ko=푸시 알/또는 문자 메시지를 통해
'''Set for specific mode(s)'''.ko=특정 상태 설정 '''Set for specific mode(s)'''.ko=특정 모드 설정
'''Tap to set'''.ko=눌러서 설정 '''Tap to set'''.ko=눌러서 설정
'''Minimum time between messages (optional, defaults to every message)'''.ko=메시지 전송 간격 설정 '''Minimum time between messages (optional, defaults to every message)'''.ko=메시지작 간 최소 시간(선택 사항, 모든 메시지의 기본 설정)
'''If outside the US please make sure to enter the proper country code'''.ko=미국 이외 국가에 거주한다면 국가 코드와 함께 입력하여 주세요. '''If outside the US please make sure to enter the proper country code'''.ko=미국 이외 거주자는 적절한 국가 코드를 입력했는지 확인하십시오
'''Water Sensor Wet'''.ko=누수가 감지되었을 때 '''Water Sensor Wet'''.ko=Water Sensor에서 물이 감지되었습니다
'''{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다 '''{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
'''{{ triggerEvent.linkText }} has arrived at {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다 '''{{ triggerEvent.linkText }} has arrived at {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
'''{{ triggerEvent.linkText }} has left the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다 '''{{ triggerEvent.linkText }} has left the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다 '''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''Assign a name'''.ko=이름 '''Assign a name'''.ko=이름
'''Choose Modes'''.ko=상태 선택 '''Choose Modes'''.ko=모드 선택

View File

@@ -76,7 +76,7 @@ def mainPage() {
} }
section{ section{
input "actionType", "enum", title: "Action?", required: true, defaultValue: "Bell 1", options: [ input "actionType", "enum", title: "Action?", required: true, defaultValue: "Bell 1", options: [
"Custom Message", //"Custom Message",
"Bell 1", "Bell 1",
"Bell 2", "Bell 2",
"Dogs Barking", "Dogs Barking",
@@ -89,7 +89,7 @@ def mainPage() {
"Someone is arriving", "Someone is arriving",
"Piano", "Piano",
"Lightsaber"] "Lightsaber"]
input "message","text",title:"Play this message", required:false, multiple: false //input "message","text",title:"Play this message", required:false, multiple: false
} }
section { section {
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
@@ -408,15 +408,13 @@ private loadText() {
case "Lightsaber": case "Lightsaber":
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/lightsaber.mp3", duration: "10"] state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/lightsaber.mp3", duration: "10"]
break; break;
case "Custom Message": default:
if (message) { /*if (message) {
state.sound = textToSpeech(message instanceof List ? message[0] : message) // not sure why this is (sometimes) needed) state.sound = textToSpeech(message instanceof List ? message[0] : message) // not sure why this is (sometimes) needed)
} }
else { else {
state.sound = textToSpeech("You selected the custom message option but did not enter a message in the $app.label Smart App") state.sound = textToSpeech("You selected the custom message option but did not enter a message in the $app.label Smart App")
} }*/
break;
default:
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/bell1.mp3", duration: "10"] state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/bell1.mp3", duration: "10"]
break; break;
} }

View File

@@ -234,7 +234,7 @@ def addSwitches() {
def d def d
if (selectedSwitch) { if (selectedSwitch) {
d = getChildDevices()?.find { d = getChildDevices()?.find {
it.deviceNetworkId == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac it.dni == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac
} }
} }
@@ -265,7 +265,7 @@ def addMotions() {
def d def d
if (selectedMotion) { if (selectedMotion) {
d = getChildDevices()?.find { d = getChildDevices()?.find {
it.deviceNetworkId == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac it.dni == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac
} }
} }
@@ -296,7 +296,7 @@ def addLightSwitches() {
def d def d
if (selectedLightSwitch) { if (selectedLightSwitch) {
d = getChildDevices()?.find { d = getChildDevices()?.find {
it.deviceNetworkId == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac it.dni == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac
} }
} }