Hot computer exhaust removal from under the desk - AC Infinity Blower made smart with ESPHome

I am not a gamer so my computer doesn’t normally run very hot. Recently my son started playing video games together and we found that the heat coming from under the desk (office style with closed back) is unbearable after playing for a while. In the graph below you can see that the room is at about 25C (electronics warm it up!) and the computer exhaust was at 30C under normal office use. When the gaming started the exhaust spiked to 38C (>100F). The blower I automated with ESPHome turned up the fan speed and sitting at the desk was way more pleasant. Where the two temperatures match is when I put the computer to sleep. While it really doesn’t matter, it is likely that one of the sensors is a bit off as I would expect the two to match when the computer is off.

This is the assembly while I was testing it. The oversized white cable gland is there because there was a huge hole in the box and the cable gland fit well. It looks terrible but works and it was a quick and easy solution with what I had. I added a power connector right next to it. The 4 screws are what hold the PCB board I built.

The assembly shown in the picture is mounted under table behind my desk and currently just blows the warm exhaust into the room, but I can easily add a flexible conduit to outside the door if it gets too hot in the room.

With minimal testing I found that the fan runs at 12V and takes a PWM signal for speed control. The power electronics that control the motor are inside the motor so automating the fan was as simple as providing a 5V PWM signal straight from the ESP32. The power supply is 12V 2A as the fan requires around 12V 1A, so I added a small buck converter that brings the voltage down to 5V for the ESP32. I need to look into smaller variants as the one I used is overkill. Anyone have suggestions?

The only trouble I had with this build is that I connected the data line of the DS18B20 temperature sensor to D13 and it did not work… maybe the IO is broken or maybe I am missing something… anyhow connecting it to D27 worked perfectly.

D23 provides the PWM signal.

To make it all fit I had to use low profile standoffs and grind down some plastic parts inside the box.

This is a newer version of the blower I have… the controller shown is “dumb” and the LED backlight broke as in 2 previous ones I had… Hopefully AC Infinity figured their issues out on newer versions. Anyhow, it is way cooler now (pun intended) that I can control it via Home Assistant thanks to ESPHome!

Below is the YAML I used. It requires a few helpers to be created for it to interface to HA. I am certain there are better ways to control the fan speed so if anyone has suggestions I am glad to try them out!

EDIT (12-JUN-2023): YAML below is now replaced by what should be a better revision… see next code block.

substitutions:
  devicename: computer-exhaust-fan
  devicename_no_dashes: computer_exhaust_fan
  friendly_devicename: "Computer Exhaust Fan"
  device_description: "Computer Exhaust Fan"
  update_interval_s: "5s"
  update_interval_wifi: "60s"
  pwm00_t: "12.0" #23.0
  pwm05_t: "11.5" #23.5
  pwm10_t: "11.0" #24.0
  pwm20_t: "10.0" #25.0
  pwm30_t: "9.0" #26.0
  pwm40_t: "8.0" #27.0
  pwm50_t: "7.0" #28.0
  pwm60_t: "6.0" #29.0
  pwm70_t: "5.0" #30.0
  pwm80_t: "3.0" #32.0
  pwm90_t: "1.0" #34.0
  pwm100_t: "0.0" #35.0
  
esphome:
    name: ${devicename}
    comment: ${device_description}
    platform: ESP32
    board: esp32doit-devkit-v1


# Enable logging
logger:

# Enable Home Assistant API
api: 
  password: !secret api_pwd

ota:
  password: !secret ota_pwd

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_password

#Faster than DHCP. Also use if can't reach because of name change
#  manual_ip:
#    static_ip: 192.168.3.195
#    gateway: 192.168.3.1
#    subnet: 255.255.255.0
#    dns1: 192.168.1.25
#    dns2: 192.168.1.26

#Manually override what address to use to connect to the ESP.
#Defaults to auto-generated value. Example, if you have changed your
#static IP and want to flash OTA to the previously configured IP address.
  use_address: 192.168.3.195
  
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename} Hotspot"
    password: !secret iot_wifi_password

web_server:
  port: 80
  include_internal: true

captive_portal:

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "${friendly_devicename}: IP"
      icon: "mdi:ip-outline"
      update_interval: ${update_interval_wifi}
    ssid:
      name: "${friendly_devicename}: SSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
    bssid:
      name: "${friendly_devicename}: BSSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
    mac_address:
      name: "${friendly_devicename}: MAC"
      icon: "mdi:network-outline"
    scan_results:
      name: "${friendly_devicename}: Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true

