Compare commits

..

1 Commits

Author SHA1 Message Date
Schwark Satyavolu
a2fd0bed4a MSA-1739: Integrates Hunter Douglas window shades controlled by their Platinum Gateway bridge. 2017-01-25 14:42:57 -08:00
15 changed files with 604 additions and 184 deletions

View File

@@ -1,46 +0,0 @@
/**
* Stateless On/Off Button Tile
*
* Author: Ronald Gouldner
*
* Date: 2015-05-14
*/
metadata {
// Automatically generated. Make future change here.
definition (name: "Stateless On-Off Button Tile", namespace: "gouldner", author: "Ronald Gouldner") {
capability "Actuator"
capability "Switch"
capability "Sensor"
}
// simulator metadata
simulator {
}
// UI tile definitions
tiles {
standardTile("button", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "offReady", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "onReady"
state "onReady", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "offReady"
state "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
state "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
}
main "button"
details "button"
}
}
def parse(String description) {
}
def on() {
log.debug "Stateless On/Off Button Tile Virtual Switch ${device.name} turned on"
sendEvent(name: "switch", value: "on")
sendEvent(name: "switch", value: "onReady")
}
def off() {
log.debug "Stateless On/Off Button Tile Virtual Switch ${device.name} turned off"
sendEvent(name: "switch", value: "off")
sendEvent(name: "switch", value: "offReady")
}

View File

