patrickcollins12 / esphome-fan-controller

ESPHome Fan Controller
431 stars 49 forks source link

cool setup just what i was looking for but my fan does not shut off totally #2

Closed dartfrogdk closed 2 years ago

dartfrogdk commented 2 years ago

i set the fan at 0% and 50% but still it wont stop running totally, do you know what i can do

sophof commented 2 years ago

I was working on the electrical setup of my own implementation of this this weekend and ran into the same problem, apparently some fans are just set up this way. You have to use a relay to switch off the power entirely to the fan. I needed a level shifter as well to both up the PWM signal and the relay control signal to 5v. The next hurdle was that the fan then would run purely from the pwm output, so I also needed to switch off the level shifter output, luckily my level shifter allowed for control there too.

The end result (just for the fan for now, no PID control yet) looks as follows in the yam. GPIO27 switches off the relay (via the lvl shifter) and GPIO14 is the PWM signal itself (also via the lvl shifter). GPIO26 enables the lvl shifter for the pwm output, so switching that off blocks the pwm entirely (doing that without the relay will just run the fan at full speed sadly):

output:
  - platform: gpio
    pin: GPIO26
    id: pwm_on
    inverted: true
  - platform: gpio
    pin: GPIO27
    id: fan_supply
  - platform: ledc
    pin: GPIO14
    frequency: 25000 Hz
    id: testfan

fan:
  - platform: speed
    output: testfan
    name: "Test Fan"
    on_turn_on:
      - output.turn_on: fan_supply
      - logger.log: "Power of Fan turned ON"
      - output.turn_on: pwm_on
      - logger.log: "PWM enabled"
    on_turn_off:
      - output.turn_off: fan_supply
      - logger.log: "Power of Fan turned OFF"
      - output.turn_off: pwm_on
      - logger.log: "PWM disabled"

With this the fan switches off at 0. 1% is then the startup RPM of my fan.

dartfrogdk commented 2 years ago

okay thanks i will try this way

oguzatagan commented 1 year ago

Hi everyone. My fan is wont stop. I tried to edit the code but I couldn't. can you help me?

substitutions:
  friendly_name: Console Fan

esphome:
  name: console-fan

# Throttle writing parameters to the internal flash memory to reduce ESP memory wear / degradation
preferences:
  flash_write_interval: 120min

esp8266:
  board: esp01_1m

# pid climate log update is noisy, dial it back to warn
logger:
  level: DEBUG
  logs: 
    climate: ERROR
    dht: WARN

# Enable Home Assistant API
api:
  encryption:
    key: ""

ota:
  password: ""

wifi:
  ssid: 
  password: 

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Inverter-Fan Fallback Hotspot"
    password: ""

captive_portal:

number:

  ## OPTIONAL:
  # RECEIVE KP, KI and KD parameters from input_text.kx helpers in 
  # Home Assistant. See the PID controller below
  # These helper values will get saved to flash thus permanently over-riding 
  # the initial values set in the PID below.

  # KP
  - platform: template
    name: kp
    icon: mdi:chart-bell-curve
    restore_value: true
    initial_value: 0.3
    min_value: 0
    max_value: 50
    step: 0.001
    set_action: 
      lambda: |- 
        id(console_thermostat).set_kp( x );
  # KI
  - platform: template
    name: ki
    icon: mdi:chart-bell-curve
    restore_value: true
    initial_value: 0.0015
    min_value: 0
    max_value: 50
    step: 0.0001
    set_action: 
      lambda: id(console_thermostat).set_ki( x );

  # KD
  - platform: template
    name: kd
    icon: mdi:chart-bell-curve
    restore_value: true
    initial_value: 0.0
    min_value: -50
    max_value: 50
    step: 0.001
    set_action: 
      lambda: id(console_thermostat).set_kd( x );

  # Set threshold low
  - platform: template
    name: Deadband Threshold Low
    icon: mdi:chart-bell-curve
    restore_value: true
    initial_value: -1.0
    min_value: -20
    max_value: 0
    step: 0.1
    set_action: 
      lambda: id(console_thermostat).set_threshold_low( x );

  # Set threshold high
  - platform: template
    name: Deadband Threshold High
    icon: mdi:chart-bell-curve
    restore_value: true
    initial_value: 0.4
    min_value: 0
    max_value: 20
    step: 0.1
    set_action: 
      lambda: id(console_thermostat).set_threshold_high( x );

  # Set ki multiplier
  - platform: template
    name: Deadband ki Multiplier
    icon: mdi:chart-bell-curve
    restore_value: true
    initial_value: 0.04
    min_value: 0
    max_value: .2
    step: 0.01
    set_action: 
      lambda: id(console_thermostat).set_ki_multiplier( x );