#https://esphome.io/guides/automations.html?highlight=restore_value#bonus-2-global-variables
globals: ##to set default reboot behavior
  - id: computer_exhaust_fan
    type: bool
    restore_value: no
    initial_value: "true"
  - id: fan_speed
    type: float
    restore_value: no
    initial_value: '0'
  - id: fan_speed_or
    type: float
    restore_value: no
    initial_value: '0.0'
  - id: max_fan_speed_temperature
    type: float
    restore_value: yes
    initial_value: '35.0'

switch:
  - platform: restart
    name: "${friendly_devicename}: Restart"

button:
  - platform: safe_mode
    name: "${friendly_devicename}: Restart (Safe Mode)"

binary_sensor: #pull in HA value
  - platform: homeassistant
    id: enable_fans
    internal: true
    entity_id: input_boolean.${devicename_no_dashes}
    on_state:
      - globals.set:
          id: computer_exhaust_fan
          value: !lambda 'return id(enable_fans).state;'

  - platform: homeassistant
    id: override_control
    internal: true
    entity_id: input_boolean.${devicename_no_dashes}_override_speed


output:
  - platform: ledc
    pin: 23
    frequency: 19531 Hz
    id: fan_pwm

#Configuration entry for 18B20 sensor
dallas:
  - pin: 27
    update_interval: ${update_interval_s}

sensor:
  - platform: wifi_signal
    name: "${friendly_devicename}: WiFi Signal"
    update_interval: ${update_interval_wifi}
    device_class: signal_strength

  - platform: template
    id: report_fan_speed
    name: "${friendly_devicename}: Fan Speed"
    icon: "mdi:fan"
    lambda: return id(fan_speed) * 100;
    unit_of_measurement: "%"
    accuracy_decimals: 0
    update_interval: ${update_interval_s}

  - platform: homeassistant
    id: fan_speed_override
    internal: true
    entity_id: input_number.${devicename_no_dashes}_speed
    on_value:
      - globals.set:
          id: fan_speed_or
          value: !lambda 'return id(fan_speed_override).state;'

  - platform: homeassistant
    id: max_fan_speed_temp
    internal: true
    entity_id: input_number.${devicename_no_dashes}_max_speed_temperature
    on_value:
      - globals.set:
          id: max_fan_speed_temperature
          value: !lambda 'return id(max_fan_speed_temp).state;'

  - platform: dallas
    address: 0xf2020292454de528
    name: "${friendly_devicename}: Temperature"
    id: computer_temp
    device_class: "temperature"
    state_class: "measurement"
    on_value:
        then:
          lambda: |-
            
            if (id(computer_exhaust_fan)) {      
                  if (!id(override_control).state) {
                      if (id(computer_temp).state < id(max_fan_speed_temperature) - 12.0) {
                          id(fan_speed) = 0.0;
                      }
                      /*Adding 0.5C to prevent constant on/off when temp is right at 23*/
                      else if ((id(computer_temp).state >= id(max_fan_speed_temperature) - 12.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 11.5)) {
                          ESP_LOGD("PWM", "Leave PWM as is. Dead band.");
                      }
                      else if ((id(computer_temp).state >= id(max_fan_speed_temperature) - 11.5) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 11.0)) {
                          id(fan_speed) = 0.05;
                      }
                      else if ((id(computer_temp).state > id(max_fan_speed_temperature) - 11.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 10.0)) {
                          id(fan_speed) = 0.1;
                      }
                      else if ((id(computer_temp).state > id(max_fan_speed_temperature) - 10.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 9.0)) {
                          id(fan_speed) = 0.2;
                      }
                      else if ((id(computer_temp).state > id(max_fan_speed_temperature) - 9.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 8.0)) {
                          id(fan_speed) = 0.3;
                      }
                      else if ((id(computer_temp).state > id(max_fan_speed_temperature) - 8.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 7.0)) {
                          id(fan_speed) = 0.4;
                      }
                      else if ((id(computer_temp).state > id(max_fan_speed_temperature) - 7.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 6.0)) {
                          id(fan_speed) = 0.5;
                      }
                      else if ((id(computer_temp).state > id(max_fan_speed_temperature) - 6.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 5.0)) {
                          id(fan_speed) = 0.6; 
                      }
                      else if ((id(computer_temp).state > id(max_fan_speed_temperature) - 5.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 3.0)) {
                          id(fan_speed) = 0.7; 
                      }
                      else if ((id(computer_temp).state > id(max_fan_speed_temperature) - 3.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 1.0)) {
                          id(fan_speed) = 0.8; 
                      }
                      else if ((id(computer_temp).state > id(max_fan_speed_temperature) - 1.0) and (id(computer_temp).state <= id(max_fan_speed_temperature) - 0.0)) {
                          id(fan_speed) = 0.9;
                      }
                      else {
                          id(fan_speed) = 1.0;
                          ESP_LOGD("ALERT", "OVER int(id(max_fan_speed_temperature))C! Setting fan to 100");
                      }
                      ESP_LOGD("ALERT", "PWM: TEMPERATURE CONTROL");
                  } else {
                      id(fan_speed) = id(fan_speed_or) / 100;
                      ESP_LOGD("ALERT", "PWM: OVERRIDE : ON @ %d%%", int(id(fan_speed_or)));
                      //ESP_LOGD("ALERT", "PWM OVERRIDE: ON - SETPOINT: %d%%", int(id(fan_speed) * 100));
                      //ESP_LOGD("PWM", "PWM OVERRIDE: %d%%" , int(id(fan_speed_or)));
                      
                  }
            } else {
              id(fan_speed) = 0.0;
              ESP_LOGD("ALERT", "FANS TURNED OFF");
            }
            id(fan_pwm).set_level(id(fan_speed));
            ESP_LOGD("PWM", "PWM: %d%%" , int(id(fan_speed) * 100));
            ESP_LOGD("PWM", "TEMP THRESHOLD: %d" , int(id(max_fan_speed_temperature)));

