Code check for sanity reasons whole house fan

Can you do a constrain_month? I’ve been trying to find the constrain lists but I can’t seem to locate them. I know you can do a constrain_day and limit it to certain days of the week but I need to be able to constrain my fan to only run from May-Sept

There was a time when they were in the API documentation. But aimc decided that was too easy, so hid them away here.

1 Like

So I see there is no method for a constrain month dang. Guess I’m going to have to python me a constraint :slight_smile:. Now to figure this out.

And viola it took some reading and learning but I figured it out

import appdaemon.plugins.hass.hassapi as hass
from datetime import datetime

class fan_control(hass.Hass):

def initialize(self):
        self.currentMonth = datetime.now().month


if  5 >= self.currentMonth <= 9:
            self.listen_state(self.fan_temp_control_cb, self.inside_temp)

It does return the current month as an integer with this method so you just check to see if the current int is within the values that you’re looking for. Now knowing what I can do with this :slight_smile: I can setup a few other things in the house such as announcements. I was trying to figure out how to do some annual announcements on Sonos. i.e. Wife’s b-day, Christmas etc without using a Google Calendar event for it because I thought it would just be a lot less work to do it in code rather than go create a calendar switch for all these things.

Edit: In case anyone is curious to see where my fan control is at now it has grown quite a bit

import appdaemon.plugins.hass.hassapi as hass
from datetime import datetime

class fan_control(hass.Hass):

    def initialize(self):
        self.fan_low = 'switch.whole_house_fan_low'
        self.fan_high = 'switch.whole_house_fan_high'
        self.fan_setting = 'input_select.whole_house_fan'
        self.outside_temp = 'sensor.average_outside_temp'
        self.inside_temp = 'sensor.whole_house_temp'
        self.media_player = 'media_player.living_room'
        self.currentMonth = datetime.now().month
        self.log(self.currentMonth)
        self.listen_state(self.fan_control_cb, self.fan_setting, new="on")
        self.listen_state(self.window_closed_cb, "sensor.all_doors_and_windows", new="closed")
        self.listen_state(self.door_closed_cb, "sensor.kids_bathroom_door", new="closed")
        self.listen_state(self.smartthings_sync_cb, self.args["fanID"])
        self.listen_state(self.fan_check_cb, self.fan_low)
        self.listen_state(self.fan_check_cb, self.fan_high)
        if  5 >= self.currentMonth <= 9:
            self.listen_state(self.fan_temp_control_cb, self.inside_temp)

# Reading the value from the input select.  Based off of the value we will then control the fan speed
    def fan_control_cb(self, entity, attribute, old, new, kwargs):
        if new == "False":
            if self.get_state(self.fan_low) == "on":
                self.log("Turning off the fan")
                self.turn_off(self.fan_low)
            elif self.get_state(self.fan_high) == "on":
                self.log("Turning off the fan")
                self.turn_off(self.fan_high)
        elif  self.get_state("sensor.all_windows_and_doors")!= "closed" and self.get_state("sensor.kids_bathroom_door") != "closed":
# Passed safety checks now lets get the fan ready to go
            if new == "High":
                self.log("Turning on high speed")
                if self.get_state(self.fan_low) == "on":
                    self.turn_off(self.fan_low)
                self.turn_on(self.fan_high)
            elif new == "Low":
                self.log("Turning on low speed")
                if self.get_state(self.fan_high) == "on":
                    self.turn_off(self.fan_high)
                self.turn_on(self.fan_low)
        else: # fan turned on but no widows or doors are opened
            self.select_option(self.fan_setting, "False")
            self.log("Announcing that all windows are closed")
            self.call_service("media_player/volume_set", entity_id=self.media_player, volume_level = 50)
            self.call_service("tts/google_say", entity_id=self.media_player, message = "All of the windows are closed.  Due to safety reasons I cannot turn on the whole house fan until at least one window is open")

    def window_closed_cb(self, entity, attribute, old, new, kwargs):
        self.log("All windows are closed we need to turn off the fan")
        if self.get_state(self.fan_setting) != "False":
            self.select_option(self.fan_setting, "False")
            self.log("Announcing that all windows are closed")
            self.call_service("media_player/volume_set", entity_id=self.media_player, volume_level = 50)
            self.call_service("tts/google_say", entity_id=self.media_player, message = "All of the windows are closed.  Due to safety reasons I cannot turn on the whole house fan until at least one window is open")


    def door_closed_cb(self, entity, attribute, old, new, kwargs):
        self.log("All doors are closed we need to turn off the fan")
        if self.get_state(self.fan_setting) != "False":
            self.select_option(self.fan_setting, "False")
            self.log("Announcing that all doors are closed")
            self.call_service("media_player/volume_set", entity_id=self.media_player, volume_level = 50)
            self.call_service("tts/google_say", entity_id=self.media_player, message = "The kids bathroom door is closed I cannot turn on the fan")

    def smartthings_sync_cb(self, entity, attribute, old, new, kwargs):