text_sensor:

  # Send IP Address
  - platform: wifi_info
    ip_address:
      name: $friendly_name IP Address

  # Send Uptime in raw seconds
  - platform: template
    name: $friendly_name Uptime
    id: uptime_human
    icon: mdi:clock-start

sensor:

  # Send WiFi signal strength & uptime to HA
  - platform: wifi_signal
    name: $friendly_name WiFi Strength
    update_interval: 60s

  # This is a bit of overkill. It sends a human readable 
  # uptime string 1h 41m 32s instead of 6092 seconds
  - platform: uptime
    name: $friendly_name Uptime
    id: uptime_sensor
    update_interval: 60s
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            # Custom C++ code to generate the result
            state: !lambda |-
              int seconds = round(id(uptime_sensor).raw_state);
              int days = seconds / (24 * 3600);
              seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;
              seconds = seconds % 3600;
              int minutes = seconds /  60;
              seconds = seconds % 60;
              return (
                (days ? to_string(days) + "d " : "") +
                (hours ? to_string(hours) + "h " : "") +
                (minutes ? to_string(minutes) + "m " : "") +
                (to_string(seconds) + "s")
              ).c_str();

########################################################
# START THE FAN CONTROLLER SETUP

  - platform: template
    name: $friendly_name p term
    id: p_term
    unit_of_measurement: "%"
    accuracy_decimals: 2

  - platform: template
    name: $friendly_name i term
    id: i_term
    unit_of_measurement: "%"
    accuracy_decimals: 2

  - platform: template
    name: $friendly_name d term
    id: d_term
    unit_of_measurement: "%"
    accuracy_decimals: 2

  - platform: template
    name: $friendly_name output value
    unit_of_measurement: "%"
    id: o_term
    accuracy_decimals: 2

  - platform: template
    name: $friendly_name error value
    id: e_term
    accuracy_decimals: 2

  - platform: template
    name: $friendly_name is in deadband
    id: in_deadband_term
    accuracy_decimals: 0

  # GET TEMP NTC
  - platform: ntc
    sensor: resistance_sensor
    calibration:
      b_constant: 3950
      reference_temperature: 25°C
      reference_resistance: 100kOhm
    name: "Temperature"
    id: console_fan_temperature
    accuracy_decimals: 1
    filters:
      - sliding_window_moving_average:
          window_size: 10
          send_every: 10
      - offset: 0

  # Example source sensors:
  - platform: resistance
    id: resistance_sensor
    sensor: source_sensor
    configuration: UPSTREAM
    resistor: 10kOhm
    name: Resistance Sensor
  - platform: adc
    id: source_sensor
    pin: A0
    update_interval: 1s

  # Take the "COOL" value of the pid and send 
  # it to the frontend to graph the output voltage
  - platform: pid
    name: "Fan Speed (PWM Voltage)"
    climate_id: console_thermostat
    type: COOL

output:
  - platform: esp8266_pwm
    id: console_fan_speed
    pin:
      number: 2
    min_power: 0.05 #Slowest Speed for Noctua Fans is 5% - Set to 0 for 'Other' Fans
    frequency: 22.5kHz #Pulse the fan fast to prevent noise

  - platform: gpio
    pin: 0
    id: fan_mosfet

