Skip to content

Commit 9914c7d

Browse files
committed
Added an option to turn on only if it's between sunrise and sunset. Added an option to only turn the fan on if there are somebody in home. If speed control is not selected the device will only turn on or off. Stop sending device commands if not necessary.
1 parent a7e7357 commit 9914c7d

1 file changed

Lines changed: 219 additions & 55 deletions

File tree

smartapps/dcoffing/3-speed-ceiling-fan-thermostat.src/3-speed-ceiling-fan-thermostat.groovy

Lines changed: 219 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55
This smartapp provides automatic control of Low, Medium, High speeds of a ceiling fan using
66
any temperature sensor with optional motion override.
77
It requires two hardware devices; any temperature sensor and a dimmer type smart fan controller
8-
such as the GE 12730 or Leviton VRF01-1LX
8+
such as the GE 12730 or Leviton VRF01-1LX. Incorporates contributions from:
99
10+
Eric Vitale (https://github.com/ericvitale/SmartThingsPublic/blob/master/smartapps/dcoffing/3-speed-ceiling-fan-thermostat.src/3-speed-ceiling-fan-thermostat.groovy)
11+
1012
Change Log
13+
2017-06-07 Added an option to only turn the fan on during the day (Sun is UP - Between SunRise and SunSet). by Victor Welasco
14+
Added the option to disable the speed control (If nothing is selected speed control will not be evaluated). by Victor Welasco
15+
Will only send a device command if the device is not already on that state. by Victor Welasco
16+
2017-06-03 Added an option to check presence using a presence sensor. - by Victor Welasco
17+
2017-04-11 Added 10.0 selection for Fan Differential Temp to mimic single speed control
18+
2016-10-19 Ver2 Parent / Child app to allow for multiple use cases with a single install - @ericvitale
1119
2016-06-30 added dynamic temperature display on temperature setpoint input text
1220
2016-06-28 x.1 version update
1321
added submitOnChange for motion so to skip minutes input next if no motion selected
@@ -58,19 +66,38 @@ definition(
5866
author: "Dale Coffing",
5967
description: "Automatic control for 3 Speed Ceiling Fan using Low, Medium, High speeds with any temperature sensor.",
6068
category: "My Apps",
69+
singleInstance: true,
6170
iconUrl: "https://raw.githubusercontent.com/dcoffing/SmartThingsPublic/master/smartapps/dcoffing/3-speed-ceiling-fan-thermostat.src/3scft125x125.png",
6271
iconX2Url: "https://raw.githubusercontent.com/dcoffing/SmartThingsPublic/master/smartapps/dcoffing/3-speed-ceiling-fan-thermostat.src/3scft250x250.png",
6372
iconX3Url: "https://raw.githubusercontent.com/dcoffing/SmartThingsPublic/master/smartapps/dcoffing/3-speed-ceiling-fan-thermostat.src/3scft250x250.png",
6473
)
6574

6675
preferences {
67-
page(name: "mainPage")
76+
page(name: "startPage")
77+
page(name: "parentPage")
78+
page(name: "childStartPage")
6879
page(name: "optionsPage")
6980
page(name: "aboutPage")
7081
}
7182

72-
def mainPage() {
73-
dynamicPage(name: "mainPage", title: "Select your devices and settings", install: true, uninstall: true) {
83+
def startPage() {
84+
if (parent) {
85+
childStartPage()
86+
} else {
87+
parentPage()
88+
}
89+
}
90+
91+
def parentPage() {
92+
return dynamicPage(name: "parentPage", title: "", nextPage: "", install: false, uninstall: true) {
93+
section("Create a new fan automation.") {
94+
app(name: "childApps", appName: appName(), namespace: "dcoffing", title: "New Fan Automation", multiple: true)
95+
}
96+
}
97+
}
98+
99+
def childStartPage() {
100+
dynamicPage(name: "childStartPage", title: "Select your devices and settings", install: true, uninstall: true) {
74101

75102
section("Select a room temperature sensor to control the fan..."){
76103
input "tempSensor", "capability.temperatureMeasurement", multiple:false, title: "Temperature Sensor", required: true, submitOnChange: true
@@ -97,10 +124,15 @@ def mainPage() {
97124
page: "optionsPage"
98125
)
99126
}
127+
128+
section("Name") {
129+
label(title: "Assign a name", required: false)
130+
}
131+
100132
section("Version Info, User's Guide") {
101133
// VERSION
102134
href (name: "aboutPage",
103-
title: "3 Speed Ceiling Fan Thermostat \n"+"Version:1.1.160630 \n"+"Copyright © 2016 Dale Coffing",
135+
title: "3 Speed Ceiling Fan Thermostat \n"+"Version:3.170610 \n"+"Copyright © 2016 Dale Coffing",
104136
description: "Tap to get user's guide.",
105137
image: "https://raw.githubusercontent.com/dcoffing/SmartThingsPublic/master/smartapps/dcoffing/3-speed-ceiling-fan-thermostat.src/3scft125x125.png",
106138
required: false,
@@ -112,8 +144,8 @@ def mainPage() {
112144

113145
def optionsPage() {
114146
dynamicPage(name: "optionsPage", title: "Configure Optional Settings", install: false, uninstall: false) {
115-
section("Enter the desired differential temp between fan speeds (default=1.0)..."){
116-
input "fanDiffTempString", "enum", title: "Fan Differential Temp", options: ["0.5","1.0","1.5","2.0"], required: false
147+
section("Enter the desired differential temp between fan speeds"){
148+
input "fanDiffTempString", "enum", title: "Fan Differential Temp", options: ["0.5","1.0","1.5","2.0","10.0"], required: false
117149
}
118150
section("Enable ceiling fan thermostat only if motion is detected at (optional, leave blank to not require motion)..."){
119151
input "motionSensor", "capability.motionSensor", title: "Select Motion device", required: false, submitOnChange: true
@@ -123,16 +155,18 @@ def optionsPage() {
123155
input "minutesNoMotion", "number", title: "Minutes?", required: true
124156
}
125157
}
158+
section("Enable ceiling fan thermostat only if someone is present..."){
159+
input "presenceSensor", "capability.presenceSensor", title: "Select Presence device", required: false, multiple:true
160+
}
161+
section("Enable ceiling fan thermostat only if during the day (Sun is UP)..."){
162+
input "sunsetsunrise", "bool", title: "Select True or False:", defaultValue: false, required: false
163+
}
126164
section("Select ceiling fan operating mode desired (default to 'YES-Auto'..."){
127165
input "autoMode", "enum", title: "Enable Ceiling Fan Thermostat?", options: ["NO-Manual","YES-Auto"], required: false
128166
}
129167
section ("Change SmartApp name, Mode selector") {
130-
label title: "Assign a name", required: false
131168
mode title: "Set for specific mode(s)", required: false
132169
}
133-
134-
135-
136170
}
137171
}
138172

@@ -144,6 +178,8 @@ def aboutPage() {
144178
}
145179
}
146180

181+
private def appName() { return "${parent ? "3 Speed Fan Automation" : "3 Speed Ceiling Fan Thermostat"}" }
182+
147183
def installed() {
148184
log.debug "def INSTALLED with settings: ${settings}"
149185
initialize()
@@ -157,12 +193,32 @@ def updated() {
157193
}
158194

159195
def initialize() {
196+
197+
if(parent) {
198+
initChild()
199+
} else {
200+
initParent()
201+
}
202+
}
203+
204+
def initChild() {
160205
log.debug "def INITIALIZE with settings: ${settings}"
161206
subscribe(tempSensor, "temperature", temperatureHandler) //call temperatureHandler method when any reported change to "temperature" attribute
162207
if (motionSensor) {
163208
subscribe(motionSensor, "motion", motionHandler) //call the motionHandler method when there is any reported change to the "motion" attribute
164-
}
209+
}
210+
if (presenceSensor) {
211+
subscribe(presenceSensor, "presence", presenceHandler) //call the presenceHandler method when there is any reported change to the "presence" attribute
212+
}
213+
if (sunsetsunrise) {
214+
subscribe(location, "sunset", sunsetsunriseHandler) //call the sunsetsunriseHandler method when the sunset
215+
subscribe(location, "sunrise", sunsetsunriseHandler) //call the sunsetsunriseHandler method when the sunrise
216+
}
165217
}
218+
219+
def initParent() {
220+
log.debug "Parent Initialized"
221+
}
166222
//Event Handler Methods
167223
def temperatureHandler(evt) {
168224
log.debug "temperatureHandler called: $evt"
@@ -172,14 +228,20 @@ def temperatureHandler(evt) {
172228

173229
def handleTemperature(temp) { //
174230
log.debug "handleTemperature called: $evt"
231+
def isSunsetSunrise = betweenSunsetSunRise()
232+
def isPresent = someonePresent()
175233
def isActive = hasBeenRecentMotion()
176-
if (isActive) {
177-
//motion detected recently
178-
tempCheck(temp, setpoint)
179-
log.debug "handleTemperature ISACTIVE($isActive)"
180-
}
234+
if(isSunsetSunrise && isPresent){
235+
if (isActive) {
236+
//motion detected recently
237+
tempCheck(temp, setpoint)
238+
log.debug "handleTemperature ISACTIVE($isActive)"
239+
}
240+
}
181241
else {
182-
fanDimmer.off()
242+
if (fanDimmer.currentSwitch != "off") {
243+
fanDimmer.off()
244+
}
183245
}
184246
}
185247

@@ -202,57 +264,126 @@ def motionHandler(evt) {
202264
}
203265
}
204266
else {
205-
fanDimmer.off()
267+
if (fanDimmer.currentSwitch != "off") {
268+
fanDimmer.off()
269+
}
206270
}
207271
}
208272
}
209273

274+
def presenceHandler(evt) {
275+
def isPresent = someonePresent() //define isPresent local variable to returned true or false
276+
277+
if(isPresent){
278+
def lastTemp = tempSensor.currentTemperature // <-- That's a dinamic method currentTemperature you can use the verb current and the name of the ability of any device type
279+
log.debug "presenceHandler ACTIVE($isPresent)"
280+
if (lastTemp != null) {
281+
tempCheck(lastTemp, setpoint)
282+
}
283+
}
284+
else{
285+
log.debug "nobody in home turning the fan off!"
286+
if (fanDimmer.currentSwitch != "off") {
287+
fanDimmer.off()
288+
}
289+
}
290+
}
291+
292+
def sunsetsunriseHandler(evt) {
293+
def isGoodTime = betweenSunsetSunRise() //define isPresent local variable to returned true or false
294+
295+
if(isGoodTime){
296+
def lastTemp = tempSensor.currentTemperature // <-- That's a dinamic method currentTemperature you can use the verb current and the name of the ability of any device type
297+
log.debug "sunsetsunriseHandler ACTIVE($isGoodTime)"
298+
if (lastTemp != null) {
299+
tempCheck(lastTemp, setpoint)
300+
}
301+
}
302+
else{
303+
log.debug "The sun is down turnning the Fan off if is not already off!"
304+
if (fanDimmer.currentSwitch != "off") {
305+
fanDimmer.off()
306+
}
307+
}
308+
}
309+
210310
private tempCheck(currentTemp, desiredTemp)
211311
{
212312
log.debug "TEMPCHECK#1(CT=$currentTemp,SP=$desiredTemp,FD=$fanDimmer.currentSwitch,FD_LVL=$fanDimmer.currentLevel, automode=$autoMode,FDTstring=$fanDiffTempString, FDTvalue=$fanDiffTempValue)"
213313

214314
//convert Fan Diff Temp input enum string to number value and if user doesn't select a Fan Diff Temp default to 1.0
215-
def fanDiffTempValue = (settings.fanDiffTempString != null && settings.fanDiffTempString != "") ? Double.parseDouble(settings.fanDiffTempString): 1.0
315+
//def fanDiffTempValue = (settings.fanDiffTempString != null && settings.fanDiffTempString != "") ? Double.parseDouble(settings.fanDiffTempString): 1.0
316+
def fanDiffTempValueSet = settings.fanDiffTempString
216317

217318
//if user doesn't select autoMode then default to "YES-Auto"
218319
def autoModeValue = (settings.autoMode != null && settings.autoMode != "") ? settings.autoMode : "YES-Auto"
219-
220-
def LowDiff = fanDiffTempValue*1
221-
def MedDiff = fanDiffTempValue*2
222-
def HighDiff = fanDiffTempValue*3
223320

224321
log.debug "TEMPCHECK#2(CT=$currentTemp,SP=$desiredTemp,FD=$fanDimmer.currentSwitch,FD_LVL=$fanDimmer.currentLevel, automode=$autoMode,FDTstring=$fanDiffTempString, FDTvalue=$fanDiffTempValue)"
225322
if (autoModeValue == "YES-Auto") {
226-
switch (currentTemp - desiredTemp) {
227-
case { it >= HighDiff }:
228-
// turn on fan high speed
229-
fanDimmer.setLevel(90)
230-
log.debug "HI speed(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, HighDiff=$HighDiff)"
231-
break //exit switch statement
232-
case { it >= MedDiff }:
233-
// turn on fan medium speed
234-
fanDimmer.setLevel(60)
235-
log.debug "MED speed(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, MedDiff=$MedDiff)"
236-
break
237-
case { it >= LowDiff }:
238-
// turn on fan low speed
239-
if (fanDimmer.currentSwitch == "off") { // if fan is OFF to make it easier on motor by
240-
fanDimmer.setLevel(90) // starting fan in High speed temporarily then
241-
fanDimmer.setLevel(30, [delay: 1000]) // change to Low speed after 1 second
242-
log.debug "LO speed after HI 3secs(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, LowDiff=$LowDiff)"
243-
} else {
244-
fanDimmer.setLevel(30) //fan is already running, not necessary to protect motor
245-
} //set Low speed immediately
246-
log.debug "LO speed immediately(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, LowDiff=$LowDiff)"
247-
break
248-
default:
249-
// check to see if fan should be turned off
250-
if (desiredTemp - currentTemp >= 0 ) { //below or equal to setpoint, turn off fan, zero level
251-
fanDimmer.off()
252-
log.debug "below SP+Diff=fan OFF (CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, FD=$fanDimmer.currentSwitch,autoMode=$autoMode,)"
253-
}
254-
log.debug "autoMode YES-MANUAL? else OFF(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, FD=$fanDimmer.currentSwitch,autoMode=$autoMode,)"
255-
}
323+
if(fanDiffTempValueSet){
324+
def fanDiffTempValue = (settings.fanDiffTempString != null && settings.fanDiffTempString != "") ? Double.parseDouble(settings.fanDiffTempString): 1.0
325+
def LowDiff = fanDiffTempValue*1
326+
def MedDiff = fanDiffTempValue*2
327+
def HighDiff = fanDiffTempValue*3
328+
switch (currentTemp - desiredTemp) {
329+
case { it >= HighDiff }:
330+
// turn on fan high speed
331+
if(fanDimmer.currentLevel != 90){
332+
fanDimmer.setLevel(90)
333+
}
334+
log.debug "HI speed(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, HighDiff=$HighDiff)"
335+
break //exit switch statement
336+
case { it >= MedDiff }:
337+
// turn on fan medium speed
338+
if(fanDimmer.currentLevel != 60){
339+
fanDimmer.setLevel(60)
340+
}
341+
log.debug "MED speed(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, MedDiff=$MedDiff)"
342+
break
343+
case { it >= LowDiff }:
344+
// turn on fan low speed
345+
if (fanDimmer.currentSwitch == "off") { // if fan is OFF to make it easier on motor by
346+
fanDimmer.setLevel(90) // starting fan in High speed temporarily then
347+
fanDimmer.setLevel(30, [delay: 5000]) // change to Low speed after 5 second
348+
log.debug "LO speed after HI 3secs(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, LowDiff=$LowDiff)"
349+
} else {
350+
if(fanDimmer.currentLevel != 30){
351+
fanDimmer.setLevel(30) //fan is already running, not necessary to protect motor
352+
} //set Low speed immediately
353+
}
354+
log.debug "LO speed immediately(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, LowDiff=$LowDiff)"
355+
break
356+
default:
357+
// check to see if fan should be turned off
358+
if (desiredTemp - currentTemp >= 0 ) { //below or equal to setpoint, turn off fan, zero level
359+
if (fanDimmer.currentSwitch != "off") {
360+
fanDimmer.off()
361+
}
362+
log.debug "below SP+Diff=fan OFF (CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, FD=$fanDimmer.currentSwitch,autoMode=$autoMode,)"
363+
}
364+
log.debug "autoMode YES-MANUAL? else OFF(CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, FD=$fanDimmer.currentSwitch,autoMode=$autoMode,)"
365+
}
366+
}
367+
else{
368+
// In case the differential temp is off we will just turn it on or off not checking the fan speed
369+
// defining difftemp - if it's a positive value turn the fan on or turn the fan off
370+
log.debug "differential temp is off checking if we must turn it on! (CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel)"
371+
def diffTemp = currentTemp - desiredTemp
372+
if(diffTemp >= 0){
373+
log.debug "Turrning the Fan On if it's not already on (CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel)"
374+
if (fanDimmer.currentSwitch != "on") {
375+
log.debug "Fan wasn't running, turnning it On"
376+
fanDimmer.setLevel(99)
377+
}
378+
}
379+
else{
380+
log.debug "below SP+Diff=fan OFF if it's not already off (CT=$currentTemp, SP=$desiredTemp, FD-LVL=$fanDimmer.currentLevel, FD=$fanDimmer.currentSwitch,autoMode=$autoMode,)"
381+
if (fanDimmer.currentSwitch != "off") {
382+
log.debug "Fan was running, turnning it Off"
383+
fanDimmer.off()
384+
}
385+
}
386+
}
256387
}
257388
}
258389

@@ -275,6 +406,39 @@ private hasBeenRecentMotion()
275406
isActive
276407
}
277408

409+
private someonePresent()
410+
{
411+
def isPresent = false
412+
if (presenceSensor) {
413+
def currPresenceDevice = presenceSensor.currentPresence
414+
def presentPresenceDevices = currPresenceDevice.findAll {
415+
deviceVal -> deviceVal == "present" ? true : false
416+
}
417+
log.debug "Amount of devices that are currently present: ${presentPresenceDevices.size()} of ${presenceSensor.size()}"
418+
if(presentPresenceDevices.size() >= 1){
419+
isPresent = true
420+
}
421+
}
422+
else {
423+
isPresent = true
424+
}
425+
isPresent
426+
}
427+
428+
private betweenSunsetSunRise()
429+
{
430+
def isGoodTime = false
431+
def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
432+
def now = new Date()
433+
def sunriseTime = s.sunrise
434+
def sunsetTime = s.sunset
435+
if(sunsetTime.after(now) || sunriseTime.before(now)) { //before midnight/after sunset or after midnight/before sunset (checking if the Sun is UP)
436+
log.info "Sun is UP"
437+
isGoodTime = true
438+
}
439+
isGoodTime
440+
}
441+
278442
private def textHelp() {
279443
def text =
280444
"This smartapp provides automatic control of Low, Medium, High speeds of a"+
@@ -293,4 +457,4 @@ private def textHelp() {
293457
"@ChadCK's 'Z-Wave Smart Fan Control Custom Device Handler' along with hardware"+
294458
" designed specifically for motor control such as the GE 12730 Z-Wave Smart Fan Control or"+
295459
" Leviton VRF01-1LX works well together with this smartapp."
296-
}
460+
}

0 commit comments

Comments
 (0)