Compare commits

..

12 Commits

Author SHA1 Message Date
Tim Slagle
4a14421ef0 test: test 2017-02-01 14:46:54 -08:00
Vinay Rao
de1894bfbf Merge pull request #1630 from SmartThingsCommunity/staging
Rolling down staging to master
2017-01-31 13:47:45 -08:00
Jason Botello
d0f8ec87bd Merge pull request #1616 from jasonbio/plantlink-exceptions
Plant link Exceptions
2017-01-30 15:01:18 -08:00
Jason Botello
1383ab1e07 Merge pull request #1615 from jasonbio/netatmo-exceptions
Netatmo Exceptions
2017-01-30 15:01:05 -08:00
Vinay Rao
d4f21b95d7 Merge pull request #1621 from varzac/zll-bulb-reporting
[DPROT-227] Manually refresh on/off status after setLevel
2017-01-27 16:37:34 -08:00
Zach Varberg
be2e19e406 Manually refresh on/off status after setLevel
This is because ZLL bulbs do not report or allow for configuration of
reporting of attributes.  As a result if we change a value, we have to
manually call a read of that attribute to update our status.

This is a part of: https://smartthings.atlassian.net/browse/DPROT-227
2017-01-26 11:27:51 -06:00
Vinay Rao
54da556c17 Merge pull request #1620 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2017-01-25 15:48:58 -08:00
Vinay Rao
8611d2e2d2 Merge pull request #1618 from workingmonk/feature/light_capability
ICP-203 Adding Light Capability to hue lights
2017-01-25 04:13:47 -08:00
Vinay Rao
c650047f31 ICP-203 Adding Light Capability to hue lights 2017-01-25 04:12:42 -08:00
Jason Botello
41adc9777a Plant link Exceptions
Adding try/catch blocks to reduce HTTP exceptions due to rate limits or
unauthorized errors.
2017-01-24 14:53:23 -08:00
Jason Botello
f334f6505a Netatmo Exceptions
Adding try/catch blocks to reduce HTTP exceptions due to rate limits or
unauthorized errors. Also updating the “devicelist” endpoint to
“getstationdata” as the former is deprecated
2017-01-24 14:30:59 -08:00
Vinay Rao
ded7501b84 Merge pull request #1614 from SmartThingsCommunity/master
Rolling up master to staging
2017-01-24 13:21:44 -08:00
15 changed files with 184 additions and 604 deletions

View File

@@ -0,0 +1,46 @@
/**
* 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

@@ -1,71 +0,0 @@
/**
* 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

@@ -1,86 +0,0 @@
/**
* 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) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
/**

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ 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) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + zigbee.onOffRefresh() + 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) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + zigbee.onOffRefresh() + 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) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
zigbee.setLevel(value) + zigbee.onOffRefresh() + 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) + ["delay 1500"] + zigbee.levelRefresh()
zigbee.setLevel(value) + zigbee.onOffRefresh() + 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,19 +146,24 @@ def callback() {
// log.debug "PARAMS: ${params}"
httpPost(params) { resp ->
try {
httpPost(params) { resp ->
def slurper = new JsonSlurper()
def slurper = new JsonSlurper()
resp.data.each { key, value ->
def data = slurper.parseText(key)
state.refreshToken = data.refresh_token
state.authToken = data.access_token
state.tokenExpires = now() + (data.expires_in * 1000)
// log.debug "swapped token: $resp.data"
}
}
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"
}
// Handle success and failure here, and render stuff accordingly
if (state.authToken) {
@@ -387,18 +392,18 @@ def getDeviceList() {
state.deviceDetail = [:]
state.deviceState = [:]
apiGet("/api/devicelist") { response ->
apiGet("/api/getstationsdata") { 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
}
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
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
}
}
}
@@ -448,6 +453,7 @@ def listDevices() {
}
def apiGet(String path, Map query, Closure callback) {
if(now() >= state.tokenExpires) {
refreshToken();
}
@@ -467,12 +473,16 @@ 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"
httpGet(params) { response ->
callback.call(response)
}
}
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"
}
}
}
}
@@ -561,4 +571,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,36 +137,44 @@ def dock_sensor(device_serial, expected_plant_name) {
contentType: "application/json",
]
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
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
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"
}
} 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"
]
@@ -174,12 +182,16 @@ 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()
httpPut(plant_put_params) { plant_put_response ->
parse_api_response(plant_put_response, 'updating plant name')
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"
}
}
}
@@ -198,25 +210,29 @@ def moistureHandler(event){
contentType: "application/json",
body: event.value
]
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)
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)
}
}
}
}
}
} catch (Exception e) {
log.debug "call failed $e"
}
}
}
@@ -235,8 +251,12 @@ def batteryHandler(event){
contentType: "application/json",
body: event.value
]
httpPost(measurement_post_params) { measurement_post_response ->
parse_api_response(measurement_post_response, 'creating battery measurement')
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"
}
}
}
@@ -248,7 +268,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,
@@ -275,8 +295,12 @@ def swapToken(){
]
def jsonMap
httpPost(postParams) { resp ->
jsonMap = resp.data
try {
httpPost(postParams) { resp ->
jsonMap = resp.data
}
} catch (Exception e) {
log.debug "call failed $e"
}
atomicState.refreshToken = jsonMap.refresh_token
@@ -287,33 +311,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

@@ -1,347 +0,0 @@
/**
* 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)
}
}