Code check for sanity reasons whole house fan

So I’m starting to work on my app for my whole house fan.

The fan has 2 speeds low and high. I can’t have one on if the other is already on otherwise it could burn up the motor or something else.

I also can’t allow the fan to run if my windows are closed.

Eventually I’m going to add to this to build in temperature control that will only allow the fan to run based on the outside temp and also adjust the speed of the fan based off of the difference of the current house temp minus where I want to get the house to. That will all come later. For now since I’m operating the fan manually through the dashboard I just want to ensure that if someone else were to manually engage the fan that the script does its checks and balances and makes sure that the following conditions are met and then we’re all good. Ideally it would be awesome to tell HA don’t send the command to the switch without doing the scirpt first but I don’t know if thats possible, or would we design a virtual switch that when its turned on causes this to run and then this will actually call the physical device?

Anyways just wanted a second set of eyes to take a look at this and make sure that I’m doing good code here :slight_smile:

import appdaemon.plugins.hass.hassapi as hass

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.media_player = 'media_player.living_room'
        self.listen_state(self.fan_low_cb, self.fan_low)
        self.listen_state(self.fan_high_cb, self.fan_high)

# First we need to check and make sure that the other fan speed is not on.  If it is turn it off
    def fan_low_cb(self, entity, attribute, old, new, kwargs):
        if self.get_state(self.fan_high) == "on":
            self.turn_off(self.fan_high)
        self.window_check

    def fan_high_cb(self, entity, attribute, old, new, kwargs):
        if self.get_state(self.fan_low) == "on":
            self.turn_off(self.fan_low)
        self.window_check

# We now need to check and make sure we have at least one open window so we can operator safely
    def window_check(self,kwargs):
        if self.get_state("sensor.matt_window") and self.get_state("sensor.family_room_window") and self.get_state("sensor.dining_room_window") == "closed":
            if self.get_state(self.fan_low) == "on":
                self.turn_off(self.fan_low)
            else
                self.turn_off(self.fan_high)
            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")

i think you are good, except this:

       self.window_check

should be

       self.window_check()

because you are starting a function.
to make sure that the script runs before everything else, you could create an input_boolean and set that on your dashboard.
but i think you would even be better of with an input_select with the choices, high, low or off.
the input select would trigger your app, and then you only need to check the windows, when the fan is turned on.

Thank you @ReneTode I shall go look into the input_select as this sounds like a better solution

Ok there Mr. @ReneTode you got me to do it :). I put the input_select to work and I now have

import appdaemon.plugins.hass.hassapi as hass

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.media_player = 'media_player.living_room'
        self.listen_state(self.fan_control_cb, self.fan_setting)

# 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)
            else:
                self.turn_off(self.fan_high)
        else:
# First we need to check that we can safely operate the fan
            self.window_check()
            self.door_check()
# 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)

# We now need to check and make sure we have at least one open window so we can operate safely
    def window_check(self):
        if self.get_state("sensor.matt_window") and self.get_state("sensor.family_room_window") and self.get_state("sensor.dining_room_window") == "closed":
            if self.get_state(self.fan_low) == "on":
                self.turn_off(self.fan_low)
                self.select_option(self.fan_setting, "Off")
            else:
                self.turn_off(self.fan_high)
                self.select_option(self.fan_setting, "Off")
            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_check(self):
        if self.get_state("sensor.kids_bathroom_door") == "closed":
            if self.get_state(self.fan_low) == "on":
                self.turn_off(self.fan_low)
                self.select_option(self.fan_setting, "Off")
            else:
                self.turn_off(self.fan_high)
                self.select_option(self.fan_setting, "Off")
            self.log("Announcing that kids bathroom door is 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 am unaable to operate the whole house fan.  Please open the door.  Thank you")

Which I def like the input select for this vs the previous way. Now I just select whatever I want and it does the rest. Ensures that before turning on a speed setting that the other switch is off first then turn on the setting. I like this.

you have something wrong in this setup:

you do now:

check windows and doors
if they are not the way you like you turn off the fan
then you turn it on, because the code doesnt stop after your checks.

so you need something like:

    def window_check(self):
        if self.get_state("sensor.matt_window")== "closed" and self.get_state("sensor.family_room_window") == "closed" and self.get_state("sensor.dining_room_window") == "closed":
            return True
        else:
            return False

then you can change your callback to:

    def fan_control_cb(self, entity, attribute, old, new, kwargs):
        if new == "Off":
            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.window_check() and self.door_check():
# 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, "Off")
            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")

as you see i did change the part after setting it to false to make sure that only something gets turned off if something is on.
and you check for setting “False” and set setting “Off”, they need to be the same :wink:

I gotcha now. Now I need to put some checks in place to monitor all the windows in case they all change to closed while the fan is in operation. But first I need to go look at some code examples I know I’ve seen around here where I can monitor multiple entities for a state change and use just one callback for those.

