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
6675preferences {
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
113145def 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+
147183def installed () {
148184 log. debug " def INSTALLED with settings: ${ settings} "
149185 initialize()
@@ -157,12 +193,32 @@ def updated() {
157193}
158194
159195def 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
167223def temperatureHandler (evt ) {
168224 log. debug " temperatureHandler called: $evt "
@@ -172,14 +228,20 @@ def temperatureHandler(evt) {
172228
173229def 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+
210310private 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+
278442private 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