@@ -0,0 +1,71 @@
/**
* Hunter Douglas Platinum Gateway Scene Control Switch for SmartThings
* Schwark Satyavolu
* Originally based on: Allan Klein's (@allanak) and Mike Maxwell's code
*
* Usage:
* 1. Add this code as a device handler in the SmartThings IDE
* 3. Create a device using PlatinumGatewaySceneSwitch as the device handler using a hexadecimal representation of IP:port as the device network ID value
* For example, a gateway at 192.168.1.222:522 would have a device network ID of C0A801DE:20A
* Note: Port 522 is the default Hunter Douglas Platinum Gateway port so you shouldn't need to change anything after the colon
* 4. Enjoy the new functionality of the SmartThings app
*
* 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: "Platinum Gateway Scene Switch", namespace: "schwark", author: "Schwark Satyavolu") {
capability "Switch"
command "setSceneNo", ["string"]
command "runScene"
}
simulator {
// TODO: define status and reply messages here
}
tiles {
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
}
}
preferences {
}
main "switch"
details(["switch"])
}
def updated() {
}
def runScene() {
parent.runScene(state.sceneNo)
}
def on() {
runScene()
sendEvent(name: "switch", value: "on")
}
def off() {
runScene()
sendEvent(name: "switch", value: "off")
}
def setSceneNo(sceneNo) {
state.sceneNo = sceneNo
}

View File

@@ -0,0 +1,86 @@
/**
* Hunter Douglas Platinum Gateway Shade Control Switch for SmartThings
* Schwark Satyavolu
* Originally based on: Allan Klein's (@allanak) and Mike Maxwell's code
*
* Usage:
* 1. Add this code as a device handler in the SmartThings IDE
* 3. Create a device using PlatinumGatewayShadeSwitch as the device handler using a hexadecimal representation of IP:port as the device network ID value
* For example, a gateway at 192.168.1.222:522 would have a device network ID of C0A801DE:20A
* Note: Port 522 is the default Hunter Douglas Platinum Gateway port so you shouldn't need to change anything after the colon
* 4. Enjoy the new functionality of the SmartThings app
*
* 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: "Platinum Gateway Shade Switch", namespace: "schwark", author: "Schwark Satyavolu") {
capability "Switch"
capability "Switch Level"
command "setShadeNo", ["string"]
}
simulator {
// TODO: define status and reply messages here
}
tiles {
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel"
}
}
preferences {
}
main "switch"
details(["switch", "levelSliderControl"])
}
def installed() {
log.debug("installed Shade with settings ${settings}")
initialize()
}
def initialize() {
}
def updated() {
}
def on() {
return setLevel(100)
}
def off() {
return setLevel(0)
}
def setLevel(percent) {
parent.setShadeLevel(state.shadeNo, percent)
if(percent == 100) {
sendEvent(name: "switch", value: "on")
} else if (percent == 0) {
sendEvent(name: "switch", value: "off")
}
sendEvent(name: "level", value: percent)
}
def setShadeNo(shadeNo) {
state.shadeNo = shadeNo
}

View File

@@ -82,7 +82,7 @@ def on() {
}
def setLevel(value) {
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
/**

View File

@@ -17,7 +17,6 @@ metadata {
capability "Refresh"
capability "Sensor"
capability "Health Check"
capability "Light"
command "setAdjustedColor"
command "reset"

View File

@@ -18,7 +18,6 @@ metadata {
capability "Refresh"
capability "Sensor"
capability "Health Check"
capability "Light"
command "setAdjustedColor"
command "reset"

View File

@@ -14,8 +14,7 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
capability "Light"
capability "Health Check"
command "refresh"
}

View File

@@ -16,7 +16,6 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Health Check"
capability "Light"
command "refresh"
}

View File

@@ -89,7 +89,7 @@ def on() {
}
def setLevel(value) {
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
def refresh() {

View File

@@ -115,7 +115,7 @@ def refreshAttributes() {
}
def setLevel(value) {
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
def setColor(value){

View File

@@ -135,7 +135,7 @@ def setColorTemperature(value) {
}
def setLevel(value) {
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
def setColor(value){

View File

@@ -90,7 +90,7 @@ def on() {
}
def setLevel(value) {
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh()
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh()
}
def refresh() {

View File

@@ -73,7 +73,7 @@ def authPage() {
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
section() {
paragraph "Tap below to log in to the netatmo and authorize SmartThings access."
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}", description:description
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description
}
}
} else {
@@ -146,24 +146,19 @@ def callback() {
// log.debug "PARAMS: ${params}"
try {
httpPost(params) { resp ->
httpPost(params) { resp ->
def slurper = new JsonSlurper()
def slurper = new JsonSlurper()
resp.data.each { key, value ->
def data = slurper.parseText(key)
log.debug "Data: $data"
state.refreshToken = data.refresh_token
state.authToken = data.access_token
//state.accessToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000)
// log.debug "swapped token: $resp.data"
}
}
} catch (Exception e) {
log.debug "callback: Call failed $e"
}
resp.data.each { key, value ->
def data = slurper.parseText(key)
state.refreshToken = data.refresh_token
state.authToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000)
// log.debug "swapped token: $resp.data"
}
}
// Handle success and failure here, and render stuff accordingly
if (state.authToken) {
@@ -392,18 +387,18 @@ def getDeviceList() {
state.deviceDetail = [:]
state.deviceState = [:]
apiGet("/api/getstationsdata") { response ->
apiGet("/api/devicelist") { response ->
response.data.body.devices.each { value ->
def key = value._id
deviceList[key] = "${value.station_name}: ${value.module_name}"
state.deviceDetail[key] = value
state.deviceState[key] = value.dashboard_data
value.modules.each { value2 ->
def key2 = value2._id
deviceList[key2] = "${value.station_name}: ${value2.module_name}"
state.deviceDetail[key2] = value2
state.deviceState[key2] = value2.dashboard_data
}
}
response.data.body.modules.each { value ->
def key = value._id
deviceList[key] = "${state.deviceDetail[value.main_device].station_name}: ${value.module_name}"
state.deviceDetail[key] = value
state.deviceState[key] = value.dashboard_data
}
}
@@ -453,7 +448,6 @@ def listDevices() {
}
def apiGet(String path, Map query, Closure callback) {
if(now() >= state.tokenExpires) {
refreshToken();
}
@@ -473,16 +467,12 @@ def apiGet(String path, Map query, Closure callback) {
} catch (Exception e) {
// This is most likely due to an invalid token. Try to refresh it and try again.
log.debug "apiGet: Call failed $e"
if(refreshToken()) {
log.debug "apiGet: Trying again after refreshing token"
try {
httpGet(params) { response ->
callback.call(response)
}
} catch (Exception f) {
log.debug "apiGet: Call failed $f"
}
}
if(refreshToken()) {
log.debug "apiGet: Trying again after refreshing token"
httpGet(params) { response ->
callback.call(response)
}
}
}
}
@@ -571,4 +561,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}
}

View File

@@ -57,7 +57,7 @@ def authPage(){
atomicState.accessToken = state.accessToken
}
def redirectUrl = oauthInitUrl()
def redirectUrl = oauthInitUrl()
def uninstallAllowed = false
def oauthTokenProvided = false
if(atomicState.authToken){
@@ -78,9 +78,9 @@ def authPage(){
}
}else{
return dynamicPage(name: "auth", title: "Step 1 of 2 - Completed", nextPage:"deviceList", uninstall:uninstallAllowed) {
section(){
section(){
paragraph "You are logged in to myplantlink.com, tap next to continue", image: iconUrl
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
}
}
}
@@ -137,44 +137,36 @@ def dock_sensor(device_serial, expected_plant_name) {
contentType: "application/json",
]
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
try {
httpPost(docking_params) { docking_response ->
if (parse_api_response(docking_response, "Docking a link")) {
if (docking_response.data.plants.size() == 0) {
log.debug "creating plant for - ${expected_plant_name}"
plant_post_body_map["name"] = expected_plant_name
plant_post_body_map['links_key'] = [docking_response.data.key]
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
plant_post_params["body"] = plant_post_body_json_builder.toString()
try {
httpPost(plant_post_params) { plant_post_response ->
if(parse_api_response(plant_post_response, 'creating plant')){
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant_post_response.data
atomicState.attached_sensors = attached_map
}
}
} catch (Exception f) {
log.debug "call failed $f"
httpPost(docking_params) { docking_response ->
if (parse_api_response(docking_response, "Docking a link")) {
if (docking_response.data.plants.size() == 0) {
log.debug "creating plant for - ${expected_plant_name}"
plant_post_body_map["name"] = expected_plant_name
plant_post_body_map['links_key'] = [docking_response.data.key]
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
plant_post_params["body"] = plant_post_body_json_builder.toString()
httpPost(plant_post_params) { plant_post_response ->
if(parse_api_response(plant_post_response, 'creating plant')){
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant_post_response.data
atomicState.attached_sensors = attached_map
}
} else {
def plant = docking_response.data.plants[0]
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant
atomicState.attached_sensors = attached_map
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
}
} else {
def plant = docking_response.data.plants[0]
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant
atomicState.attached_sensors = attached_map
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
}
}
} catch (Exception e) {
log.debug "call failed $e"
}
return true
}
def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
def plant_put_params = [
uri : appSettings.https_plantLinkServer,
uri : appSettings.https_plantLinkServer,
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType : "application/json"
]
@@ -182,16 +174,12 @@ def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
log.debug "updating plant for - ${expected_plant_name}"
plant_put_params["path"] = "/api/v1/plants/${plant.key}"
def plant_put_body_map = [
name: expected_plant_name
name: expected_plant_name
]
def plant_put_body_json_builder = new JsonBuilder(plant_put_body_map)
plant_put_params["body"] = plant_put_body_json_builder.toString()
try {
httpPut(plant_put_params) { plant_put_response ->
parse_api_response(plant_put_response, 'updating plant name')
}
} catch (Exception e) {
log.debug "call failed $e"
httpPut(plant_put_params) { plant_put_response ->
parse_api_response(plant_put_response, 'updating plant name')
}
}
}
@@ -210,29 +198,25 @@ def moistureHandler(event){
contentType: "application/json",
body: event.value
]
try {
httpPost(measurement_post_params) { measurement_post_response ->
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
measurement_post_response.data.size() >0){
def measurement = measurement_post_response.data[0]
def plant = measurement.plant
log.debug plant
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
plantlinksensors.each{ sensor_device ->
if (sensor_device.id == event.deviceId){
sensor_device.setStatusIcon(plant.status)
if (plant.last_measurements && plant.last_measurements[0].moisture){
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
}
if (plant.last_measurements && plant.last_measurements[0].battery){
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
}
httpPost(measurement_post_params) { measurement_post_response ->
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
measurement_post_response.data.size() >0){
def measurement = measurement_post_response.data[0]
def plant = measurement.plant
log.debug plant
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
plantlinksensors.each{ sensor_device ->
if (sensor_device.id == event.deviceId){
sensor_device.setStatusIcon(plant.status)
if (plant.last_measurements && plant.last_measurements[0].moisture){
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
}
if (plant.last_measurements && plant.last_measurements[0].battery){
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
}
}
}
}
} catch (Exception e) {
log.debug "call failed $e"
}
}
}
@@ -251,12 +235,8 @@ def batteryHandler(event){
contentType: "application/json",
body: event.value
]
try {
httpPost(measurement_post_params) { measurement_post_response ->
parse_api_response(measurement_post_response, 'creating battery measurement')
}
} catch (Exception e) {
log.debug "call failed $e"
httpPost(measurement_post_params) { measurement_post_response ->
parse_api_response(measurement_post_response, 'creating battery measurement')
}
}
}
@@ -268,7 +248,7 @@ def getDeviceSerialFromEvent(event){
}
def oauthInitUrl(){
atomicState.oauthInitState = UUID.randomUUID().toString()
atomicState.oauthInitState = UUID.randomUUID().toString()
def oauthParams = [
response_type: "code",
client_id: appSettings.client_id,
@@ -295,12 +275,8 @@ def swapToken(){
]
def jsonMap
try {
httpPost(postParams) { resp ->
jsonMap = resp.data
}
} catch (Exception e) {
log.debug "call failed $e"
httpPost(postParams) { resp ->
jsonMap = resp.data
}
atomicState.refreshToken = jsonMap.refresh_token
@@ -311,33 +287,33 @@ def swapToken(){
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.container {
padding:25px;
}
.flex1 {
width:33%;
float:left;
text-align: center;
}
p {
font-size: 2em;
font-family: Verdana, Geneva, sans-serif;
text-align: center;
color: #777;
}
.container {
padding:25px;
}
.flex1 {
width:33%;
float:left;
text-align: center;
}
p {
font-size: 2em;
font-family: Verdana, Geneva, sans-serif;
text-align: center;
color: #777;
}
</style>
</head>
<body>
<div class="container">
<div class="flex1"><img src="https://dashboard.myplantlink.com/images/PLlogo.png" alt="PlantLink" height="75"/></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected to" height="25" style="padding-top:25px;" /></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings" height="75"/></div>
<br clear="all">
<div class="container">
<div class="flex1"><img src="https://dashboard.myplantlink.com/images/PLlogo.png" alt="PlantLink" height="75"/></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected to" height="25" style="padding-top:25px;" /></div>
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings" height="75"/></div>
<br clear="all">
</div>
<div class="container">
<p>Your PlantLink Account is now connected to SmartThings!</p>
<p style="color:green;">Click <strong>Done</strong> at the top right to finish setup.</p>
</div>
<p>Your PlantLink Account is now connected to SmartThings!</p>
<p style="color:green;">Click <strong>Done</strong> at the top right to finish setup.</p>
</div>
</body>
</html>
"""

View File

@@ -0,0 +1,347 @@
/**
* PlatinumGateway Service Manager
*
* Author: Schwark Satyavolu
*. nc -i3 <ip-address-of-gateway> 522 < input.txt > output.txt
*
*/
definition(
name: "Hunter Douglas Platinum Gateway",
namespace: "schwark",
author: "Schwark Satyavolu",
description: "Allows you to connect your Hunter Douglas Platinum Gateway shades with SmartThings and control them from your Things area or Dashboard in the SmartThings Mobile app. Adjust colors by going to the Thing detail screen for your PlatinumGateway shades (tap the gear on PlatinumGateway tiles).",
category: "SmartThings Labs",
iconUrl: "https://lh5.ggpht.com/FN3-xG6R0q9VjJHYE1iK5K2J11rTphiDEePr8XluI6o_s52xfPoHwt0-TZxc0qlVSQ=w300",
iconX2Url: "https://lh5.ggpht.com/FN3-xG6R0q9VjJHYE1iK5K2J11rTphiDEePr8XluI6o_s52xfPoHwt0-TZxc0qlVSQ=w300",
singleInstance: true
)
preferences {
input("gatewayIP", "string", title:"Gateway IP Address", description: "Please enter your gateway's IP Address", required: true, displayDuringSetup: true)
input("statusURL", "string", title:"Gateway Status URL", description: "Please enter the URL to download status", required: true, displayDuringSetup: true)
input("scenePrefix", "string", title:"Scene Name Prefix", description: "Please choose a prefix to add to all the Scenes", required: false, displayDuringSetup: true, defaultValue: "Shade Scene " )
input("shadePrefix", "string", title:"Shade Name Prefix", description: "Please choose a prefix to add to all the Shades", required: false, displayDuringSetup: true, defaultValue: "Shade " )
input("wantShades", "bool", title:"Do you want to add each Shade as a Switch?", description: "Turning this on will add one switch for EACH shade in your house", required: false, displayDuringSetup: true, defaultValue: false )
}
def makeNetworkId(ipaddr, port) {
String hexIp = ipaddr.tokenize('.').collect {String.format('%02X', it.toInteger()) }.join()
String hexPort = String.format('%04X', port.toInteger())
log.debug "The target device is configured as: ${hexIp}:${hexPort}"
return "${hexIp}:${hexPort}"
}
/////////////////////////////////////
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def uninstalled() {
log.debug("Uninstalling with settings: ${settings}")
unschedule()
if(state.scenes) {
// remove scene child devices
state.scenes = [:]
}
if(state.shades) {
// remove window child devices
state.shades = [:]
}
removeChildDevices(getChildDevices())
}
/////////////////////////////////////
def updated() {
//log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
/////////////////////////////////////
def initialize() {
// remove location subscription aftwards
unsubscribe()
state.subscribe = false
log.debug("gatewayIP is ${gatewayIP}")
if (gatewayIP) {
addgateway()
}
runEvery5Minutes(doDeviceSync)
}
def getHubId() {
return state.hubId ? state.hubId : location.hubs[0].id
}
/////////////////////////////////////
def addgateway() {
if(!state.gatewayHex) {
state.gatewayHex = makeNetworkId(gatewayIP,522)
}
}
/////////////////////////////////////
def locationHandler(evt) {
log.debug "$locationHandler(evt.description)"
def description = evt.description
def hub = evt?.hubId
state.hubId = hub
log.debug("location handler: event description is ${description}")
}
/////////////////////////////////////
private def parseEventMessage(Map event) {
//handles gateway attribute events
return event
}
private def parseEventMessage(String description) {
}
/////////////////////////////////////
def doDeviceSync(){
log.debug "Doing Platinum Gateway Device Sync!"
if(!state.subscribe) {
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
if(statusURL) {
try {
httpGet(statusURL) { resp ->
resp.headers.each {
log.debug "${it.name} : ${it.value}"
}
log.debug "response contentType: ${resp.contentType}"
//log.trace "response data: ${resp.data}"
if(resp.status == 200) {
state.statusText = resp.data
}
}
} catch (e) {
log.error "something went wrong: $e"
}
}
updateStatus()
}
def processState(info) {
log.debug("processing state...")
def DB = ['rooms':[:], 'shades':[:], 'scenes':[:]]
def prefix = ""
//def lines = info.split(/[\n\r]+/)
info.eachLine() { line ->
line = line.trim()
if(!prefix) {
prefix = line[0..1]
log.debug("prefix is set to ${prefix}")
}
else if(!line.startsWith(prefix)) {
return
}
line = line.drop(2)
//log.trace("processing line ${line}")
if(line.startsWith("\$cr")) {
// name of room
def room_id = line[3..4]
def room_name = line.split('-')[-1].trim()
log.debug("found room with ${room_id} and ${room_name}")
DB['rooms'][room_id] = ['name':room_name, 'id':room_id, 'search':room_name.toLowerCase()]
} else if(line.startsWith("\$cm")) {
// name of scene
def scene_id = line[3..4]
def scene_name = line.split('-')[-1].trim()
log.debug("found scene with ${scene_id} and ${scene_name}")
DB['scenes'][scene_id] = ['name':scene_name, 'id':scene_id, 'search':scene_name.toLowerCase()]
} else if(line.startsWith("\$cs")) {
// name of a shade
def parts = line.split('-')
def shade_id = line[3..4]
def shade_name = parts[-1].trim()
def room_id = parts[1]
log.debug("found shade with ${shade_id} and ${shade_name}")
DB['shades'][shade_id] = ['name':shade_name, 'id':shade_id, 'search':shade_name.toLowerCase(), 'room': room_id]
} else if(line.startsWith("\$cp")) {
// state of a shade
def shade_id = line[3..4]
def stateTxt = line[-4..-2]
def state = stateTxt.toInteger()/255.0
log.debug("found shade state with ${shade_id} and ${state}")
def shade = DB['shades'][shade_id]
if(shade) {
DB['shades'][shade_id]['state'] = state
}
}
}
log.debug("DB is ${DB}")
return DB
}
////////////////////////////////////////////
//CHILD DEVICE METHODS
/////////////////////////////////////
def parse(childDevice, description) {
def parsedEvent = parseEventMessage(description)
if (parsedEvent.headers && parsedEvent.body) {
def headerString = new String(parsedEvent.headers.decodeBase64())
def bodyString = new String(parsedEvent.body.decodeBase64())
log.debug "parse() - ${bodyString}"
} else {
log.debug "parse - got something other than headers,body..."
return []
}
}
def sendMessage(params) {
def newDNI = state.gatewayHex
if(newDNI) {
log.debug("sending ${params.msg} to ${newDNI}")
def ha = new physicalgraph.device.HubAction(params.msg,physicalgraph.device.Protocol.LAN, newDNI)
sendHubCommand(ha)
}
}
/////////////////////////////////////
def runScene(sceneID) {
log.debug "Running Scene ${sceneID}"
sceneID = String.format('%02d',sceneID.toInteger())
def msg = "\$inm${sceneID}-"
sendMessage(["msg":msg])
}
def setShadeLevel(shadeNo, percent) {
log.debug "Setting Shade level on Shade ${shadeNo} to ${percent}%"
def shadeValue = 255 - (percent * 2.55).toInteger()
log.debug "Setting Shade level on Shade ${shadeNo} to ${shadeValue} value"
def msg = String.format("\$pss%s-04-%03d",shadeNo,shadeValue)
sendMessage(["msg":msg])
runIn(1, "sendMessage", [overwrite: false, data:["msg":"\$rls"]])
}
def updateScenes(DB) {
log.debug("Updating Scenes...")
if(!state.scenes) {
state.scenes = [:]
}
state.scenes.each() { id, sceneDevice ->
if(DB['scenes'][id]) {
// update device
if(DB['scenes'][id]['name'] != sceneDevice.label) {
log.debug("processing scene ${id} from name ${sceneDevice.label} to ${DB['scenes'][id]['name']}")
sceneDevice.sendEvent(name:'label', value: DB['scenes'][id]['name'], isStateChange: true)
}
DB['scenes'].remove(id)
} else {
// remove device
log.debug("removing scene ${id} from name ${sceneDevice.displayName}")
deleteChildDevice(sceneDevice.deviceNetworkId)
}
}
def namePrefix = scenePrefix
if(namePrefix) {
namePrefix = namePrefix.trim()+" "
}
DB['scenes']?.each() { id, sceneMap ->
def name = sceneMap['name']
log.debug("processing scene ${id} with name ${name}")
def PREFIX = "PLATINUMGATEWAYSCENE"
def hubId = getHubId()
def sceneDevice = addChildDevice("schwark", "Platinum Gateway Scene Switch", "${PREFIX}${id}", hubId, ["name": "PlatinumScene.${id}", "label": "${namePrefix}${name}", "completedSetup": true])
log.debug("created child device ${PREFIX}${id} for scene ${id} with name ${name} and hub ${hubId}")
sceneDevice.setSceneNo(id)
state.scenes[id] = sceneDevice
}
}
def updateShades(DB) {
if(!wantShades) return
log.debug("Updating Shades...")
if(!state.shades) {
state.shades = [:]
}
state.shades.each() { id, shadeDevice ->
if(DB['shades'][id]) {
// update device
if(DB['shades'][id]['name'] != shadeDevice.label) {
log.debug("processing shade rename ${id} from name ${shadeDevice.label} to ${DB['shades'][id]['name']}")
shadeDevice.sendEvent(name:'label', value: DB['shades'][id]['name'], isStateChange: true)
}
DB['shades'].remove(id)
} else {
// remove device
log.debug("removing shade ${id} from name ${shadeDevice.displayName}")
deleteChildDevice(shadeDevice.deviceNetworkId)
}
}
def namePrefix = shadePrefix
if(namePrefix) {
namePrefix = namePrefix.trim()+" "
}
DB['shades']?.each() { id, shadeMap ->
def name = shadeMap['name']
log.debug("processing shade ${id} with name ${name}")
def PREFIX = "PLATINUMGATEWAYSHADE"
def hubId = getHubId()
def shadeDevice = addChildDevice("schwark", "Platinum Gateway Shade Switch", "${PREFIX}${id}", hubId, ["name": "PlatinumShade.${id}", "label": "${namePrefix}${name}", "completedSetup": true])
log.debug("created child device ${PREFIX}${id} for shade ${id} with name ${name} and hub ${hubId}")
shadeDevice.setShadeNo(id)
state.shades[id] = shadeDevice
}
}
def updateStatus() {
if(!state.statusText) {
log.debug("statusText is empty - ${state.statusText}.")
return
}
log.debug ("Updating status")
def DB = processState(state.statusText)
updateScenes(DB)
updateShades(DB)
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private Boolean canInstallLabs()
{
return hasAllHubsOver("000.011.00603")
}
private Boolean hasAllHubsOver(String desiredFirmware)
{
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
private List getRealHubFirmwareVersions()
{
return location.hubs*.firmwareVersionString.findAll { it }
}
private removeChildDevices(data) {
data.delete.each {
deleteChildDevice(it.deviceNetworkId)
}
}