UPDATED YAML FOR COMPUTER FAN CONTROLLER (12-JUN-2023):

substitutions:
  devicename: computer-exhaust-fan
  devicename_no_dashes: computer_exhaust_fan
  friendly_devicename: "Computer Exhaust Fan"
  device_description: "Computer Exhaust Fan"
  update_interval_s: "2s"
  update_interval_wifi: "120s"
  #temp_calibration: "-0.7" #The DS18B20 measures about 0.7C too high
  
esphome:
  name: ${devicename}
  friendly_name: "${friendly_devicename}"
  comment: ${device_description}
  platform: ESP32
  board: esp32doit-devkit-v1
  on_boot:
     then:
      # Using multiple lambdas as the compiler was complaining otherwise
      - lambda: |-
          auto call = id(max_fan_speed_temperature_ha).make_call();
          call.set_value(id(max_fan_speed_temperature));
          call.perform();
      - lambda: |-
          auto call = id(fan_speed_override_ha).make_call();
          call.set_value(id(fan_speed_override));
          call.perform();
      # Push values to HA for initital display
      - lambda: |-
          id(override_fan_speed_ha).publish_state(id(override_fan_speed));
          id(computer_exhaust_fan_ha).publish_state(id(computer_exhaust_fan));
          id(room_temperature_ha).publish_state(id(room_temperature_sensor).state);  // Report Room Temperature from Sensor directly
          id(report_room_humidity).publish_state(id(room_humidity_sensor).state);        // Report Room Humidity from Sensor directly

# Enable logging
logger:

# Enable Home Assistant API
api: 
  encryption:
    key: JTPbDiE5vCSb2bF6MbVewjgz6PSOSbuDUCQXFNGcVMQ=

ota:
  password: !secret ota_pwd

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_password

#Faster than DHCP. Also use if can't reach because of name change
#  manual_ip:
#    static_ip: 192.168.3.195
#    gateway: 192.168.3.1
#    subnet: 255.255.255.0
#    dns1: 192.168.1.25
#    dns2: 192.168.1.26

#Manually override what address to use to connect to the ESP.
#Defaults to auto-generated value. Example, if you have changed your
#static IP and want to flash OTA to the previously configured IP address.
  use_address: 192.168.3.195
  
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename}"
    password: !secret iot_wifi_password

web_server:
  port: 80
  include_internal: true

captive_portal:

# Sync time with Home Assistant
time:
  - platform: homeassistant
    id: ha_time

i2c:
  - id: bus_a
    sda: 32
    scl: 33
    scan: true

#Configuration entry for 18B20 sensor
dallas:
  - pin: 27
    update_interval: ${update_interval_s}