fan:
  - platform: speed
    output: console_fan_speed
    name: "Fan mosfet"
    on_turn_on:
      - output.turn_on: fan_mosfet
      - output.turn_on: console_fan_speed
    on_turn_off:
      - output.turn_off: fan_mosfet
      - output.turn_off: console_fan_speed

climate:
  - platform: pid
    name: "Console Fan Thermostat"
    id: console_thermostat
    sensor: console_fan_temperature

    # It is summer right now, so 30c is a decent target.
    default_target_temperature: 30°C
    cool_output: console_fan_speed

    # ON state change, publish the values to the x_term numbers defined 
    # above, so that they can be viewed in HA
    on_state:
      - sensor.template.publish:
          id: p_term
          state: !lambda 'return -id(console_thermostat).get_proportional_term() * 100.0;'
      - sensor.template.publish:
          id: i_term
          state: !lambda 'return -id(console_thermostat).get_integral_term()* 100.0;'
      - sensor.template.publish:
          id: d_term
          state: !lambda 'return -id(console_thermostat).get_derivative_term()* 100.0;'
      - sensor.template.publish:
          id: o_term
          state: !lambda 'return -id(console_thermostat).get_output_value()* 100.0;'
      - sensor.template.publish:
          id: in_deadband_term
          state: !lambda 'return id(console_thermostat).in_deadband();'
      - sensor.template.publish:
          id: e_term
          state: !lambda 'return -id(console_thermostat).get_error_value();'

    # The extents of the HA Thermostat
    visual:
      min_temperature: 15 °C
      max_temperature: 60 °C

    # See the README for setting up these parameters.
    # These are over ridden by the number templates above.
    control_parameters:
      kp: 0.3
      ki: 0.0015
      kd: 0
      max_integral: 0.0
      output_averaging_samples: 1
      derivative_averaging_samples: 5

    # How to behave when close to the target temperature?
    deadband_parameters:
      threshold_high: 0.4°C
      threshold_low: -1.0°C
      kp_multiplier: 0.0
      ki_multiplier: 0.04
      kd_multiplier: 0.0
      deadband_output_averaging_samples: 15

switch:
  # Expose an ESP8266 restart button to HA
  - platform: restart
    name: ${friendly_name} ESP8266 Restart
    id: console_fan_restart

# Restart every day at 12:30am. 
# I've had some memory issues lockup 
# the device after a couple weeks
time:
  - platform: homeassistant
    on_time:
      # Every morning at 05:30am
    - seconds: 0
      minutes: 30
      hours: 05
      then:
       - switch.turn_on: console_fan_restart

# I was able to find good KP,KI,KD values manually, per the instructions,
# but you can try pressing the autotune button from home assistant and copying the 
# values it produces. 
# See more at: https://esphome.io/components/climate/pid.html#climate-pid-autotune-action
button:
- platform: template
  name: "PID Climate Autotune"
  on_press: 
    - climate.pid.autotune: console_thermostat
sophof commented 1 year ago

