mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-25 05:04:09 +00:00
Compare commits
18 Commits
MSA-1097-1
...
MSA-1152-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26ac4343ee | ||
|
|
82214e29eb | ||
|
|
bf00284c74 | ||
|
|
8de5ed77f4 | ||
|
|
4d31f8dbe8 | ||
|
|
9d378ce9a1 | ||
|
|
8abe4ac29f | ||
|
|
104fa8d616 | ||
|
|
5b5e185ef0 | ||
|
|
3a0c9c1298 | ||
|
|
51fb7fc7a9 | ||
|
|
4a096fc884 | ||
|
|
babc0206df | ||
|
|
d419fb8606 | ||
|
|
70aae0d76c | ||
|
|
1cd5c68e68 | ||
|
|
9e427d4108 | ||
|
|
a8628b7343 |
78
build.gradle
78
build.gradle
@@ -1,13 +1,15 @@
|
|||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
import com.smartthings.deployment.slack.FileUpload
|
||||||
|
import com.smartthings.deployment.slack.Message
|
||||||
|
|
||||||
apply plugin: 'groovy'
|
apply plugin: 'groovy'
|
||||||
apply plugin: 'smartthings-executable-deployment'
|
apply plugin: 'smartthings-executable-deployment'
|
||||||
apply plugin: 'smartthings-hipchat'
|
apply plugin: 'smartthings-slack'
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.6"
|
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.7"
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@@ -30,7 +32,43 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
hipchatShareFile {
|
slackSendMessage {
|
||||||
|
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
|
||||||
|
String token = project.hasProperty('slackToken') ? project.property('slackToken') : null
|
||||||
|
String webhookUrl = project.hasProperty('slackWebhookUrl') ? project.property('slackWebhookUrl') : null
|
||||||
|
String channel = project.hasProperty('slackChannel') ? project.property('slackChannel') : null
|
||||||
|
String drinks = 'https://dl.dropboxusercontent.com/s/m1z5mpd3c83lwev/minion_beer.jpeg?dl=0'
|
||||||
|
String wolverine = 'https://dl.dropboxusercontent.com/s/4lbjqzvm2v033u9/minion_wolverine.jpg?dl=0'
|
||||||
|
String beach = 'https://dl.dropboxusercontent.com/s/rqrfgxk53gfng69/minion_beach.png?dl=0'
|
||||||
|
String iconUrl
|
||||||
|
String color
|
||||||
|
String messageText
|
||||||
|
String username
|
||||||
|
switch (branch) {
|
||||||
|
case 'master':
|
||||||
|
username = 'Hickory'
|
||||||
|
iconUrl = wolverine
|
||||||
|
color = '#35D0F2'
|
||||||
|
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
|
||||||
|
break
|
||||||
|
case 'staging':
|
||||||
|
username = 'Dickory'
|
||||||
|
iconUrl = beach
|
||||||
|
color = '#FFDE20'
|
||||||
|
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
|
||||||
|
break
|
||||||
|
case 'production':
|
||||||
|
username = 'Dock'
|
||||||
|
iconUrl = drinks
|
||||||
|
color = '#FF1D23'
|
||||||
|
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
username = 'Hickory'
|
||||||
|
iconUrl = wolverine
|
||||||
|
color = '#35D0F2'
|
||||||
|
messageText = "Began deployment of an _SmartThingsPublic[${branch}]_ branch. Have no idea what's going on."
|
||||||
|
}
|
||||||
List<String> archives = []
|
List<String> archives = []
|
||||||
File rootDir = new File("${project.buildDir}/archives")
|
File rootDir = new File("${project.buildDir}/archives")
|
||||||
if (rootDir.exists()) {
|
if (rootDir.exists()) {
|
||||||
@@ -43,19 +81,25 @@ hipchatShareFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Date date = new Date()
|
||||||
|
String fileDate = date.format('yyyy-MM-dd_HH-mm-ss', TimeZone.getTimeZone('GMT'))
|
||||||
|
|
||||||
// Set task properties
|
// Required Task Arguments.
|
||||||
data = archives.join('\n').getBytes(StandardCharsets.UTF_8)
|
file = new FileUpload(
|
||||||
fileName = 'deployment-notes.txt'
|
data: archives.join('\n').getBytes(StandardCharsets.UTF_8),
|
||||||
contentType = 'text/html'
|
filename: "deployment-notes-${fileDate}.txt",
|
||||||
}
|
title: 'Deployment Notes',
|
||||||
|
channels: channel,
|
||||||
hipchatSendNotification {
|
token: token,
|
||||||
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
|
color: color
|
||||||
message = "Began executable deploy of SmartThingsPublic(${branch})."
|
)
|
||||||
if (branch == 'master') {
|
message = new Message(
|
||||||
message += ' (dev shards)'
|
webhookUrl: webhookUrl,
|
||||||
}
|
username: username,
|
||||||
color = branch == 'master' ? 'yellow' : 'red'
|
asUser: true,
|
||||||
notify = true
|
iconUrl: iconUrl,
|
||||||
|
channel: channel,
|
||||||
|
fallback: 'Deployment Notification',
|
||||||
|
text: messageText
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
10
circle.yml
10
circle.yml
@@ -15,13 +15,11 @@ deployment:
|
|||||||
develop:
|
develop:
|
||||||
branch: master
|
branch: master
|
||||||
commands:
|
commands:
|
||||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3Buckets="$S3_BUCKETS_DEV"
|
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_DEV"
|
||||||
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
|
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
||||||
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD
|
|
||||||
|
|
||||||
stage:
|
stage:
|
||||||
branch: staging
|
branch: staging
|
||||||
commands:
|
commands:
|
||||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3Buckets="$S3_BUCKETS_STAGE"
|
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_STAGE"
|
||||||
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
|
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
||||||
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD
|
|
||||||
|
|||||||
194
devicetypes/mitchpond/iris-smart-fob.src/iris-smart-fob.groovy
Normal file
194
devicetypes/mitchpond/iris-smart-fob.src/iris-smart-fob.groovy
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* Iris Smart Fob
|
||||||
|
*
|
||||||
|
* Copyright 2015 Mitch Pond
|
||||||
|
* Presence code adapted from SmartThings Arrival Sensor HA device type
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||||
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
|
* for the specific language governing permissions and limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
metadata {
|
||||||
|
definition (name: "Iris Smart Fob", namespace: "mitchpond", author: "Mitch Pond") {
|
||||||
|
capability "Battery"
|
||||||
|
capability "Button"
|
||||||
|
capability "Configuration"
|
||||||
|
capability "Presence Sensor"
|
||||||
|
capability "Sensor"
|
||||||
|
|
||||||
|
//fingerprint endpointId: "01", profileId: "0104", inClusters: "0000,0001,0003,0007,0020,0B05", outClusters: "0003,0006,0019", model:"3450-L", manufacturer: "CentraLite"
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences{
|
||||||
|
input ("holdTime", "number", title: "Minimum time in seconds for a press to count as \"held\"",
|
||||||
|
defaultValue: 3, displayDuringSetup: false)
|
||||||
|
input "checkInterval", "enum", title: "Presence timeout (minutes)",
|
||||||
|
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
|
||||||
|
input "logging", "bool", title: "Enable debug logging",
|
||||||
|
defaultValue: false, displayDuringSetup: false
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles(scale: 2) {
|
||||||
|
standardTile("presence", "device.presence", width: 4, height: 4, canChangeBackground: true) {
|
||||||
|
state "present", label: "Present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
|
||||||
|
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
|
||||||
|
}
|
||||||
|
standardTile("button", "device.button", decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "default", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
|
||||||
|
}
|
||||||
|
valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "battery", label:'${currentValue}% battery', unit:""
|
||||||
|
}
|
||||||
|
|
||||||
|
main (["presence"])
|
||||||
|
details(["presence","button","battery"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse(String description) {
|
||||||
|
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||||
|
logIt descMap
|
||||||
|
state.lastCheckin = now()
|
||||||
|
logIt "lastCheckin = ${state.lastCheckin}"
|
||||||
|
handlePresenceEvent(true)
|
||||||
|
|
||||||
|
def results = []
|
||||||
|
if (description?.startsWith('catchall:'))
|
||||||
|
results = parseCatchAllMessage(descMap)
|
||||||
|
else if (description?.startsWith('read attr -'))
|
||||||
|
results = parseReportAttributeMessage(descMap)
|
||||||
|
else logIt(descMap, "trace")
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
startTimer()
|
||||||
|
configure()
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure(){
|
||||||
|
logIt "Configuring Smart Fob..."
|
||||||
|
[
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 2 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 3 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 4 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 200"
|
||||||
|
] +
|
||||||
|
zigbee.configureReporting(0x0001,0x0020,0x20,20,20,0x01)
|
||||||
|
}
|
||||||
|
|
||||||
|
def parseCatchAllMessage(descMap) {
|
||||||
|
if (descMap?.clusterId == "0006" && descMap?.command == "01") //button pressed
|
||||||
|
handleButtonPress(descMap.sourceEndpoint as int)
|
||||||
|
else if (descMap?.clusterId == "0006" && descMap?.command == "00") //button released
|
||||||
|
handleButtonRelease(descMap.sourceEndpoint as int)
|
||||||
|
else logIt("Parse: Unhandled message: ${descMap}","trace")
|
||||||
|
}
|
||||||
|
|
||||||
|
def parseReportAttributeMessage(descMap) {
|
||||||
|
if (descMap?.cluster == "0001" && descMap?.attrId == "0020") createBatteryEvent(getBatteryLevel(descMap.value))
|
||||||
|
else logIt descMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBatteryEvent(percent) {
|
||||||
|
logIt "Battery level at " + percent
|
||||||
|
return createEvent([name: "battery", value: percent])
|
||||||
|
}
|
||||||
|
|
||||||
|
//this method determines if a press should count as a push or a hold and returns the relevant event type
|
||||||
|
private handleButtonRelease(button) {
|
||||||
|
logIt "lastPress state variable: ${state.lastPress}"
|
||||||
|
def sequenceError = {logIt("Uh oh...missed a message? Dropping this event.", "error"); state.lastPress = null; return []}
|
||||||
|
|
||||||
|
if (!state.lastPress) return sequenceError()
|
||||||
|
else if (state.lastPress.button != button) return sequenceError()
|
||||||
|
|
||||||
|
def currentTime = now()
|
||||||
|
def startOfPress = state.lastPress?.time
|
||||||
|
def timeDif = currentTime - startOfPress
|
||||||
|
def holdTimeMillisec = (settings.holdTime?:3).toInteger() * 1000
|
||||||
|
|
||||||
|
state.lastPress = null //we're done with this. clear it to make error conditions easier to catch
|
||||||
|
|
||||||
|
if (timeDif < 0)
|
||||||
|
//likely a message sequence issue or dropped packet. Drop this press and wait for another.
|
||||||
|
return sequenceError()
|
||||||
|
else if (timeDif < holdTimeMillisec)
|
||||||
|
return createButtonEvent(button,"pushed")
|
||||||
|
else
|
||||||
|
return createButtonEvent(button,"held")
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleButtonPress(button) {
|
||||||
|
state.lastPress = [button: button, time: now()]
|
||||||
|
}
|
||||||
|
|
||||||
|
private createButtonEvent(button,action) {
|
||||||
|
logIt "Button ${button} ${action}"
|
||||||
|
return createEvent([
|
||||||
|
name: "button",
|
||||||
|
value: action,
|
||||||
|
data:[buttonNumber: button],
|
||||||
|
descriptionText: "${device.displayName} button ${button} was ${action}",
|
||||||
|
isStateChange: true,
|
||||||
|
displayed: true])
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBatteryLevel(rawValue) {
|
||||||
|
def intValue = Integer.parseInt(rawValue,16)
|
||||||
|
def min = 2.1
|
||||||
|
def max = 3.0
|
||||||
|
def vBatt = intValue / 10
|
||||||
|
return ((vBatt - min) / (max - min) * 100) as int
|
||||||
|
}
|
||||||
|
|
||||||
|
private handlePresenceEvent(present) {
|
||||||
|
def wasPresent = device.currentState("presence")?.value == "present"
|
||||||
|
if (!wasPresent && present) {
|
||||||
|
logIt "Sensor is present"
|
||||||
|
startTimer()
|
||||||
|
} else if (!present) {
|
||||||
|
logIt "Sensor is not present"
|
||||||
|
stopTimer()
|
||||||
|
}
|
||||||
|
def linkText = getLinkText(device)
|
||||||
|
def eventMap = [
|
||||||
|
name: "presence",
|
||||||
|
value: present ? "present" : "not present",
|
||||||
|
linkText: linkText,
|
||||||
|
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
|
||||||
|
]
|
||||||
|
logIt "Creating presence event: ${eventMap}"
|
||||||
|
sendEvent(eventMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
private startTimer() {
|
||||||
|
logIt "Scheduling periodic timer"
|
||||||
|
schedule("0 * * * * ?", checkPresenceCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopTimer() {
|
||||||
|
logIt "Stopping periodic timer"
|
||||||
|
unschedule()
|
||||||
|
}
|
||||||
|
|
||||||
|
def checkPresenceCallback() {
|
||||||
|
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
|
||||||
|
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
|
||||||
|
logIt "Sensor checked in ${timeSinceLastCheckin} seconds ago"
|
||||||
|
if (timeSinceLastCheckin >= theCheckInterval) {
|
||||||
|
handlePresenceEvent(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ****** Utility functions ******
|
||||||
|
|
||||||
|
private logIt(str, logLevel = 'debug') {if (settings.logging) log."$logLevel"(str) }
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
'''Presence timeout (minutes)'''.ko=시간 초과. 스마트폰 위치 정보
|
'''Presence timeout (minutes)'''.ko=시간 초과. 스마트폰 위치 정보
|
||||||
'''Tap to set'''.ko=눌러서 설정
|
'''Tap to set'''.ko=눌러서 설정
|
||||||
'''Arrival Sensor'''.ko=도착알림 센서
|
'''Arrival Sensor'''.ko=도착알림 센서
|
||||||
|
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||||
# Events / Notifications
|
# Events / Notifications
|
||||||
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}남아있는 배터리는 {{ value }}입니다.
|
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}남아있는 배터리는 {{ value }}입니다.
|
||||||
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
|
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
|
||||||
|
|||||||
@@ -28,4 +28,8 @@
|
|||||||
'''{{ device.displayName }} is On'''.ko={{ device.displayName }}켜졌습니다.
|
'''{{ device.displayName }} is On'''.ko={{ device.displayName }}켜졌습니다.
|
||||||
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }}꺼졌습니다.
|
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }}꺼졌습니다.
|
||||||
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전원은 {{ value }}와트입니다
|
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전원은 {{ value }}와트입니다
|
||||||
|
'''On'''.ko=켜짐
|
||||||
|
'''Off'''.ko=꺼짐
|
||||||
|
'''Turning On'''.ko=켜기
|
||||||
|
'''Turning Off'''.ko=끄기
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
|
|||||||
@@ -65,10 +65,10 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||||
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||||
attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
attributeState "turningOn", label: 'Turning On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||||
attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
attributeState "turningOff", label: 'Turning Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
||||||
attributeState "power", label:'${currentValue} W'
|
attributeState "power", label:'${currentValue} W'
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Korean (ko)
|
# Korean (ko)
|
||||||
# Device Preferences
|
# Device Preferences
|
||||||
|
'''Dry'''.ko=건조
|
||||||
|
'''Wet'''.ko=누수
|
||||||
'''dry'''.ko=건조
|
'''dry'''.ko=건조
|
||||||
'''wet'''.ko=누수
|
'''wet'''.ko=누수
|
||||||
'''battery'''.ko=배터리
|
'''battery'''.ko=배터리
|
||||||
@@ -31,6 +33,7 @@
|
|||||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||||
'''Water Leak Sensor'''.ko=누수센서
|
'''Water Leak Sensor'''.ko=누수센서
|
||||||
|
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||||
# Events descriptionText
|
# Events descriptionText
|
||||||
'''{{ device.displayName }} is dry'''.ko={{ device.displayName }}가 건조
|
'''{{ device.displayName }} is dry'''.ko={{ device.displayName }}가 건조
|
||||||
'''{{ device.displayName }} is wet'''.ko={{ device.displayName }}누수
|
'''{{ device.displayName }} is wet'''.ko={{ device.displayName }}누수
|
||||||
|
|||||||
@@ -30,6 +30,9 @@
|
|||||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||||
'''Motion Sensor'''.ko=모션 센서
|
'''Motion Sensor'''.ko=모션 센서
|
||||||
|
'''motion'''.ko=동작 감지
|
||||||
|
'''no motion'''.ko=동작 없음
|
||||||
|
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||||
# Events descriptionText
|
# Events descriptionText
|
||||||
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }} 가 움직임을 감지하였습니다.
|
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }} 가 움직임을 감지하였습니다.
|
||||||
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }}움직임이 중단되었습니다
|
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }}움직임이 중단되었습니다
|
||||||
|
|||||||
@@ -43,3 +43,8 @@
|
|||||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
||||||
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
|
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
|
||||||
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
|
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
|
||||||
|
'''Inactive'''.ko=비활성
|
||||||
|
'''Active'''.ko=활성
|
||||||
|
'''Open'''.ko=열림
|
||||||
|
'''Closed'''.ko=닫힘
|
||||||
|
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
||||||
|
|||||||
@@ -83,19 +83,19 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
||||||
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
attributeState "open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||||
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
attributeState "closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||||
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
|
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
|
||||||
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
state("open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
state("closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||||
}
|
}
|
||||||
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
||||||
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
state("active", label:'Active', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
||||||
state("inactive", label:'${name}', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
state("inactive", label:'Inactive', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
||||||
}
|
}
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||||
state("temperature", label:'${currentValue}°',
|
state("temperature", label:'${currentValue}°',
|
||||||
|
|||||||
@@ -30,341 +30,3 @@ preferences {
|
|||||||
page(name: "prefLogIn", title: "MyQ")
|
page(name: "prefLogIn", title: "MyQ")
|
||||||
page(name: "prefListDevices", title: "MyQ")
|
page(name: "prefListDevices", title: "MyQ")
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Preferences */
|
|
||||||
def prefLogIn() {
|
|
||||||
def showUninstall = username != null && password != null
|
|
||||||
return dynamicPage(name: "prefLogIn", title: "Connect to MyQ", nextPage:"prefListDevices", uninstall:showUninstall, install: false) {
|
|
||||||
section("Login Credentials"){
|
|
||||||
input("username", "email", title: "Username", description: "MyQ Username (email address)")
|
|
||||||
input("password", "password", title: "Password", description: "MyQ password")
|
|
||||||
}
|
|
||||||
section("Gateway Brand"){
|
|
||||||
input(name: "brand", title: "Gateway Brand", type: "enum", metadata:[values:["Liftmaster","Chamberlain","Craftsman"]] )
|
|
||||||
}
|
|
||||||
section("Advanced Options"){
|
|
||||||
//input(name: "polling", title: "Server Polling (in Minutes)", type: "int", description: "in minutes", defaultValue: "5" )
|
|
||||||
input(name: "contactSensorTrigger", title: "Contact Sensor to trigger refresh ", type: "capability.contactSensor", required: "false", multiple: "true")
|
|
||||||
input(name: "accelerationSensorTrigger", title: "Acceleration Sensor to trigger refresh ", type: "capability.accelerationSensor", required: "false", multiple: "true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def prefListDevices() {
|
|
||||||
if (forceLogin()) {
|
|
||||||
def doorList = getDoorList()
|
|
||||||
def lightList = getLightList()
|
|
||||||
if ((doorList) || (lightList)) {
|
|
||||||
return dynamicPage(name: "prefListDevices", title: "Devices", install:true, uninstall:true) {
|
|
||||||
if (doorList) {
|
|
||||||
section("Select which garage door/gate to use"){
|
|
||||||
input(name: "doors", type: "enum", required:false, multiple:true, metadata:[values:doorList])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lightList) {
|
|
||||||
section("Select which light controller to use"){
|
|
||||||
input(name: "lights", type: "enum", required:false, multiple:true, metadata:[values:lightList])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
def devList = getDeviceList()
|
|
||||||
return dynamicPage(name: "prefListDevices", title: "Error!", install:true, uninstall:true) {
|
|
||||||
section(""){
|
|
||||||
paragraph "Could not find any supported device(s). Please report to author about these devices: " + devList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return dynamicPage(name: "prefListDevices", title: "Error!", install:false, uninstall:true) {
|
|
||||||
section(""){
|
|
||||||
paragraph "The username or password you entered is incorrect. Try again. "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialization */
|
|
||||||
def installed() { initialize() }
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
unsubscribe()
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def uninstalled() {
|
|
||||||
unsubscribe()
|
|
||||||
unschedule()
|
|
||||||
getAllChildDevices().each { deleteChildDevice(it.deviceNetworkId) }
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
login()
|
|
||||||
|
|
||||||
// Get initial device status in state.data
|
|
||||||
state.polling = [ last: 0, rescheduler: now() ]
|
|
||||||
state.data = [:]
|
|
||||||
|
|
||||||
// Create selected devices
|
|
||||||
def doorsList = getDoorList()
|
|
||||||
def lightsList = getLightList()
|
|
||||||
def selectedDevices = [] + getSelectedDevices("doors") + getSelectedDevices("lights")
|
|
||||||
|
|
||||||
selectedDevices.each {
|
|
||||||
if (!getChildDevice(it)) {
|
|
||||||
if (it.contains("GarageDoorOpener")) { addChildDevice("copy-ninja", "MyQ Garage Door Opener", it, null, ["name": "MyQ: " + doorsList[it]]) }
|
|
||||||
if (it.contains("LightController")) { addChildDevice("copy-ninja", "MyQ Light Controller", it, null, ["name": "MyQ: " + lightsList[it]]) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove unselected devices
|
|
||||||
def deleteDevices = (selectedDevices) ? (getChildDevices().findAll { !selectedDevices.contains(it.deviceNetworkId) }) : getAllChildDevices()
|
|
||||||
deleteDevices.each { deleteChildDevice(it.deviceNetworkId) }
|
|
||||||
|
|
||||||
|
|
||||||
//Subscribes to sunrise and sunset event to trigger refreshes
|
|
||||||
subscribe(location, "sunrise", runRefresh)
|
|
||||||
subscribe(location, "sunset", runRefresh)
|
|
||||||
subscribe(location, "mode", runRefresh)
|
|
||||||
subscribe(location, "sunriseTime", runRefresh)
|
|
||||||
subscribe(location, "sunsetTime", runRefresh)
|
|
||||||
|
|
||||||
//Subscribe to events from contact sensor
|
|
||||||
if (settings.contactSensorTrigger) {
|
|
||||||
subscribe(settings.contactSensorTrigger, "contact", runRefresh)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Subscribe to events from contact sensor
|
|
||||||
if (settings.accelerationSensorTrigger) {
|
|
||||||
subscribe(settings.accelerationSensorTrigger, "acceleration", runRefresh)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run refresh after installation
|
|
||||||
runRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def getSelectedDevices( settingsName ) {
|
|
||||||
def selectedDevices = []
|
|
||||||
(!settings.get(settingsName))?:((settings.get(settingsName)?.getAt(0)?.size() > 1) ? settings.get(settingsName)?.each { selectedDevices.add(it) } : selectedDevices.add(settings.get(settingsName)))
|
|
||||||
return selectedDevices
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Access Management */
|
|
||||||
private forceLogin() {
|
|
||||||
//Reset token and expiry
|
|
||||||
state.session = [ brandID: 0, brandName: settings.brand, securityToken: null, expiration: 0 ]
|
|
||||||
state.polling = [ last: 0, rescheduler: now() ]
|
|
||||||
state.data = [:]
|
|
||||||
return doLogin()
|
|
||||||
}
|
|
||||||
|
|
||||||
private login() { return (!(state.session.expiration > now())) ? doLogin() : true }
|
|
||||||
|
|
||||||
private doLogin() {
|
|
||||||
apiGet("/api/user/validate", [username: settings.username, password: settings.password] ) { response ->
|
|
||||||
if (response.status == 200) {
|
|
||||||
if (response.data.SecurityToken != null) {
|
|
||||||
state.session.brandID = response.data.BrandId
|
|
||||||
state.session.brandName = response.data.BrandName
|
|
||||||
state.session.securityToken = response.data.SecurityToken
|
|
||||||
state.session.expiration = now() + 150000
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listing all the garage doors you have in MyQ
|
|
||||||
private getDoorList() {
|
|
||||||
def deviceList = [:]
|
|
||||||
apiGet("/api/v4/userdevicedetails/get", []) { response ->
|
|
||||||
if (response.status == 200) {
|
|
||||||
response.data.Devices.each { device ->
|
|
||||||
// 2 = garage door, 5 = gate, 7 = MyQGarage(no gateway), 17 = Garage Door Opener WGDO
|
|
||||||
if (device.MyQDeviceTypeId == 2||device.MyQDeviceTypeId == 5||device.MyQDeviceTypeId == 7||device.MyQDeviceTypeId == 17) {
|
|
||||||
def dni = [ app.id, "GarageDoorOpener", device.MyQDeviceId ].join('|')
|
|
||||||
device.Attributes.each {
|
|
||||||
if (it.AttributeDisplayName=="desc") deviceList[dni] = it.Value
|
|
||||||
if (it.AttributeDisplayName=="doorstate") {
|
|
||||||
state.data[dni] = [ status: it.Value, lastAction: it.UpdatedTime ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return deviceList
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listing all the light controller you have in MyQ
|
|
||||||
private getLightList() {
|
|
||||||
def deviceList = [:]
|
|
||||||
apiGet("/api/v4/userdevicedetails/get", []) { response ->
|
|
||||||
if (response.status == 200) {
|
|
||||||
response.data.Devices.each { device ->
|
|
||||||
if (device.MyQDeviceTypeId == 3) {
|
|
||||||
def dni = [ app.id, "LightController", device.MyQDeviceId ].join('|')
|
|
||||||
device.Attributes.each {
|
|
||||||
if (it.AttributeDisplayName=="desc") { deviceList[dni] = it.Value }
|
|
||||||
if (it.AttributeDisplayName=="lightstate") { state.data[dni] = [ status: it.Value ] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return deviceList
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDeviceList() {
|
|
||||||
def deviceList = []
|
|
||||||
apiGet("/api/v4/userdevicedetails/get", []) { response ->
|
|
||||||
if (response.status == 200) {
|
|
||||||
response.data.Devices.each { device ->
|
|
||||||
log.debug "MyQDeviceTypeId : " + device.MyQDeviceTypeId.toString()
|
|
||||||
if (!(device.MyQDeviceTypeId == 1||device.MyQDeviceTypeId == 2||device.MyQDeviceTypeId == 3||device.MyQDeviceTypeId == 5||device.MyQDeviceTypeId == 7)) {
|
|
||||||
deviceList.add( device.MyQDeviceTypeId.toString() + "|" + device.TypeID )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return deviceList
|
|
||||||
}
|
|
||||||
|
|
||||||
/* api connection */
|
|
||||||
// get URL
|
|
||||||
private getApiURL() {
|
|
||||||
if (settings.brand == "Craftsman") {
|
|
||||||
return "https://craftexternal.myqdevice.com"
|
|
||||||
} else {
|
|
||||||
return "https://myqexternal.myqdevice.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getApiAppID() {
|
|
||||||
if (settings.brand == "Craftsman") {
|
|
||||||
return "QH5AzY8MurrilYsbcG1f6eMTffMCm3cIEyZaSdK/TD/8SvlKAWUAmodIqa5VqVAs"
|
|
||||||
} else {
|
|
||||||
return "JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP GET call
|
|
||||||
private apiGet(apiPath, apiQuery = [], callback = {}) {
|
|
||||||
// set up query
|
|
||||||
apiQuery = [ appId: getApiAppID() ] + apiQuery
|
|
||||||
if (state.session.securityToken) { apiQuery = apiQuery + [SecurityToken: state.session.securityToken ] }
|
|
||||||
|
|
||||||
try {
|
|
||||||
httpGet([ uri: getApiURL(), path: apiPath, query: apiQuery ]) { response -> callback(response) }
|
|
||||||
} catch (SocketException e) {
|
|
||||||
log.debug "API Error: $e"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP PUT call
|
|
||||||
private apiPut(apiPath, apiBody = [], callback = {}) {
|
|
||||||
// set up body
|
|
||||||
apiBody = [ ApplicationId: getApiAppID() ] + apiBody
|
|
||||||
if (state.session.securityToken) { apiBody = apiBody + [SecurityToken: state.session.securityToken ] }
|
|
||||||
|
|
||||||
// set up query
|
|
||||||
def apiQuery = [ appId: getApiAppID() ]
|
|
||||||
if (state.session.securityToken) { apiQuery = apiQuery + [SecurityToken: state.session.securityToken ] }
|
|
||||||
|
|
||||||
try {
|
|
||||||
httpPut([ uri: getApiURL(), path: apiPath, contentType: "application/json; charset=utf-8", body: apiBody, query: apiQuery ]) { response -> callback(response) }
|
|
||||||
} catch (SocketException e) {
|
|
||||||
log.debug "API Error: $e"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates data for devices
|
|
||||||
private updateDeviceData() {
|
|
||||||
// automatically checks if the token has expired, if so login again
|
|
||||||
if (login()) {
|
|
||||||
// set polling states
|
|
||||||
state.polling["last"] = now()
|
|
||||||
|
|
||||||
// Get all the door information, updated to state.data
|
|
||||||
return (getDoorList()||getLightList())? true : false
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* for SmartDevice to call */
|
|
||||||
// Refresh data
|
|
||||||
def refresh() {
|
|
||||||
//force devices to poll to get the latest status
|
|
||||||
if (updateDeviceData()) {
|
|
||||||
log.info "Refreshing data..."
|
|
||||||
// get all the children and send updates
|
|
||||||
getAllChildDevices().each {
|
|
||||||
//log.debug "Polling " + it.deviceNetworkId
|
|
||||||
it.updateDeviceStatus(state.data[it.deviceNetworkId].status)
|
|
||||||
if (it.deviceNetworkId.contains("GarageDoorOpener")) {
|
|
||||||
it.updateDeviceLastActivity(state.data[it.deviceNetworkId].lastAction.toLong())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//schedule the rescheduler to schedule refresh ;)
|
|
||||||
if ((state.polling["rescheduler"]?:0) + 2400000 < now()) {
|
|
||||||
log.info "Scheduling Auto Rescheduler.."
|
|
||||||
runEvery30Minutes(runRefresh)
|
|
||||||
state.polling["rescheduler"] = now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Device ID
|
|
||||||
def getChildDeviceID(child) {
|
|
||||||
return child.device.deviceNetworkId.split("\\|")[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get single device status
|
|
||||||
def getDeviceStatus(child) {
|
|
||||||
return state.data[child.device.deviceNetworkId].status
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get single device last activity
|
|
||||||
def getDeviceLastActivity(child) {
|
|
||||||
return state.data[child.device.deviceNetworkId].lastAction.toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send command to start or stop
|
|
||||||
def sendCommand(child, attributeName, attributeValue) {
|
|
||||||
if (login()) {
|
|
||||||
//Send command
|
|
||||||
apiPut("/api/v4/deviceattribute/putdeviceattribute", [ MyQDeviceId: getChildDeviceID(child), AttributeName: attributeName, AttributeValue: attributeValue ])
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def runRefresh(evt) {
|
|
||||||
if (evt) {
|
|
||||||
log.info "Event " + evt.displayName + " triggered refresh"
|
|
||||||
runIn(30, delayedRefresh) //schedule a refresh
|
|
||||||
}
|
|
||||||
log.info "Last refresh was " + ((now() - state.polling["last"])/60000) + " minutes ago"
|
|
||||||
// Reschedule if didn't update for more than 5 minutes plus specified polling
|
|
||||||
//if ((((state.polling["last"]?:0) + ((((!settings.polling)?:(settings.polling.toInteger() > 0 )? settings.polling.toInteger() : 5)) * 60000) + 300000) < now()) && canSchedule()) {
|
|
||||||
if ((((state.polling["last"]?:0) + 600000) < now()) && canSchedule()) {
|
|
||||||
log.info "Scheduling Auto Refresh.."
|
|
||||||
//schedule("* */" + ((settings.polling.toInteger() > 0 )? settings.polling.toInteger() : 5) + " * * * ?", refresh)
|
|
||||||
schedule("* */5 * * * ?", refresh)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force Refresh NOWWW!!!!
|
|
||||||
refresh()
|
|
||||||
|
|
||||||
if (!evt) state.polling["rescheduler"] = now() //Update rescheduler's last run
|
|
||||||
}
|
|
||||||
|
|
||||||
def delayedRefresh() {
|
|
||||||
log.info "Delayed refresh triggered"
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
@@ -40,6 +40,7 @@ preferences {
|
|||||||
page name:"pageSetup"
|
page name:"pageSetup"
|
||||||
page name:"Setup"
|
page name:"Setup"
|
||||||
page name:"Settings"
|
page name:"Settings"
|
||||||
|
page name: "timeIntervalInput"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +186,8 @@ def Settings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
|
def timeIntervalInput() {
|
||||||
|
dynamicPage(name: "timeIntervalInput") {
|
||||||
section {
|
section {
|
||||||
input "startTimeType", "enum", title: "Starting at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
|
input "startTimeType", "enum", title: "Starting at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
|
||||||
if (startTimeType in ["sunrise","sunset"]) {
|
if (startTimeType in ["sunrise","sunset"]) {
|
||||||
@@ -204,9 +206,10 @@ page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfte
|
|||||||
input "ending", "time", title: "End time", required: false
|
input "ending", "time", title: "End time", required: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user