text_sensor:
  - platform: wifi_info
    ip_address:
      name: "IP"
      icon: "mdi:ip-outline"
      update_interval: ${update_interval_wifi}
    ssid:
      name: "SSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
    bssid:
      name: "BSSID"
      icon: "mdi:wifi-settings"
      update_interval: ${update_interval_wifi}
    mac_address:
      name: "MAC"
      icon: "mdi:network-outline"
    scan_results:
      name: "Wifi Scan"
      icon: "mdi:wifi-refresh"
      disabled_by_default: true

#https://esphome.io/guides/automations.html?highlight=restore_value#bonus-2-global-variables
globals: ##to set default reboot behavior
  # Variables to recall (saves them after 1 minute by default, can be changed)
  - id: computer_exhaust_fan
    type: bool
    restore_value: yes
    initial_value: "false"
  - id: override_fan_speed
    type: bool
    restore_value: yes
    initial_value: "false"
  - id: fan_speed_override
    type: float
    restore_value: yes
    initial_value: '0.0'
  - id: max_fan_speed_temperature
    type: float
    restore_value: yes
    initial_value: '35.0'

  - id: fan_speed
    type: float
    restore_value: no
    initial_value: '0'
  - id: computer_exhaust_temperature
    type: float
    restore_value: no
    initial_value: '0.0'
  - id: room_temperature
    type: float
    restore_value: no
    initial_value: '0.0'
  - id: room_humidity
    type: float
    restore_value: no
    initial_value: '0.0'

switch:
  - platform: restart
    name: "Restart"

  - platform: template
    name: ""
    id: computer_exhaust_fan_ha
    icon: "mdi:fan"
    optimistic: true
    turn_on_action:
      - globals.set:
          id: computer_exhaust_fan
          value: 'true'
    turn_off_action:
      - globals.set:
          id: computer_exhaust_fan
          value: 'false'

  - platform: template
    name: "Override Fan Speed"
    id: override_fan_speed_ha
    icon: "mdi:fan"
    optimistic: true
    turn_on_action:
      - globals.set:
          id: override_fan_speed
          value: 'true'
    turn_off_action:
      - globals.set:
          id: override_fan_speed
          value: 'false'

button:
  - platform: safe_mode
    name: "Restart (Safe Mode)"

output:
  - platform: ledc
    pin: 23
    frequency: 19531 Hz
    id: fan_pwm

number:
  - platform: template
    name: "Max Fan Speed Temperature"
    id: max_fan_speed_temperature_ha
    unit_of_measurement: "°C"
    optimistic: true #Not sure what this does, research. leave? remove?
    min_value: 25
    max_value: 40
    step: 1
    on_value:
      then:
        - lambda: |-
            id(max_fan_speed_temperature) = id(max_fan_speed_temperature_ha).state;

  - platform: template
    name: "Fan Speed Override"
    icon: "mdi:speedometer"
    id: fan_speed_override_ha
    unit_of_measurement: "%"
    optimistic: true #Not sure what this does, research. leave? remove?
    min_value: 0
    max_value: 100
    step: 1
    on_value:
      then:
        - lambda: |-
            id(fan_speed_override) = id(fan_speed_override_ha).state;

sensor:
  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: ${update_interval_wifi}
    device_class: signal_strength

  - platform: template
    id: fan_speed_ha
    name: "Fan Speed"
    icon: "mdi:fan"
    #lambda: return id(fan_speed) * 100;
    unit_of_measurement: "%"
    accuracy_decimals: 0
    update_interval: never