# Ensuring that if ST turns on the fan that we keep our input_select in sync
        if entity == "switch.whole_house_fan_low" and new == on:
            if self.get_state(self.fan_setting) != "Low":
                self.select_option(self.fan_setting, "Low")
        elif entity == "switch.whole_house_fan_high" and new == on:
            if self.get_state(self.fan_setting) != "High":
                self.select_option(self.fan_setting, "High")
        else:
            self.select_option(self.fan_setting, "False")

    def fan_temp_control_cb(self, entity, attribute, old, new, kwargs):
        if 50 >= self.outside_temp <= 110:
            house_temp=float(new)
            target_temp=62
            temp_diff = house_temp - int(target_temp)
            if temp_diff  > 5:
                self.select_option(self.fan_setting, "High")
            elif temp_diff < 5:
                self.select_option(self.fan_setting, "Low")
            elif temp_diff == 0:
                self.select_option(self.fan_setting, "False")
        else:
            if self.get_state(self.fan_setting) != "False":
                self.select_option(self.fan_setting, "False")

# This is just a safety check to make sure that we never have both switches on at the same time
    def fan_check_cb(self, entity, attribute, old, new, kwargs):
        if entity == self.fan_low and new == "on":
            if self.get_state(self.fan_high) == "on":
                self.turn_off(self.fan_high)
        elif entity == self.fan_high and new == "on":
            if self.get_state(self.fan_low) == "on":
                self.turn_off(self.fan_low)

Once I remove SmartThings from the picture some of these functions will also be removed but while ST exists and the ability to turn on the fan from ST exists I need to ensure that we keep things safe.

Edit 2: So will appdaemon keep tabs on the current month throughout the year? Or do I need to write up something that will force it to check the current month every 24 hours or what? Or better yet I could do a nested if for that. When the inside_temp changes use that as the main if and then put the current month one and then the cb function.

The initialize function will only run when you start (or restart) the app. So with your current code, if you don’t restart the app the callback will continue to be activated or not.

I suggest you move the check for month inside the callback.

1 Like

i dont think you have on in your input select.
so you need to listen to all, false, high or low :wink:
but in this case i think you just want to remove the part ‘new=“on”’


you have this line:

        self.listen_state(self.smartthings_sync_cb, self.args["fanID"])

and from your callback i think you use a list of things there, because you check for which entity in the callback.
but a listen_state can only listen to 1 entity (or a device group) and must be provided with a string, not a list.


            target_temp=62
            temp_diff = house_temp - int(target_temp)

target temp is already an int


but its a nice app already

@gpbenton

I figured that was what was going on. So yeah I just moved it down into the function so now when it gets called it will check to see what month we’re in and then do whatever it needs to do with it from there. Now to just add a few more things to it but I’m almost done with it.

@ReneTode yeah I caught that new=on thing lol and yanked it.

So you’re saying that the listen state can’t monitor more than one entity at a time. Well dang I’m going to have to change that up then :slight_smile: Yeah I had put into the apps.yaml the fanID arg and then listed the 2 switches under it.

Or could we move it to a for loop?

yup like:

for device in self.args["devices"]:
  self.listen_state(self.dosomething,device)

Ok so I have something that I’m writing into this to run_hourly

time = datetime.time(0, 0, 0)
        self.run_hourly(self.fan_circulate_on_cb, time,
                constrain_start_time = "sunrise",
                constrain_end_time = "sunset",
                constrain_presence = "anyone")

But its complaining

2018-03-26 15:14:52.185669 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 1513, in init_object
    init()
  File "/config/appdaemon/apps/whole_house_fan_controls.py", line 22, in initialize
    time = datetime.time(0, 0, 0)
TypeError: descriptor 'time' requires a 'datetime.datetime' object but received a 'int'

2018-03-26 15:14:52.186683 WARNING AppDaemon: ------------------------------------------------------------

I don’t get it because I’ve used this elsewhere just fine so I don’t get why its complaining about the time being a int and not the object it wants.

did you import datetime?

I did

from datetime import datetime

But I’ll add the import line too for fun

the difference between

1) import datetime

and

2 ) from datetime import datetime

is this:
in case 1 you import the whole datetime lib.
that lib has a subclass called datetime and that is why we can use:
datetime.datetime.time()

in case 2 you only import the subclass
leaving out a bunch of functions.
but because both have the same name it gets confusing.

i suggest you just stick to the first option.

1 Like

Thank you very much for that wonderful explanation

1 Like