Edit: I see I need to pass my window sensors through in the apps.yaml which thats fine it will let me clean up the code a little as I could just use it as an arg instead of specifying all the windows and as I add more window sensors down the road one place to update them :slight_smile:

you mean

init(..):
  for my_entity in self.args["window_list"]:
    self.listen_state(self.change_state,my_entity)

def change_state(...):
  windows = "closed"
  for my_entity in self.args["window_list"]:
    if self.get_state(my_entity) != "closed:
      windows = "opened"
    if windows == "opened":
      self.log("somewhere there is an opened window")
    else:
      self.log("all windows are closed")

?? :wink: :wink:

1 Like

trying to figure out how to put that to work in the window_check function I completely understand what we’re doing here just need to put it to work in the window check so I can clean that line up.

create what i gave to a seperate app.
add this at the end

self.set_state("sensor.all_doors_and_windows", state = windows)

and you can check for opened and closed in the other app with a get_state :wink:

Woah we’re going advanced now. So you mean we’re going to make a new app with the window/door checker built into it. Then with our current app we’re going to make a call back to the other app? Is that what I’m understanding here?

EDIT: I see now we’re creating a new sensor aha like we did with that stale sensor check. I see now.

i mean you create this app:

init(..):
  for my_entity in self.args["window_list"]:
    self.listen_state(self.change_state,my_entity)

def change_state(...):
  windows = "closed"
  for my_entity in self.args["window_list"]:
    if self.get_state(my_entity) != "closed:
      windows = "opened"
    if windows == "opened":
      self.log("somewhere there is an opened window")
    else:
      self.log("all windows are closed")
  self.set_state("sensor.all_doors_and_windows", state = windows)

and in the other app on the place where you have

    def window_check(self):
        if self.get_state("sensor.matt_window")== "closed" and self.get_state("sensor.family_room_window") == "closed" and self.get_state("sensor.dining_room_window") == "closed":
            return True
        else:
            return False

like i showed you, you just have to do

    def window_check(self):
        if self.get_state("sensor.all_windows_and_doors")== "closed":
            return True
        else:
            return False

but then you also could change the line

        elif self.window_check() and self.door_check():

to

       elif  self.get_state("sensor.all_windows_and_doors")!= "closed":

and lose your subfunctions.

1 Like

I like the ability to lose the sub functions cleans it up a good bit.

and now we have

import appdaemon.plugins.hass.hassapi as hass

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.media_player = 'media_player.living_room'
        self.listen_state(self.fan_control_cb, self.fan_setting)

# 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 == "Off":
            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":
# 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, "Off")
            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")

Now I’ll do the listen state and cb function for that for when they are all closed to turn off the fan.

EDIT: With the check in place to watch for all doors and windows to be closed

import appdaemon.plugins.hass.hassapi as hass

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.media_player = 'media_player.living_room'
        self.listen_state(self.fan_control_cb, self.fan_setting)
        self.listen_state(self.door_window_closed_cb, "sensor.all_doors_and_windows" new="closed")

# 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 == "Off":
            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":
# 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, "Off")
            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_window_closed_cb(self, entity, attribute, old, new, kwargs):
        self.log("All doors and windows are closed we need to turn off the fan")
        if self.get_state(self.fan_setting) != "Off":
            self.select_option(self.fan_setting, "Off")
            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")
1 Like

i forgot 1 thing.
in the initialize from your app that creates the sensor for alldoors and windows you need to add this:

if not self.entity_exists("sensor.all_doors_and_windows"):
  self.set_state("sensor.all_doors_and_windows", state = "unknown")

that will make sure that the sensor gets created as soon as you restart HA, but it wont overwrite the sensor when you restart AD without restarting HA.

1 Like

Ah yes forgot about that little piece from the stale_sensor check thanks dude.

1 Like

and in that if statement you could also run the callback, to make sure that the sensor is set to the right state like:

if not self.entity_exists("sensor.all_doors_and_windows"):
  self.set_state("sensor.all_doors_and_windows", state = "unknown")
  self.change_state("","","","")

What do all the quotes in that do?

Edit: I think I understand. These are the args to pass into the function but we don’t really need to pass in any args so we’re going to just pass in a bunch of nothing to it, force it to run and update the sensor.

1 Like

the function is a callback from a listen_state so it will look like:

def change_state(self, entity, attribute, old, new, kwargs):

if you want to call that function you need to provide 4 args (entity,attribute,old and new)
none of them are used in the function so you can without trouble call that function with empty values for those args.

i love it that you ask questions and then come up with the answers yourselve :wink:

1 Like

I’ll bet you never thought a whole house fan could be so involved lol

i never thought about fans at all :wink:
i live in a moderate climate :wink:
we are glad when the temp goes up a bit.

1 Like