#  - platform: qmp6988
#    i2c_id: bus_a
#    temperature:
#      name: "Temperature 2"
#      oversampling: 16x
#    pressure:
#      name: "Pressure"
#      oversampling: 16x
#    address: 0x70
#    update_interval: 5s
#    iir_filter: 2x

  - platform: sht3xd
    i2c_id: bus_a
    address: 0x44
    update_interval: 1s
    humidity:
      id: room_humidity_sensor
      internal: true
      force_update: true
      device_class: "humidity"
      state_class: "measurement"
      on_value:
        - globals.set:
            id: room_humidity
            value: !lambda 'return id(room_humidity_sensor).state;'
    temperature:
      id: room_temperature_sensor
      internal: true
      force_update: true
      device_class: "temperature"
      state_class: "measurement"
      on_value:
        - globals.set:
            id: room_temperature
            value: !lambda 'return id(room_temperature_sensor).state;'

  - platform: template
    name: "Room Temperature"
    id: room_temperature_ha
    device_class: "temperature"
    state_class: "measurement"
    unit_of_measurement: "°C"
    update_interval: never
    filters:
      # Map MEASURED -> TRUTH
      - calibrate_linear:
        - 0.0 -> 0.0
        - 25.0 -> 24.2
        - 40.0 -> 39.2
      - lambda: return x;

  - platform: template
    name: "Room Humidity"
    id: report_room_humidity
    device_class: "humidity"
    state_class: "measurement"
    unit_of_measurement: "%"
    update_interval: never

  - platform: dallas
    address: 0xf2020292454de528
    id: computer_exhaust_temperature_sensor
    device_class: "temperature"
    state_class: "measurement"
    unit_of_measurement: "°C"
    filters:
      - sliding_window_moving_average:
          window_size: 10
          send_every: 10
      # Map MEASURED -> TRUTH
      - calibrate_linear:
        - 0.0 -> 0.0
        - 20.0 -> 19.1
        - 25.0 -> 24.1
        - 40.0 -> 39.1
      - lambda: return x;
    on_value:
      - globals.set:
          id: computer_exhaust_temperature
          value: !lambda 'return id(computer_exhaust_temperature_sensor).state;'

  - platform: template
    name: "Exhaust Temperature"
    device_class: "temperature"
    state_class: "measurement"
    unit_of_measurement: "°C"
    id: computer_exhaust_temperature_ha
    update_interval: never

interval:
  - interval: 2s
    then:
      lambda: |-
        if (id(computer_exhaust_fan)) {      
            if (!id(override_fan_speed)) {
                if (id(computer_exhaust_temperature) > id(max_fan_speed_temperature)) {
                  id(fan_speed) = 1.0;
                  ESP_LOGD("ALERT", "OVER int(id(max_fan_speed_temperature))C! Setting fan to 100");
                }
                else if (id(computer_exhaust_temperature) <= id(room_temperature_sensor).state + 1.0) {
                  id(fan_speed) = 0.00;
                  ESP_LOGD("PWM", "Minimum fan speed as exhaust is <= ambient.");
                }
                else {
                  id(fan_speed) = (1.0 - 0.00) * ((id(computer_exhaust_temperature) - id(room_temperature_sensor).state)/(id(max_fan_speed_temperature) - id(room_temperature_sensor).state));
                }
                ESP_LOGD("PWM", "TEMPERATURE CONTROL - Max Speed @ %dC - CURRENT PWM: %d%%" , int(id(max_fan_speed_temperature)), int(id(fan_speed) * 100));
            } else {
                id(fan_speed) = id(fan_speed_override) / 100;
                ESP_LOGD("ALERT", "PWM OVERRIDE: %d%% - CURRENT PWM: %d%%", int(id(fan_speed_override)), int(id(fan_speed) * 100));
            }
        } else {
            id(fan_speed) = 0.0;
            ESP_LOGD("ALERT", "FANS TURNED OFF");
        }
        id(fan_pwm).set_level(id(fan_speed));

  - interval: 10s
    then:
      lambda: |-
        id(computer_exhaust_temperature_ha).publish_state(id(computer_exhaust_temperature_sensor).state);   // Report Computer Exhaust Temperature from Sensor directly
        id(fan_speed_ha).publish_state(id(fan_speed) * 100);

  - interval: 60s
    then:
      lambda: |-
        id(room_temperature_ha).publish_state(id(room_temperature));  // Report Room Temperature Sensor
        id(report_room_humidity).publish_state(id(room_humidity));    // Report Room Humidity Sensor



script:
  - id: setup_controls
    then:
      - lambda: |-
          auto call = id(max_fan_speed_temperature_ha).make_call();
          call.set_value(id(max_fan_speed_temperature));
          call.perform();
      - lambda: |-
          auto call = id(fan_speed_override_ha).make_call();
          call.set_value(id(fan_speed_override));
          call.perform();
      - lambda: |-
          id(override_fan_speed_ha).publish_state(id(override_fan_speed));
          id(computer_exhaust_fan_ha).publish_state(id(computer_exhaust_fan));

  - id: setup_sensors
    then:
      - lambda: |-
          id(room_temperature_ha).publish_state(id(room_temperature_sensor).state);  // Report Room Temperature Sensor
          id(report_room_humidity).publish_state(id(room_humidity_sensor).state);        // Report Room Humidity Sensor