It is hard to parse your comment this way, can you put your yaml in a code block? Just surround it with ``` before and after the code.

I changed my code a lot in the mean time and right now I have a much simpler version, where the fans are always on if my receiver is on in the TV cabinet that I'm cooling. Since I'm using two 140mm fans I literally don't hear them anyway on the lowest speed.

It simply imports a binary sensor from home assistant to check if the receiver is on, and if it is, switches the climate to COOL and turns on the relay and PWM. I also added a few simple buttons to be able to do it by hand (but I never have to use them anymore). My code now looks as follows:

substitutions:
  friendly_name: Receiver Fan

esphome:
  name: tv-meubel-fan-links

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "redacted"

ota:
  password: "redacted"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: none
  fast_connect: true

dallas:
  - pin: 26
    update_interval: 10s

binary_sensor:
  - platform: homeassistant
    id: receiver_power
    entity_id: binary_sensor.receiver_power
    on_state:
      then:
        - script.execute: toggle_climate

script:
  - id: toggle_climate
    mode: restart 
    then:
      if:
        condition:
          binary_sensor.is_on: receiver_power
        then:
          - climate.control:
              id: receiver_thermostat
              mode: "COOL"
        else:
          - delay: 5 min
          - climate.control:
              id: receiver_thermostat
              mode: "OFF"

sensor:
# ########################################################
# # START THE FAN CONTROLLER SETUP

  - platform: template
    name: $friendly_name p term
    id: p_term
    unit_of_measurement: "%"
    accuracy_decimals: 2

  - platform: template
    name: $friendly_name i term
    id: i_term
    unit_of_measurement: "%"
    accuracy_decimals: 2

  - platform: template
    name: $friendly_name d term
    id: d_term
    unit_of_measurement: "%"
    accuracy_decimals: 2

  - platform: template
    name: $friendly_name output value
    unit_of_measurement: "%"
    id: o_term
    accuracy_decimals: 2

  - platform: template
    name: $friendly_name error value
    id: e_term
    accuracy_decimals: 2

  - platform: dallas
    address: 0x940000000ca14e28
    name: "Receiver Temperature"
    id: receiver_temperature
    accuracy_decimals: 1
    filters:
    - filter_out: 85.0
    - throttle: 30s

  # Take the "COOL" value of the pid and send 
  # it to the frontend to graph the output voltage
  - platform: pid
    name: "Fan Speed (PWM Voltage)"
    climate_id: receiver_thermostat
    type: COOL

output:
  - platform: ledc
    id: fan_speed
    pin: GPIO19
    frequency: "25000 Hz"

switch:
  - platform: gpio
    name: PWM gpio
    pin:
      number: GPIO18
      inverted: true
    id: pwm_on
  - platform: gpio
    name: Fan Power
    pin:
      number: GPIO03
    id: fan_supply

climate:
  - platform: pid
    name: "Receiver Fan Thermostat"
    id: receiver_thermostat
    sensor: receiver_temperature

    default_target_temperature: 33°C
    cool_output: fan_speed

    deadband_parameters:
      threshold_high: 0.4°C
      threshold_low: -1.0°C
      kp_multiplier: 0.0   # proportional gain turned off inside deadband
      ki_multiplier: 0.2  # integral accumulates at only 20% of normal ki
      kd_multiplier: 0.0   # derviative is turned off inside deadband
      deadband_output_averaging_samples: 10   # average the output over 10 samples within the deadband

    on_state:
      - sensor.template.publish:
          id: p_term
          state: !lambda 'return -id(receiver_thermostat).get_proportional_term() * 100.0;'
      - sensor.template.publish:
          id: i_term
          state: !lambda 'return -id(receiver_thermostat).get_integral_term()* 100.0;'
      - sensor.template.publish:
          id: d_term
          state: !lambda 'return -id(receiver_thermostat).get_derivative_term()* 100.0;'
      - sensor.template.publish:
          id: o_term
          state: !lambda 'return -id(receiver_thermostat).get_output_value()* 100.0;'
      - sensor.template.publish:
          id: e_term
          state: !lambda 'return -id(receiver_thermostat).get_error_value();'
      - if:
          condition:
            and:
              - lambda: 'return id(receiver_thermostat).mode == esphome::climate::ClimateMode::CLIMATE_MODE_OFF;'
              - switch.is_on: fan_supply
              - switch.is_on: pwm_on
          then:
            - switch.turn_off: fan_supply
            - switch.turn_off: pwm_on
            - logger.log: "Fan switched off"
      - if:
          condition:
            and:
              - lambda: 'return id(receiver_thermostat).mode != esphome::climate::ClimateMode::CLIMATE_MODE_OFF;'
              - switch.is_off: fan_supply
              - switch.is_off: pwm_on
          then:
            - switch.turn_on: fan_supply
            - switch.turn_on: pwm_on
            - logger.log: "Fan switched on"

    # The extents of the HA Thermostat
    visual:
      min_temperature: 20 °C
      max_temperature: 50 °C
      temperature_step: 1 °C

    # See the README for setting up these parameters.
    # These are over ridden by the number templates above.
    control_parameters:
      kp: 0.05
      ki: 0.0002
      kd: 0
      max_integral: 0.0
      output_averaging_samples: 4