mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 13:21:53 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9712f04850 | ||
|
|
089ab00ab7 | ||
|
|
a7b2e6a6cd | ||
|
|
361eca6b15 | ||
|
|
56d991b8d2 | ||
|
|
e83d08cf2f | ||
|
|
55905a10da | ||
|
|
546ee007f1 | ||
|
|
21ae20302c | ||
|
|
9880ced851 | ||
|
|
fb99a81704 | ||
|
|
6bda59c340 | ||
|
|
da029000c9 | ||
|
|
600a9a2ca1 | ||
|
|
783a30fa5f | ||
|
|
177c816348 |
107
devicetypes/com-obycode/beaconthing.src/beaconthing.groovy
Normal file
107
devicetypes/com-obycode/beaconthing.src/beaconthing.groovy
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* BeaconThing
|
||||
*
|
||||
* Copyright 2015 obycode
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
metadata {
|
||||
definition (name: "BeaconThing", namespace: "com.obycode", author: "obycode") {
|
||||
capability "Beacon"
|
||||
capability "Presence Sensor"
|
||||
capability "Sensor"
|
||||
|
||||
attribute "inRange", "json_object"
|
||||
attribute "inRangeFriendly", "string"
|
||||
|
||||
command "setPresence", ["string"]
|
||||
command "arrived", ["string"]
|
||||
command "left", ["string"]
|
||||
}
|
||||
|
||||
simulator {
|
||||
status "present": "presence: 1"
|
||||
status "not present": "presence: 0"
|
||||
}
|
||||
|
||||
tiles {
|
||||
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
|
||||
state("present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0")
|
||||
state("not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff")
|
||||
}
|
||||
valueTile("inRange", "device.inRangeFriendly", inactiveLabel: true, height:1, width:3, decoration: "flat") {
|
||||
state "default", label:'${currentValue}', backgroundColor:"#ffffff"
|
||||
}
|
||||
main "presence"
|
||||
details (["presence","inRange"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
}
|
||||
|
||||
def installed() {
|
||||
sendEvent(name: "presence", value: "not present")
|
||||
def emptyList = []
|
||||
def json = new groovy.json.JsonBuilder(emptyList)
|
||||
sendEvent(name:"inRange", value:json.toString())
|
||||
}
|
||||
|
||||
def setPresence(status) {
|
||||
log.debug "Status is $status"
|
||||
sendEvent(name:"presence", value:status)
|
||||
}
|
||||
|
||||
def arrived(id) {
|
||||
log.debug "$id has arrived"
|
||||
def theList = device.latestValue("inRange")
|
||||
def inRangeList = new JsonSlurper().parseText(theList)
|
||||
if (inRangeList.contains(id)) {
|
||||
return
|
||||
}
|
||||
inRangeList += id
|
||||
def json = new groovy.json.JsonBuilder(inRangeList)
|
||||
log.debug "Now in range: ${json.toString()}"
|
||||
sendEvent(name:"inRange", value:json.toString())
|
||||
|
||||
// Generate human friendly string for tile
|
||||
def friendlyList = "Nearby: " + inRangeList.join(", ")
|
||||
sendEvent(name:"inRangeFriendly", value:friendlyList)
|
||||
|
||||
if (inRangeList.size() == 1) {
|
||||
setPresence("present")
|
||||
}
|
||||
}
|
||||
|
||||
def left(id) {
|
||||
log.debug "$id has left"
|
||||
def theList = device.latestValue("inRange")
|
||||
def inRangeList = new JsonSlurper().parseText(theList)
|
||||
inRangeList -= id
|
||||
def json = new groovy.json.JsonBuilder(inRangeList)
|
||||
log.debug "Now in range: ${json.toString()}"
|
||||
sendEvent(name:"inRange", value:json.toString())
|
||||
|
||||
// Generate human friendly string for tile
|
||||
def friendlyList = "Nearby: " + inRangeList.join(", ")
|
||||
|
||||
if (inRangeList.empty) {
|
||||
setPresence("not present")
|
||||
friendlyList = "No one is nearby"
|
||||
}
|
||||
|
||||
sendEvent(name:"inRangeFriendly", value:friendlyList)
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016 Stuart Buchanan
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Aeon Water Sensor (DSB45-ZWEU/DSB45-ZWUS)
|
||||
*
|
||||
* Author: Stuart Buchanan, Based on original work by Tosa with thanks
|
||||
* Date: 2016-03-07
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Aeon Water Sensor", namespace: "fuzzysb", author: "Stuart Buchanan") {
|
||||
capability "Water Sensor"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
|
||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x80,0x84,0x71,0x70,0x85,0x86,0x72"
|
||||
}
|
||||
|
||||
simulator {
|
||||
status "dry": "command: 2001, payload: 00"
|
||||
status "wet": "command: 2001, payload: FF"
|
||||
}
|
||||
|
||||
|
||||
tiles {
|
||||
standardTile("water", "device.water", width: 2, height: 2) {
|
||||
state "dry", icon:"st.alarm.water.dry", backgroundColor:"#ffffff"
|
||||
state "wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, canChangeBackground: true) {
|
||||
state "battery", label:'${currentValue}% Battery', unit:"",
|
||||
backgroundColors:[
|
||||
[value: 19, color: "#BC2323"],
|
||||
[value: 20, color: "#D04E00"],
|
||||
[value: 30, color: "#D04E00"],
|
||||
[value: 40, color: "#DAC400"],
|
||||
[value: 41, color: "#79b821"]
|
||||
]
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
main "water"
|
||||
details(["water", "battery", "configure"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def result = null
|
||||
if (description.startsWith("Err 106")) {
|
||||
if (state.sec) {
|
||||
log.debug description
|
||||
} else {
|
||||
result = createEvent(
|
||||
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.",
|
||||
eventType: "ALERT",
|
||||
name: "secureInclusion",
|
||||
value: "failed",
|
||||
isStateChange: true,
|
||||
)
|
||||
}
|
||||
} else if (description != "updated") {
|
||||
def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1])
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
log.debug "parsed '$description' to $result"
|
||||
return result
|
||||
}
|
||||
|
||||
def updated() {
|
||||
def cmds = []
|
||||
if (!state.MSR) {
|
||||
cmds = [
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
|
||||
"delay 1200",
|
||||
zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||
]
|
||||
} else if (!state.lastbat) {
|
||||
cmds = []
|
||||
} else {
|
||||
cmds = [zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
|
||||
}
|
||||
response(cmds)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
delayBetween([
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
|
||||
batteryGetCommand()
|
||||
], 6000)
|
||||
}
|
||||
|
||||
def sensorValueEvent(value) {
|
||||
if (value) {
|
||||
createEvent(name: "water", value: "wet", descriptionText: "$device.displayName is wet")
|
||||
} else {
|
||||
createEvent(name: "water", value: "dry", descriptionText: "$device.displayName is dry")
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd)
|
||||
{
|
||||
sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd)
|
||||
{
|
||||
sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd)
|
||||
{
|
||||
sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd)
|
||||
{
|
||||
sensorValueEvent(cmd.sensorValue)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd)
|
||||
{
|
||||
sensorValueEvent(cmd.sensorState)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd)
|
||||
{
|
||||
def result = []
|
||||
if (cmd.notificationType == 0x06 && cmd.event == 0x16) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.notificationType == 0x06 && cmd.event == 0x17) {
|
||||
result << sensorValueEvent(0)
|
||||
} else if (cmd.notificationType == 0x07) {
|
||||
if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice door/window sensors
|
||||
result << sensorValueEvent(cmd.v1AlarmLevel)
|
||||
} else if (cmd.event == 0x01 || cmd.event == 0x02) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x03) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
|
||||
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||
} else if (cmd.event == 0x07) {
|
||||
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion")
|
||||
}
|
||||
} else if (cmd.notificationType) {
|
||||
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
|
||||
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
|
||||
} else {
|
||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
{
|
||||
def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
|
||||
def cmds = []
|
||||
if (!state.MSR) {
|
||||
cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId).format()
|
||||
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()
|
||||
cmds << "delay 1200"
|
||||
}
|
||||
if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {
|
||||
cmds << batteryGetCommand()
|
||||
} else {
|
||||
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||
}
|
||||
[event, response(cmds)]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} has a low battery"
|
||||
map.isStateChange = true
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
state.lastbat = now()
|
||||
[createEvent(map), response(zwave.wakeUpV1.wakeUpNoMoreInformation())]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
def result = []
|
||||
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
log.debug "msr: $msr"
|
||||
updateDataValue("MSR", msr)
|
||||
|
||||
result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
|
||||
|
||||
if (msr == "011A-0601-0901") { // Enerwave motion doesn't always get the associationSet that the hub sends on join
|
||||
result << response(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
|
||||
} else if (!device.currentState("battery")) {
|
||||
if (msr == "0086-0102-0059") {
|
||||
result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
|
||||
} else {
|
||||
result << response(batteryGetCommand())
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1])
|
||||
// log.debug "encapsulated: $encapsulatedCommand"
|
||||
if (encapsulatedCommand) {
|
||||
state.sec = 1
|
||||
zwaveEvent(encapsulatedCommand)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)
|
||||
}
|
||||
|
||||
def batteryGetCommand() {
|
||||
def cmd = zwave.batteryV1.batteryGet()
|
||||
if (state.sec) {
|
||||
cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
|
||||
}
|
||||
cmd.format()
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Cree Bulb
|
||||
*
|
||||
* Copyright 2014 SmartThings
|
||||
* 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:
|
||||
@@ -15,29 +15,29 @@
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
||||
}
|
||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
status "on": "on/off: 1"
|
||||
status "off": "on/off: 0"
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
status "on": "on/off: 1"
|
||||
status "off": "on/off: 0"
|
||||
|
||||
// reply messages
|
||||
reply "zcl on-off on": "on/off: 1"
|
||||
reply "zcl on-off off": "on/off: 0"
|
||||
}
|
||||
// reply messages
|
||||
reply "zcl on-off on": "on/off: 1"
|
||||
reply "zcl on-off off": "on/off: 0"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
@@ -62,18 +62,12 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
def resultMap = zigbee.getEvent(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
sendEvent(resultMap)
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
@@ -87,7 +81,7 @@ def on() {
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
|
||||
@@ -36,155 +36,71 @@
|
||||
* Slider range from 0..100
|
||||
* Change 9: 2015-03-06 (Juan Risso)
|
||||
* Setlevel -> value to integer (to prevent smartapp calling this function from not working).
|
||||
* Change 10: 2016-03-06 (Vinay Rao/Tom Manley)
|
||||
* changed 2/3rds of the file to clean up code and add zigbee library improvements
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Sensor"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
capability "Switch Level"
|
||||
capability "Polling"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
|
||||
}
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState:"turningOff"
|
||||
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action: "switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action: "switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
||||
state "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "level", "levelSliderControl", "refresh"])
|
||||
}
|
||||
|
||||
preferences {
|
||||
|
||||
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
|
||||
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
|
||||
preferences {
|
||||
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
|
||||
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.trace description
|
||||
|
||||
if (description?.startsWith("on/off:")) {
|
||||
log.debug "The bulb was sent a command to do something just now..."
|
||||
if (description[-1] == "1") {
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "On command was sent maybe from manually turning on? : Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
} else if (description[-1] == "0") {
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "Off command was sent : Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
def resultMap = zigbee.getEvent(description)
|
||||
if (resultMap) {
|
||||
if ((resultMap.name == "level" && state.trigger == "setLevel") || resultMap.name != "level") { //doing this to account for weird level reporting bug with GE Link Bulbs
|
||||
sendEvent(resultMap)
|
||||
}
|
||||
}
|
||||
|
||||
def msg = zigbee.parse(description)
|
||||
|
||||
if (description?.startsWith("catchall:")) {
|
||||
// log.trace msg
|
||||
// log.trace "data: $msg.data"
|
||||
|
||||
def x = description[-4..-1]
|
||||
// log.debug x
|
||||
|
||||
switch (x)
|
||||
{
|
||||
|
||||
case "0000":
|
||||
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
|
||||
case "1000":
|
||||
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
|
||||
case "0100":
|
||||
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
|
||||
case "1001":
|
||||
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "${result?.descriptionText}"
|
||||
return result
|
||||
break
|
||||
}
|
||||
else {
|
||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
[
|
||||
"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'}}"
|
||||
def refreshCmds = [
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
||||
]
|
||||
|
||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
@@ -270,109 +186,63 @@ def updated() {
|
||||
}
|
||||
|
||||
def on() {
|
||||
state.lvl = "00"
|
||||
state.trigger = "on/off"
|
||||
|
||||
// log.debug "on()"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def off() {
|
||||
state.lvl = "00"
|
||||
state.trigger = "on/off"
|
||||
|
||||
// log.debug "off()"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
|
||||
[
|
||||
"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'}}"
|
||||
def refreshCmds = [
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
||||
]
|
||||
poll()
|
||||
|
||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()
|
||||
}
|
||||
|
||||
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.lvl = "${level}"
|
||||
|
||||
def cmd
|
||||
def delayForRefresh = 500
|
||||
if (dimRate && (state?.rate != null)) {
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} ${state.rate}}"
|
||||
def computedRate = convertRateValue(state.rate)
|
||||
cmd = zigbee.setLevel(value, computedRate)
|
||||
delayForRefresh += computedRate * 100 //converting tenth of second to milliseconds
|
||||
}
|
||||
else {
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 1500}"
|
||||
cmd = zigbee.setLevel(value, 20)
|
||||
delayForRefresh += 2000
|
||||
}
|
||||
cmd + ["delay $delayForRefresh"] + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
log.debug cmds
|
||||
cmds
|
||||
int convertRateValue(rate) {
|
||||
int convertedRate = 0
|
||||
switch (rate)
|
||||
{
|
||||
case "0000":
|
||||
convertedRate = 0
|
||||
break
|
||||
|
||||
case "1500":
|
||||
convertedRate = 20 //0015 hex in int is 2.1
|
||||
break
|
||||
|
||||
case "2500":
|
||||
convertedRate = 35 //0025 hex in int is 3.7
|
||||
break
|
||||
|
||||
case "3500":
|
||||
convertedRate = 50 //0035 hex in int is 5.1
|
||||
break
|
||||
}
|
||||
convertedRate
|
||||
}
|
||||
|
||||
def configure() {
|
||||
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def configCmds = [
|
||||
|
||||
//Switch Reporting
|
||||
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} 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
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
return zigbee.onOffConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Hue Bulb
|
||||
*
|
||||
@@ -11,13 +10,14 @@ metadata {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Color Control"
|
||||
capability "Color Temperature"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
command "setAdjustedColor"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -25,7 +25,7 @@ metadata {
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"switch", 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") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
@@ -33,23 +33,58 @@ metadata {
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setAdjustedColor"
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
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)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: '${currentValue} K'
|
||||
}
|
||||
|
||||
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
standardTile("refresh", "device.switch", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
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(["switch"])
|
||||
details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"])
|
||||
main(["switch"])
|
||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
@@ -119,19 +154,27 @@ void setColor(value) {
|
||||
|
||||
void reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
|
||||
setAdjustedColor(value)
|
||||
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
|
||||
setAdjustedColor(value)
|
||||
parent.poll()
|
||||
}
|
||||
|
||||
void setAdjustedColor(value) {
|
||||
if (value) {
|
||||
log.trace "setAdjustedColor: ${value}"
|
||||
def adjusted = value + [:]
|
||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
||||
// Needed because color picker always sends 100
|
||||
adjusted.level = null
|
||||
setColor(adjusted)
|
||||
log.trace "setAdjustedColor: ${value}"
|
||||
def adjusted = value + [:]
|
||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
||||
// Needed because color picker always sends 100
|
||||
adjusted.level = null
|
||||
setColor(adjusted)
|
||||
}
|
||||
}
|
||||
|
||||
void setColorTemperature(value) {
|
||||
if (value) {
|
||||
log.trace "setColorTemperature: ${value}k"
|
||||
parent.setColorTemperature(this, value)
|
||||
sendEvent(name: "colorTemperature", value: value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,51 +9,59 @@ metadata {
|
||||
definition (name: "Hue Lux Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Color Temperature"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
command "refresh"
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
}
|
||||
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: '${currentValue} K'
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["rich-control", "refresh"])
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
@@ -90,6 +98,14 @@ void setLevel(percent) {
|
||||
sendEvent(name: "level", value: percent)
|
||||
}
|
||||
|
||||
void setColorTemperature(value) {
|
||||
if (value) {
|
||||
log.trace "setColorTemperature: ${value}k"
|
||||
parent.setColorTemperature(this, value)
|
||||
sendEvent(name: "colorTemperature", value: value)
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
|
||||
46
devicetypes/test11/test1.src/test1.groovy
Normal file
46
devicetypes/test11/test1.src/test1.groovy
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* test1
|
||||
*
|
||||
* Copyright 2016 임희진
|
||||
*
|
||||
* 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: "test1", namespace: "test11", author: "임희진") {
|
||||
capability "Lock"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles {
|
||||
// TODO: define your main and details tiles here
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
// TODO: handle 'lock' attribute
|
||||
|
||||
}
|
||||
|
||||
// handle commands
|
||||
def lock() {
|
||||
log.debug "Executing 'lock'"
|
||||
// TODO: handle 'lock' command
|
||||
}
|
||||
|
||||
def unlock() {
|
||||
log.debug "Executing 'unlock'"
|
||||
// TODO: handle 'unlock' command
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
/**
|
||||
* 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 }
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* 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") : ""
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
/**
|
||||
* 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 }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
definition(
|
||||
name: "Hue (Connect)",
|
||||
namespace: "smartthings",
|
||||
@@ -24,7 +24,7 @@ definition(
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
|
||||
singleInstance: true
|
||||
//singleInstance: true
|
||||
)
|
||||
|
||||
preferences {
|
||||
@@ -58,7 +58,7 @@ def bridgeDiscovery(params=[:])
|
||||
state.bridges = [:]
|
||||
state.bridgeRefreshCount = 0
|
||||
app.updateSetting("selectedHue", "")
|
||||
}
|
||||
}
|
||||
|
||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||
|
||||
@@ -130,8 +130,8 @@ def bulbDiscovery() {
|
||||
def bulboptions = bulbsDiscovered() ?: [:]
|
||||
def numFound = bulboptions.size() ?: 0
|
||||
if (numFound == 0)
|
||||
app.updateSetting("selectedBulbs", "")
|
||||
|
||||
app.updateSetting("selectedBulbs", "")
|
||||
|
||||
if((bulbRefreshCount % 5) == 0) {
|
||||
discoverHueBulbs()
|
||||
}
|
||||
@@ -140,7 +140,7 @@ def bulbDiscovery() {
|
||||
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
|
||||
}
|
||||
section {
|
||||
section {
|
||||
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
|
||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||
|
||||
@@ -246,13 +246,13 @@ def installed() {
|
||||
|
||||
def updated() {
|
||||
log.trace "Updated with settings: ${settings}"
|
||||
unsubscribe()
|
||||
unschedule()
|
||||
unsubscribe()
|
||||
unschedule()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
log.debug "Initializing"
|
||||
log.debug "Initializing"
|
||||
unsubscribe(bridge)
|
||||
state.inBulbDiscovery = false
|
||||
state.bridgeRefreshCount = 0
|
||||
@@ -281,18 +281,18 @@ def uninstalled(){
|
||||
def bulbListHandler(hub, data = "") {
|
||||
def msg = "Bulbs list not processed. Only while in settings menu."
|
||||
def bulbs = [:]
|
||||
if (state.inBulbDiscovery) {
|
||||
if (state.inBulbDiscovery) {
|
||||
def logg = ""
|
||||
log.trace "Adding bulbs to state..."
|
||||
state.bridgeProcessedLightList = true
|
||||
def object = new groovy.json.JsonSlurper().parseText(data)
|
||||
def object = new groovy.json.JsonSlurper().parseText(data)
|
||||
object.each { k,v ->
|
||||
if (v instanceof Map)
|
||||
if (v instanceof Map)
|
||||
bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub]
|
||||
}
|
||||
}
|
||||
}
|
||||
def bridge = null
|
||||
if (selectedHue)
|
||||
if (selectedHue)
|
||||
bridge = getChildDevice(selectedHue)
|
||||
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
|
||||
msg = "${bulbs.size()} bulbs found. ${bulbs}"
|
||||
@@ -318,7 +318,7 @@ def addBulbs() {
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
} else {
|
||||
} else {
|
||||
//backwards compatable
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
||||
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
|
||||
@@ -344,7 +344,7 @@ def addBridge() {
|
||||
def d = getChildDevice(selectedHue)
|
||||
if(!d) {
|
||||
// compatibility with old devices
|
||||
def newbridge = true
|
||||
def newbridge = true
|
||||
childDevices.each {
|
||||
if (it.getDeviceDataByName("mac")) {
|
||||
def newDNI = "${it.getDeviceDataByName("mac")}"
|
||||
@@ -354,10 +354,10 @@ def addBridge() {
|
||||
it.setDeviceNetworkId("${newDNI}")
|
||||
if (oldDNI == selectedHue)
|
||||
app.updateSetting("selectedHue", newDNI)
|
||||
newbridge = false
|
||||
newbridge = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newbridge) {
|
||||
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
|
||||
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
||||
@@ -368,13 +368,13 @@ def addBridge() {
|
||||
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
|
||||
childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port)
|
||||
} else {
|
||||
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
||||
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
||||
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
||||
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $selectedHue already exists"
|
||||
@@ -436,7 +436,7 @@ def locationHandler(evt) {
|
||||
dstate.name = "Philips hue ($ip)"
|
||||
d.sendEvent(name:"networkAddress", value: host)
|
||||
d.updateDataValue("networkAddress", host)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -504,11 +504,11 @@ def isValidSource(macAddress) {
|
||||
/////////////////////////////////////
|
||||
|
||||
def parse(childDevice, description) {
|
||||
def parsedEvent = parseLanMessage(description)
|
||||
def parsedEvent = parseLanMessage(description)
|
||||
if (parsedEvent.headers && parsedEvent.body) {
|
||||
def headerString = parsedEvent.headers.toString()
|
||||
def bodyString = parsedEvent.body.toString()
|
||||
if (headerString?.contains("json")) {
|
||||
if (headerString?.contains("json")) {
|
||||
def body
|
||||
try {
|
||||
body = new groovy.json.JsonSlurper().parseText(bodyString)
|
||||
@@ -516,11 +516,11 @@ def parse(childDevice, description) {
|
||||
log.warn "Parsing Body failed - trying again..."
|
||||
poll()
|
||||
}
|
||||
if (body instanceof java.util.HashMap) {
|
||||
if (body instanceof java.util.HashMap) {
|
||||
//poll response
|
||||
def bulbs = getChildDevices()
|
||||
for (bulb in body) {
|
||||
def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
||||
def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
||||
if (d) {
|
||||
if (bulb.value.state?.reachable) {
|
||||
sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"])
|
||||
@@ -535,18 +535,18 @@ def parse(childDevice, description) {
|
||||
}
|
||||
} else {
|
||||
sendEvent(d.deviceNetworkId, [name: "switch", value: "off"])
|
||||
sendEvent(d.deviceNetworkId, [name: "level", value: 100])
|
||||
sendEvent(d.deviceNetworkId, [name: "level", value: 100])
|
||||
if (bulb.value.state.sat) {
|
||||
def hue = 23
|
||||
def sat = 56
|
||||
def hex = colorUtil.hslToHex(23, 56)
|
||||
sendEvent(d.deviceNetworkId, [name: "color", value: hex])
|
||||
sendEvent(d.deviceNetworkId, [name: "hue", value: hue])
|
||||
sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
|
||||
}
|
||||
sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ //put response
|
||||
@@ -595,7 +595,7 @@ def parse(childDevice, description) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug "parse - got something other than headers,body..."
|
||||
return []
|
||||
@@ -616,7 +616,7 @@ def off(childDevice) {
|
||||
|
||||
def setLevel(childDevice, percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
def level
|
||||
def level
|
||||
if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255)
|
||||
put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0])
|
||||
}
|
||||
@@ -633,6 +633,14 @@ def setHue(childDevice, percent) {
|
||||
put("lights/${getId(childDevice)}/state", [hue: level])
|
||||
}
|
||||
|
||||
def setColorTemperature(childDevice, huesettings) {
|
||||
log.debug "Executing 'setColorTemperature($huesettings)'"
|
||||
def ct = Math.round(Math.abs((huesettings / 12.96829971181556) - 654))
|
||||
def value = [ct: ct, on: true]
|
||||
log.trace "sending command $value"
|
||||
put("lights/${getId(childDevice)}/state", value)
|
||||
}
|
||||
|
||||
def setColor(childDevice, huesettings) {
|
||||
log.debug "Executing 'setColor($huesettings)'"
|
||||
def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||
@@ -689,7 +697,7 @@ HOST: ${host}
|
||||
}
|
||||
|
||||
private put(path, body) {
|
||||
def host = getBridgeIP()
|
||||
def host = getBridgeIP()
|
||||
def uri = "/api/${state.username}/$path"
|
||||
def bodyJSON = new groovy.json.JsonBuilder(body).toString()
|
||||
def length = bodyJSON.getBytes().size().toString()
|
||||
@@ -715,11 +723,11 @@ private getBridgeIP() {
|
||||
host = d.getDeviceDataByName("networkAddress")
|
||||
else
|
||||
host = d.latestState('networkAddress').stringValue
|
||||
}
|
||||
}
|
||||
if (host == null || host == "") {
|
||||
def serialNumber = selectedHue
|
||||
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
|
||||
if (!bridge) {
|
||||
if (!bridge) {
|
||||
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
|
||||
}
|
||||
if (bridge?.ip && bridge?.port) {
|
||||
@@ -729,9 +737,9 @@ private getBridgeIP() {
|
||||
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
|
||||
} else if (bridge?.networkAddress && bridge?.deviceAddress)
|
||||
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
|
||||
}
|
||||
}
|
||||
log.trace "Bridge: $selectedHue - Host: $host"
|
||||
}
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user