1 Like

Hi, is your fan new EC motor or old Dc?
Thanks

@grssll As far as I know, EC is for AC motors. The “AC Infinity” (brand name) blower runs off 12V DC.

Oh my goodness! Are you a god?

I have the S6 inline fan and the speed controller button thingy. Do you reckon something similar could be achieved using your technique? I really need to control the fan speeds via home assistant, I got like 10 fans and I didn’t really they sort of tried to lock you into their ecosystem. You’re the only person I’ve found in my weeks of research that has achieved something like this! Pls help :sob:

@Sanpyonator - I took a very quick peek at the S6 line and it seems that they use the same control method as the one I have… 12V fan with a PWM signal to control speed. If that is the case, then it should be pretty straightforward to control them with an ESP32. Without going into details, the ESP32 is better for PWM applications than the 8266 and I believe it can also output multiple PWM signals, but I am not sure how many. If you want to control all 10 from a single system, I don’t know of a good way of doing it with a single ESP32.

1 Like

Thanks for taking a look! I’m using home assistant and I’m not planning on controlling everything through 1 esp32 board. I’m thinking that 1 esp32 per fan to control the pwm signal. But ideally I would use an ethernet cable to control the signals sent to the esp32. Do you think that’s doable? If not, I’m perfectly happy with being able to control the esp32 using mqtt.

There are some ESP32 boards that have an ethernet port and you could either get one with onboard POE or use an external POE adapter. You might even be able to power the fan using POE if the current needed is low enough (I wonder about spikes though) which would simplify the setup down to a single cable to control and power the fan. Are the fans controlled manually or based on temperature? Either way is easy to implement in ESPHome.

I’m reviving this is old thread because I got some inspiration to build my own controller running esphome. And I’m thinking this might spark some interest. I’ve completed the first prototype, all cooked in my PCB oven, and I’m getting ready to order the 2nd iteration that includes a break out connector for external sensors like humidity, temperature, mouvement, VOC, gas, etc


@sle118 I like your design but are you sure you want to add all those extra sensors and screen for something that would be likely hidden from view and close to a heat generating computer?

I have ambient and exhaust temperatures simply because setting a fixed temperature did not work well when it gets hot in the room. By having both I know whether there is value to drawing air out from the PC (and above all from under my desk so it doesn’t roast me) because ambient is cooler than the exhaust. Anyhow, the ambient sensor is close to the fan which is on the other side of the desk. This leads to the ambient sensor reading a temperature that is actually several degrees above ambient. It is not too big of a deal for my control loop, but it could be if I were using it as an ambient comfort sensor. I have CO2, PM, C, rH sensors, in a nice enclosure with touch activated display, on the other side of the room away from heat sources or HVAC vents. While I like integrating multiple things into one device, here I see value in keeping them separate.

Either way… I wish I could design my own PCB like you did. It would open a world of new opportunities. I have the sw… maybe I just have to get down to learning it.

Thanks for the reply. I didn’t mean to hijack the thread, by the way. So the sensor connectors are all optional of course. My design had a very specific purpose, as the fan is accessible from the workshop on the other side of a wall. Sensors are there for me to experiment stuff like methane detection (it is a bathroom fan), humidity, etc with the sensors somewhere inline of the airflow.

Also, when designing a board, you always keep all options opened as the same design is reusable for other projects. For instance, I could use one board as a remote connected through Bluetooth.

Thanks for your comments!

@sle118 Makes total sense! Is the odd shape of the PCB custom designed for some electrical box?

To connect sensors I usually use easy to crimp connectors as it takes up less space and makes a solid connection. For example: JST-XH Connector Kit 2.54mm 2/3/4/5/6 Pin

Yup. The odd shape is for a direct fit inside of the original fan box, with a custom cover for screen and rotary.

I actually thought about using JST connectors (I have a selection of crimping tools for various connector formats), but not everyone can deal with these, so a good old screw terminal works ok for most. But I think I’ll probably take your recommendation in and mirror the terminals with some JST and solder whichever one I decided to use. I have plenty of board real estate anyhow.

I’m also leaning towards a bigger screen since I might also want to display sensor data. Remembering that my fan is visible.

Also, the unit could very well be mounted remotely in its own case, so a wide screen would be nice.

1 Like