mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 05:31:56 +00:00
834 lines
20 KiB
Groovy
834 lines
20 KiB
Groovy
/**
|
|
* Copyright 2015 SmartThings
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
* in compliance with the License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing permissions and limitations under the License.
|
|
*
|
|
* Gentle Wake Up
|
|
*
|
|
* Author: Steve Vlaminck
|
|
* Date: 2013-03-11
|
|
*
|
|
* https://s3.amazonaws.com/smartapp-icons/HealthAndWellness/App-SleepyTime.png
|
|
* https://s3.amazonaws.com/smartapp-icons/HealthAndWellness/App-SleepyTime%402x.png
|
|
* Gentle Wake Up turns on your lights slowly, allowing you to wake up more
|
|
* naturally. Once your lights have reached full brightness, optionally turn on
|
|
* more things, or send yourself a text for a more gentle nudge into the waking
|
|
* world (you may want to set your normal alarm as a backup plan).
|
|
*
|
|
*/
|
|
definition(
|
|
name: "Gentle Wake Up",
|
|
namespace: "smartthings",
|
|
author: "SmartThings",
|
|
description: "Dim your lights up slowly, allowing you to wake up more naturally.",
|
|
category: "Health & Wellness",
|
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/HealthAndWellness/App-SleepyTime.png",
|
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/HealthAndWellness/App-SleepyTime@2x.png"
|
|
)
|
|
|
|
preferences {
|
|
page(name: "rootPage")
|
|
page(name: "schedulingPage")
|
|
page(name: "completionPage")
|
|
page(name: "numbersPage")
|
|
}
|
|
|
|
def rootPage() {
|
|
dynamicPage(name: "rootPage", title: "", install: true, uninstall: true) {
|
|
|
|
section {
|
|
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
|
}
|
|
|
|
if (dimmers) {
|
|
|
|
section {
|
|
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
|
}
|
|
|
|
section {
|
|
href(name: "toSchedulingPage", page: "schedulingPage", title: "Rules For Automatically Dimming Your Lights", description: schedulingHrefDescription(), state: schedulingHrefDescription() ? "complete" : "")
|
|
}
|
|
|
|
section {
|
|
href(name: "toCompletionPage", title: "Completion Actions (Optional)", page: "completionPage", state: completionHrefDescription() ? "complete" : "", description: completionHrefDescription())
|
|
}
|
|
|
|
section {
|
|
// TODO: fancy label
|
|
label(title: "Label this SmartApp", required: false, defaultValue: "")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def numbersPage() {
|
|
dynamicPage(name:"numbersPage", title:"") {
|
|
|
|
section {
|
|
paragraph(name: "pGraph", title: "These lights will dim", fancyDeviceString(dimmers))
|
|
}
|
|
|
|
section {
|
|
input(name: "duration", type: "number", title: "For this many minutes", description: "30", required: false, defaultValue: 30)
|
|
}
|
|
|
|
section {
|
|
input(name: "startLevel", type: "number", range: "0..99", title: "From this level", defaultValue: defaultStart(), description: "Current Level", required: false, multiple: false)
|
|
input(name: "endLevel", type: "number", range: "0..99", title: "To this level", defaultValue: defaultEnd(), description: "Between 0 and 99", required: true, multiple: false)
|
|
}
|
|
|
|
def colorDimmers = dimmersWithSetColorCommand()
|
|
if (colorDimmers) {
|
|
section {
|
|
input(name: "colorize", type: "bool", title: "Gradually change the color of ${fancyDeviceString(colorDimmers)}", description: null, required: false, defaultValue: "true")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def defaultStart() {
|
|
if (usesOldSettings() && direction && direction == "Down") {
|
|
return 99
|
|
}
|
|
return 0
|
|
}
|
|
|
|
def defaultEnd() {
|
|
if (usesOldSettings() && direction && direction == "Down") {
|
|
return 0
|
|
}
|
|
return 99
|
|
}
|
|
|
|
def startLevelLabel() {
|
|
if (usesOldSettings()) { // using old settings
|
|
if (direction && direction == "Down") { // 99 -> 1
|
|
return "99%"
|
|
}
|
|
return "0%"
|
|
}
|
|
return hasStartLevel() ? "${startLevel}%" : "Current Level"
|
|
}
|
|
|
|
def endLevelLabel() {
|
|
if (usesOldSettings()) {
|
|
if (direction && direction == "Down") { // 99 -> 1
|
|
return "0%"
|
|
}
|
|
return "99%"
|
|
}
|
|
return "${endLevel}%"
|
|
}
|
|
|
|
def schedulingPage() {
|
|
dynamicPage(name: "schedulingPage", title: "Rules For Automatically Dimming Your Lights") {
|
|
|
|
section {
|
|
input(name: "days", type: "enum", title: "Allow Automatic Dimming On These Days", description: "Every day", required: false, multiple: true, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"])
|
|
}
|
|
|
|
section {
|
|
input(name: "modeStart", title: "Start when entering this mode", type: "mode", required: false, mutliple: false, submitOnChange: true)
|
|
if (modeStart) {
|
|
input(name: "modeStop", title: "Stop when leaving '${modeStart}' mode", type: "bool", required: false)
|
|
}
|
|
}
|
|
|
|
section {
|
|
input(name: "startTime", type: "time", title: "Start Dimming At This Time", description: null, required: false)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
def completionPage() {
|
|
dynamicPage(name: "completionPage", title: "Completion Rules") {
|
|
|
|
section("Switches") {
|
|
input(name: "completionSwitches", type: "capability.switch", title: "Set these switches", description: null, required: false, multiple: true, submitOnChange: true)
|
|
if (completionSwitches || androidClient()) {
|
|
input(name: "completionSwitchesState", type: "enum", title: "To", description: null, required: false, multiple: false, options: ["on", "off"], style: "segmented", defaultValue: "on")
|
|
input(name: "completionSwitchesLevel", type: "number", title: "Optionally, Set Dimmer Levels To", description: null, required: false, multiple: false, range: "(0..99)")
|
|
}
|
|
}
|
|
|
|
section("Notifications") {
|
|
input("recipients", "contact", title: "Send notifications to") {
|
|
input(name: "completionPhoneNumber", type: "phone", title: "Text This Number", description: "Phone number", required: false)
|
|
input(name: "completionPush", type: "bool", title: "Send A Push Notification", description: "Phone number", required: false)
|
|
}
|
|
input(name: "completionMusicPlayer", type: "capability.musicPlayer", title: "Speak Using This Music Player", required: false)
|
|
input(name: "completionMessage", type: "text", title: "With This Message", description: null, required: false)
|
|
}
|
|
|
|
section("Modes and Phrases") {
|
|
input(name: "completionMode", type: "mode", title: "Change ${location.name} Mode To", description: null, required: false)
|
|
input(name: "completionPhrase", type: "enum", title: "Execute The Phrase", description: null, required: false, multiple: false, options: location.helloHome.getPhrases().label)
|
|
}
|
|
|
|
section("Delay") {
|
|
input(name: "completionDelay", type: "number", title: "Delay This Many Minutes Before Executing These Actions", description: "0", required: false)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========================================================
|
|
// Handlers
|
|
// ========================================================
|
|
|
|
def installed() {
|
|
log.debug "Installing 'Gentle Wake Up' with settings: ${settings}"
|
|
|
|
initialize()
|
|
}
|
|
|
|
def updated() {
|
|
log.debug "Updating 'Gentle Wake Up' with settings: ${settings}"
|
|
unschedule()
|
|
|
|
initialize()
|
|
}
|
|
|
|
private initialize() {
|
|
stop()
|
|
|
|
if (startTime) {
|
|
log.debug "scheduling dimming routine to run at $startTime"
|
|
schedule(startTime, "scheduledStart")
|
|
}
|
|
|
|
// TODO: make this an option
|
|
subscribe(app, appHandler)
|
|
|
|
subscribe(location, locationHandler)
|
|
}
|
|
|
|
def appHandler(evt) {
|
|
log.debug "appHandler evt: ${evt.value}"
|
|
if (evt.value == "touch") {
|
|
if (atomicState.running) {
|
|
stop()
|
|
} else {
|
|
start()
|
|
}
|
|
}
|
|
}
|
|
|
|
def locationHandler(evt) {
|
|
log.debug "locationHandler evt: ${evt.value}"
|
|
|
|
if (!modeStart) {
|
|
return
|
|
}
|
|
|
|
def isSpecifiedMode = (evt.value == modeStart)
|
|
def modeStopIsTrue = (modeStop && modeStop != "false")
|
|
|
|
if (isSpecifiedMode && canStartAutomatically()) {
|
|
start()
|
|
} else if (!isSpecifiedMode && modeStopIsTrue) {
|
|
stop()
|
|
}
|
|
|
|
}
|
|
|
|
// ========================================================
|
|
// Scheduling
|
|
// ========================================================
|
|
|
|
def scheduledStart() {
|
|
if (canStartAutomatically()) {
|
|
start()
|
|
}
|
|
}
|
|
|
|
def start() {
|
|
log.trace "START"
|
|
|
|
setLevelsInState()
|
|
|
|
atomicState.running = true
|
|
|
|
atomicState.start = new Date().getTime()
|
|
|
|
schedule("0 * * * * ?", "healthCheck")
|
|
increment()
|
|
}
|
|
|
|
def stop() {
|
|
log.trace "STOP"
|
|
|
|
atomicState.running = false
|
|
atomicState.start = 0
|
|
|
|
unschedule("healthCheck")
|
|
}
|
|
|
|
private healthCheck() {
|
|
log.trace "'Gentle Wake Up' healthCheck"
|
|
|
|
if (!atomicState.running) {
|
|
return
|
|
}
|
|
|
|
increment()
|
|
}
|
|
|
|
// ========================================================
|
|
// Setting levels
|
|
// ========================================================
|
|
|
|
|
|
private increment() {
|
|
|
|
if (!atomicState.running) {
|
|
return
|
|
}
|
|
|
|
def percentComplete = completionPercentage()
|
|
|
|
if (percentComplete > 99) {
|
|
percentComplete = 99
|
|
}
|
|
|
|
updateDimmers(percentComplete)
|
|
|
|
if (percentComplete < 99) {
|
|
|
|
def runAgain = stepDuration()
|
|
log.debug "Rescheduling to run again in ${runAgain} seconds"
|
|
|
|
runIn(runAgain, 'increment', [overwrite: true])
|
|
|
|
} else {
|
|
|
|
int completionDelay = completionDelaySeconds()
|
|
if (completionDelay) {
|
|
log.debug "Finished with steps. Scheduling completion for ${completionDelay} second(s) from now"
|
|
runIn(completionDelay, 'completion', [overwrite: true])
|
|
unschedule("healthCheck")
|
|
// don't let the health check start incrementing again while we wait for the delayed execution of completion
|
|
} else {
|
|
log.debug "Finished with steps. Execution completion"
|
|
completion()
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
def updateDimmers(percentComplete) {
|
|
dimmers.each { dimmer ->
|
|
|
|
def nextLevel = dynamicLevel(dimmer, percentComplete)
|
|
|
|
if (nextLevel == 0) {
|
|
|
|
dimmer.off()
|
|
|
|
} else {
|
|
|
|
def shouldChangeColors = (colorize && colorize != "false")
|
|
def canChangeColors = hasSetColorCommand(dimmer)
|
|
|
|
log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}"
|
|
|
|
if (shouldChangeColors && canChangeColors) {
|
|
dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel])
|
|
} else {
|
|
dimmer.setLevel(nextLevel)
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
int dynamicLevel(dimmer, percentComplete) {
|
|
def start = atomicState.startLevels[dimmer.id]
|
|
def end = dynamicEndLevel()
|
|
|
|
if (!percentComplete) {
|
|
return start
|
|
}
|
|
|
|
def totalDiff = end - start
|
|
def actualPercentage = percentComplete / 100
|
|
def percentOfTotalDiff = totalDiff * actualPercentage
|
|
|
|
(start + percentOfTotalDiff) as int
|
|
}
|
|
|
|
// ========================================================
|
|
// Completion
|
|
// ========================================================
|
|
|
|
private completion() {
|
|
log.trace "Starting completion block"
|
|
|
|
if (!atomicState.running) {
|
|
return
|
|
}
|
|
|
|
stop()
|
|
|
|
handleCompletionSwitches()
|
|
|
|
handleCompletionMessaging()
|
|
|
|
handleCompletionModesAndPhrases()
|
|
|
|
}
|
|
|
|
private handleCompletionSwitches() {
|
|
completionSwitches.each { completionSwitch ->
|
|
|
|
def isDimmer = hasSetLevelCommand(completionSwitch)
|
|
|
|
if (completionSwitchesLevel && isDimmer) {
|
|
completionSwitch.setLevel(completionSwitchesLevel)
|
|
} else {
|
|
def command = completionSwitchesState ?: "on"
|
|
completionSwitch."${command}"()
|
|
}
|
|
}
|
|
}
|
|
|
|
private handleCompletionMessaging() {
|
|
if (completionMessage) {
|
|
if (location.contactBookEnabled) {
|
|
sendNotificationToContacts(completionMessage, recipients)
|
|
} else {
|
|
if (completionPhoneNumber) {
|
|
sendSms(completionPhoneNumber, completionMessage)
|
|
}
|
|
if (completionPush) {
|
|
sendPush(completionMessage)
|
|
}
|
|
}
|
|
if (completionMusicPlayer) {
|
|
speak(completionMessage)
|
|
}
|
|
}
|
|
}
|
|
|
|
private handleCompletionModesAndPhrases() {
|
|
|
|
if (completionMode) {
|
|
setLocationMode(completionMode)
|
|
}
|
|
|
|
if (completionPhrase) {
|
|
location.helloHome.execute(completionPhrase)
|
|
}
|
|
|
|
}
|
|
|
|
def speak(message) {
|
|
def sound = textToSpeech(message)
|
|
def soundDuration = (sound.duration as Integer) + 2
|
|
log.debug "Playing $sound.uri"
|
|
completionMusicPlayer.playTrack(sound.uri)
|
|
log.debug "Scheduled resume in $soundDuration sec"
|
|
runIn(soundDuration, resumePlaying, [overwrite: true])
|
|
}
|
|
|
|
def resumePlaying() {
|
|
log.trace "resumePlaying()"
|
|
def sonos = completionMusicPlayer
|
|
if (sonos) {
|
|
def currentTrack = sonos.currentState("trackData").jsonValue
|
|
if (currentTrack.status == "playing") {
|
|
sonos.playTrack(currentTrack)
|
|
} else {
|
|
sonos.setTrack(currentTrack)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========================================================
|
|
// Helpers
|
|
// ========================================================
|
|
|
|
def setLevelsInState() {
|
|
def startLevels = [:]
|
|
dimmers.each { dimmer ->
|
|
if (usesOldSettings()) {
|
|
startLevels[dimmer.id] = defaultStart()
|
|
} else if (hasStartLevel()) {
|
|
startLevels[dimmer.id] = startLevel
|
|
} else {
|
|
def dimmerIsOff = dimmer.currentValue("switch") == "off"
|
|
startLevels[dimmer.id] = dimmerIsOff ? 0 : dimmer.currentValue("level")
|
|
}
|
|
}
|
|
|
|
atomicState.startLevels = startLevels
|
|
}
|
|
|
|
def canStartAutomatically() {
|
|
|
|
def today = new Date().format("EEEE")
|
|
log.debug "today: ${today}, days: ${days}"
|
|
|
|
if (!days || days.contains(today)) {// if no days, assume every day
|
|
return true
|
|
}
|
|
|
|
log.trace "should not run"
|
|
return false
|
|
}
|
|
|
|
def completionPercentage() {
|
|
log.trace "checkingTime"
|
|
|
|
if (!atomicState.running) {
|
|
return
|
|
}
|
|
|
|
int now = new Date().getTime()
|
|
int diff = now - atomicState.start
|
|
int totalRunTime = totalRunTimeMillis()
|
|
int percentOfRunTime = (diff / totalRunTime) * 100
|
|
log.debug "percentOfRunTime: ${percentOfRunTime}"
|
|
|
|
percentOfRunTime
|
|
}
|
|
|
|
int totalRunTimeMillis() {
|
|
int minutes = sanitizeInt(duration, 30)
|
|
def seconds = minutes * 60
|
|
def millis = seconds * 1000
|
|
return millis as int
|
|
}
|
|
|
|
int dynamicEndLevel() {
|
|
if (usesOldSettings()) {
|
|
if (direction && direction == "Down") {
|
|
return 0
|
|
}
|
|
return 99
|
|
}
|
|
return endLevel as int
|
|
}
|
|
|
|
def getHue(dimmer, level) {
|
|
def start = atomicState.startLevels[dimmer.id] as int
|
|
def end = dynamicEndLevel()
|
|
if (start > end) {
|
|
return getDownHue(level)
|
|
} else {
|
|
return getUpHue(level)
|
|
}
|
|
}
|
|
|
|
def getUpHue(level) {
|
|
getBlueHue(level)
|
|
}
|
|
|
|
def getDownHue(level) {
|
|
getRedHue(level)
|
|
}
|
|
|
|
private getBlueHue(level) {
|
|
if (level < 5) return 72
|
|
if (level < 10) return 71
|
|
if (level < 15) return 70
|
|
if (level < 20) return 69
|
|
if (level < 25) return 68
|
|
if (level < 30) return 67
|
|
if (level < 35) return 66
|
|
if (level < 40) return 65
|
|
if (level < 45) return 64
|
|
if (level < 50) return 63
|
|
if (level < 55) return 62
|
|
if (level < 60) return 61
|
|
if (level < 65) return 60
|
|
if (level < 70) return 59
|
|
if (level < 75) return 58
|
|
if (level < 80) return 57
|
|
if (level < 85) return 56
|
|
if (level < 90) return 55
|
|
if (level < 95) return 54
|
|
if (level >= 95) return 53
|
|
}
|
|
|
|
private getRedHue(level) {
|
|
if (level < 6) return 1
|
|
if (level < 12) return 2
|
|
if (level < 18) return 3
|
|
if (level < 24) return 4
|
|
if (level < 30) return 5
|
|
if (level < 36) return 6
|
|
if (level < 42) return 7
|
|
if (level < 48) return 8
|
|
if (level < 54) return 9
|
|
if (level < 60) return 10
|
|
if (level < 66) return 11
|
|
if (level < 72) return 12
|
|
if (level < 78) return 13
|
|
if (level < 84) return 14
|
|
if (level < 90) return 15
|
|
if (level < 96) return 16
|
|
if (level >= 96) return 17
|
|
}
|
|
|
|
private hasSetLevelCommand(device) {
|
|
def isDimmer = false
|
|
device.supportedCommands.each {
|
|
if (it.name.contains("setLevel")) {
|
|
isDimmer = true
|
|
}
|
|
}
|
|
return isDimmer
|
|
}
|
|
|
|
private hasSetColorCommand(device) {
|
|
def hasColor = false
|
|
device.supportedCommands.each {
|
|
if (it.name.contains("setColor")) {
|
|
hasColor = true
|
|
}
|
|
}
|
|
return hasColor
|
|
}
|
|
|
|
private dimmersWithSetColorCommand() {
|
|
def colorDimmers = []
|
|
dimmers.each { dimmer ->
|
|
if (hasSetColorCommand(dimmer)) {
|
|
colorDimmers << dimmer
|
|
}
|
|
}
|
|
return colorDimmers
|
|
}
|
|
|
|
private int sanitizeInt(i, int defaultValue = 0) {
|
|
try {
|
|
if (!i) {
|
|
return defaultValue
|
|
} else {
|
|
return i as int
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
log.debug e
|
|
return defaultValue
|
|
}
|
|
}
|
|
|
|
private completionDelaySeconds() {
|
|
int completionDelayMinutes = sanitizeInt(completionDelay)
|
|
int completionDelaySeconds = (completionDelayMinutes * 60)
|
|
return completionDelaySeconds ?: 0
|
|
}
|
|
|
|
private stepDuration() {
|
|
int minutes = sanitizeInt(duration, 30)
|
|
int stepDuration = (minutes * 60) / 100
|
|
return stepDuration ?: 1
|
|
}
|
|
|
|
private debug(message) {
|
|
log.debug "${message}\nstate: ${state}"
|
|
}
|
|
|
|
public smartThingsDateFormat() { "yyyy-MM-dd'T'HH:mm:ss.SSSZ" }
|
|
|
|
public humanReadableStartDate() {
|
|
new Date().parse(smartThingsDateFormat(), startTime).format("h:mm a", timeZone(startTime))
|
|
}
|
|
|
|
def fancyString(listOfStrings) {
|
|
|
|
def fancify = { list ->
|
|
return list.collect {
|
|
def label = it
|
|
if (list.size() > 1 && it == list[-1]) {
|
|
label = "and ${label}"
|
|
}
|
|
label
|
|
}.join(", ")
|
|
}
|
|
|
|
return fancify(listOfStrings)
|
|
}
|
|
|
|
def fancyDeviceString(devices = []) {
|
|
fancyString(devices.collect { deviceLabel(it) })
|
|
}
|
|
|
|
def deviceLabel(device) {
|
|
return device.label ?: device.name
|
|
}
|
|
|
|
def schedulingHrefDescription() {
|
|
|
|
def descriptionParts = []
|
|
if (days) {
|
|
descriptionParts << "On ${fancyString(days)},"
|
|
}
|
|
|
|
descriptionParts << "${fancyDeviceString(dimmers)} will start dimming"
|
|
|
|
if (startTime) {
|
|
descriptionParts << "at ${humanReadableStartDate()}"
|
|
}
|
|
|
|
if (modeStart) {
|
|
if (startTime) {
|
|
descriptionParts << "or"
|
|
}
|
|
descriptionParts << "when ${location.name} enters '${modeStart}' mode"
|
|
}
|
|
|
|
if (descriptionParts.size() <= 1) {
|
|
// dimmers will be in the list no matter what. No rules are set if only dimmers are in the list
|
|
return null
|
|
}
|
|
|
|
return descriptionParts.join(" ")
|
|
}
|
|
|
|
def completionHrefDescription() {
|
|
|
|
def descriptionParts = []
|
|
def example = "Switch1 will be turned on. Switch2, Switch3, and Switch4 will be dimmed to 50%. The message '<message>' will be spoken, sent as a text, and sent as a push notification. The mode will be changed to '<mode>'. The phrase '<phrase>' will be executed"
|
|
|
|
if (completionSwitches) {
|
|
def switchesList = []
|
|
def dimmersList = []
|
|
|
|
|
|
completionSwitches.each {
|
|
def isDimmer = completionSwitchesLevel ? hasSetLevelCommand(it) : false
|
|
|
|
if (isDimmer) {
|
|
dimmersList << deviceLabel(it)
|
|
}
|
|
|
|
if (!isDimmer) {
|
|
switchesList << deviceLabel(it)
|
|
}
|
|
}
|
|
|
|
|
|
if (switchesList) {
|
|
descriptionParts << "${fancyString(switchesList)} will be turned ${completionSwitchesState ?: 'on'}."
|
|
}
|
|
|
|
if (dimmersList) {
|
|
descriptionParts << "${fancyString(dimmersList)} will be dimmed to ${completionSwitchesLevel}%."
|
|
}
|
|
|
|
}
|
|
|
|
if (completionMessage && (completionPhoneNumber || completionPush || completionMusicPlayer)) {
|
|
def messageParts = []
|
|
|
|
if (completionMusicPlayer) {
|
|
messageParts << "spoken"
|
|
}
|
|
if (completionPhoneNumber) {
|
|
messageParts << "sent as a text"
|
|
}
|
|
if (completionPush) {
|
|
messageParts << "sent as a push notification"
|
|
}
|
|
|
|
descriptionParts << "The message '${completionMessage}' will be ${fancyString(messageParts)}."
|
|
}
|
|
|
|
if (completionMode) {
|
|
descriptionParts << "The mode will be changed to '${completionMode}'."
|
|
}
|
|
|
|
if (completionPhrase) {
|
|
descriptionParts << "The phrase '${completionPhrase}' will be executed."
|
|
}
|
|
|
|
return descriptionParts.join(" ")
|
|
}
|
|
|
|
def numbersPageHrefDescription() {
|
|
def title = "All dimmers will dim for ${duration ?: '30'} minutes from ${startLevelLabel()} to ${endLevelLabel()}"
|
|
if (colorize) {
|
|
def colorDimmers = dimmersWithSetColorCommand()
|
|
if (colorDimmers == dimmers) {
|
|
title += " and will gradually change color."
|
|
} else {
|
|
title += ".\n${fancyDeviceString(colorDimmers)} will gradually change color."
|
|
}
|
|
}
|
|
return title
|
|
}
|
|
|
|
def hueSatToHex(h, s) {
|
|
def convertedRGB = hslToRgb(h, s, 0.5)
|
|
return rgbToHex(convertedRGB)
|
|
}
|
|
|
|
def hslToRgb(h, s, l) {
|
|
def r, g, b;
|
|
|
|
if (s == 0) {
|
|
r = g = b = l; // achromatic
|
|
} else {
|
|
def hue2rgb = { p, q, t ->
|
|
if (t < 0) t += 1;
|
|
if (t > 1) t -= 1;
|
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
if (t < 1 / 2) return q;
|
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
return p;
|
|
}
|
|
|
|
def q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
def p = 2 * l - q;
|
|
|
|
r = hue2rgb(p, q, h + 1 / 3);
|
|
g = hue2rgb(p, q, h);
|
|
b = hue2rgb(p, q, h - 1 / 3);
|
|
}
|
|
|
|
return [r * 255, g * 255, b * 255];
|
|
}
|
|
|
|
def rgbToHex(red, green, blue) {
|
|
def toHex = {
|
|
int n = it as int;
|
|
n = Math.max(0, Math.min(n, 255));
|
|
def hexOptions = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]
|
|
|
|
def firstDecimal = ((n - n % 16) / 16) as int
|
|
def secondDecimal = (n % 16) as int
|
|
|
|
return "${hexOptions[firstDecimal]}${hexOptions[secondDecimal]}"
|
|
}
|
|
|
|
def rgbToHex = { r, g, b ->
|
|
return toHex(r) + toHex(g) + toHex(b)
|
|
}
|
|
|
|
return rgbToHex(red, green, blue)
|
|
}
|
|
|
|
def usesOldSettings() {
|
|
!hasEndLevel()
|
|
}
|
|
|
|
def hasStartLevel() {
|
|
return (startLevel != null && startLevel != "")
|
|
}
|
|
|
|
def hasEndLevel() {
|
|
return (endLevel != null && endLevel != "")
|
|
}
|