IKEA FÖRNUFTIG in Home-Assistant

I don’t know the answer but i can confirm that all fan speed works for me (however I can’t turn off the fan by the knob)

What do you see in the ESPHome log?

I’ve also replaced the whole board with a wemos D1 mini with the wiring diagram Nierit posted above, plus I have also added a standard rotary encoder in place of the 4 pos switch, which fits nicely on top. It turns the speed up and down and acts as a on/off toggle button.

Also I managed to read the speed of the fan, although I have no clue about how many steps the encoder does per revolution, the reading of the fan-tacho seems a tad high.
I have measured the speed of the fan using my phone in slowmo mode and have adjusted the multiplier in the pulse counter so it would read correct rounds per minute of the fan.

Additionally there was a problem with the configuration where the PWM wouldn’t switch off, I’ve managed to fix this by adding zero_means_zero = true to the config of the pwm output pin.

I’ve spent a few hours with the yaml today so I figured it’s best to share :hugs:

Here’s a wiring diagram made by @Montaneste which he graciously allowed me to share:

and here’s my yaml config:

esphome:
  name: "fornuftig"

esp8266:
  board: d1_mini

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Fornuftig-AP"
    password: ""

captive_portal:

output:
  - platform: esp8266_pwm
    pin: D1
    frequency: 150 Hz
    id: pwm_output
    min_power: 0.5
    max_power: 0.5
    zero_means_zero: true

fan:
  - platform: speed
    output: pwm_output
    name: "Fornuftig Air Purifier"
    id: "fornuftig_fan"
    restore_mode: RESTORE_DEFAULT_OFF
    on_speed_set:
      lambda: |-
        if (id(fornuftig_fan).speed != id(fan_speed) && id(fornuftig_fan).state) {
          id(fan_speed) = id(fornuftig_fan).speed;
          id(set_fan_freq).publish_state(id(fornuftig_fan).speed);
        }
    on_turn_on:
      lambda: |-
        id(set_fan_freq).publish_state(id(fornuftig_fan).speed);
      
    on_turn_off:
      - output.esp8266_pwm.set_frequency:
          id: pwm_output
          frequency: !lambda 'return int(0);'
      - logger.log: "Fan Turned Off!"

sensor:
  - platform: rotary_encoder
    name: "Rotary Encoder"
    pin_a: 
      number: D5
      inverted: false
      mode:
        input: true
        pullup: true
    pin_b: 
      number: D6
      inverted: false
      mode:
        input: true
        pullup: true
    on_clockwise:
      - logger.log: "Turned Clockwise"
      - lambda: |-
          if(!id(fornuftig_fan).state && id(fan_speed) == 20) {
            auto call = id(fornuftig_fan).turn_on();
            call.perform();
          } else if(id(fan_speed) < 100) {
            auto call = id(fornuftig_fan).turn_on();
            call.set_speed(id(fan_speed)+2);
            call.perform();  
          }
          
    on_anticlockwise:
      - logger.log: "Turned Anticlockwise"
      - lambda: |-
          if (id(fan_speed) > 20) {
            auto call = id(fornuftig_fan).turn_on();
            call.set_speed(id(fan_speed)-2);
            call.perform();  
          } else if (id(fan_speed) <= 20) {
            auto call = id(fornuftig_fan).turn_off();
            call.perform();
          }
        
  - platform: pulse_counter
    pin: 
      number: D2
      mode: INPUT_PULLUP
    unit_of_measurement: 'RPM'
    id: fan_tach
    name: Fornuftig Fanspeed
    filters:
      - multiply: 0.06
      
  - platform: template
    id: set_fan_freq
    filters:
      - multiply: 3
      - lambda: |-
          if (id(fornuftig_fan).state){
            if(x > 300) { // limit max frequency
              return 300;
              
            } else if(x < 60 && x > 5) { // limit low frequency
              return 60;
              
            } else if(x <= 5) {
              return 0;
              
            } else {
              return x;
              
            }
          } else {
            return 0;
          }
    on_value:
      then:
        - output.esp8266_pwm.set_frequency:
            id: pwm_output
            frequency: !lambda 'return int(x);'
        - logger.log: "on_value called"
        - lambda: |- 
            // send current status to homeassistant
            if(x) {
              auto call = id(fornuftig_fan).turn_on();
              call.set_speed(int(x)/3);
              call.perform();
            } else {
              auto call = id(fornuftig_fan).turn_off();
              call.perform();
            }

binary_sensor:
  - platform: gpio
    pin:
      number: D7
      mode:
        input: true
        pullup: true
    name: "Toggle Fan"
    on_press:
      then:
        - fan.toggle: "fornuftig_fan"
        - logger.log: "Clicked"
        
globals:
  - id: fan_speed
    type: int
    restore_value: yes
    initial_value: '0'
4 Likes

Hi, i’ve done all steps but i´m only getting 1 speed (slowest) and the rotary switch doesn’t work too… :sa:

Any help?

Thks

Hi,

For the fellas using the PWM version: (actually it’s not PWM, since the fan speed reacts on frequency of the input signal).

Both scripts above seems a bit strange. First one doesn’t switch the fan completely off, the second script does switch the fan off, but somehow the knob turns doesn’t really work well here.
Also the RPM sensor seems a bit weird. I used another factor for this, though based on guessing :wink:
If there’s someone around with a tachometer, please :wink:

I combined both scripts, everything seems to work well for me now. I changed the initial power on RPM to use the value it was set on in HA (instead of speed_1, 33%).
Also changed the minimum freqnuency to 36 Hz, at this point my fan starts turning. Might be safer to put it a bit higher if needed.
Always room for enhancements, credits to the other posters!

Regards,
Patrick

captive_portal:

output:
  - platform: esp8266_pwm
    pin: D1
    frequency: 150 Hz
    id: pwm_output
    min_power: 0.5
    max_power: 0.5
    zero_means_zero: true

fan:
  - platform: speed
    output: pwm_output
    name: "Fornuftig Air Purifier"
    id: "fornuftig_fan"
    restore_mode: RESTORE_DEFAULT_OFF
    on_speed_set:
      lambda: |-
        if(id(fornuftig_fan).speed != id(fan_speed) && id(fornuftig_fan).state){
          id(fan_speed) = id(fornuftig_fan).speed;
          id(set_fan_freq).publish_state(id(fornuftig_fan).speed);
        }
    on_turn_on:
      lambda: |-
       id(set_fan_freq).publish_state(id(fornuftig_fan).speed);
    on_turn_off:
      - output.esp8266_pwm.set_frequency:
          id: pwm_output
          frequency: !lambda 'return int(0);'
      - logger.log: "Fan Turned Off!"

sensor:
  - platform: pulse_counter
    pin: 
      number: D2
      mode: INPUT_PULLUP
    unit_of_measurement: 'RPM'
    accuracy_decimals: 0
    id: fan_tach
    name: Fornuftig Fanspeed
    filters:
      - multiply: 0.1

  - platform: template
    id: set_fan_freq
    filters:
      - multiply: 3
      - lambda: |-
          if (id(fornuftig_fan).state){
            if(x > 300) { // limit max frequency
              return 300;
              
            } else if(x < 36 && x > 3) { // limit low frequency
              return 36;
              
            } else if(x <= 3) {
              return 0;
              
            } else {
              return x;
              
            }
          } else {
            return 0;
          }
    on_value:
      then:
        - output.esp8266_pwm.set_frequency:
            id: pwm_output
            frequency: !lambda 'return int(x);'
        - logger.log: "on_value called"
        - lambda: |- 
            // send current status to homeassistant
            if(x) {
              auto call = id(fornuftig_fan).turn_on();
              call.set_speed(int(x)/3);
              call.perform();
            } else {
              auto call = id(fornuftig_fan).turn_off();
              call.perform();
            }

binary_sensor:
  - platform: template
    id: 'knob_0'
    filters:
      - delayed_on_off: 300ms
    lambda: |-
      if (!id(knob_1).state && !id(knob_2).state && !id(knob_3).state) {
        return true;
      } else {
        return false;
      }
    on_press: 
      lambda: |-
        if(!id(speed_0)){
          auto call = id(fornuftig_fan).turn_off();
          call.perform();
        }else{
          auto call = id(fornuftig_fan).turn_on();
          call.set_speed(id(speed_0));
          call.perform();
        }

  - platform: gpio
    id: 'knob_1'
    pin:
      number: D7
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on_off: 100ms
    on_press: 
      then:
        - fan.turn_on:
            id: fornuftig_fan
            speed: !lambda 'return id(speed_1);'
    
    
  - platform: gpio
    id: 'knob_2'
    pin:
      number: D6
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on_off: 100ms
    on_press: 
      then:
        - fan.turn_on:
            id: fornuftig_fan
            speed: !lambda 'return id(speed_2);'
  
  
  - platform: gpio
    id: 'knob_3'
    pin:
      number: D5
      mode: INPUT_PULLUP
      inverted: true
    filters:
      - delayed_on_off: 100ms
    on_press:
      then:
        - fan.turn_on:
            id: fornuftig_fan
            speed: !lambda 'return id(speed_3);'

globals:
  - id: fan_speed
    type: int
    restore_value: yes
    initial_value: '0'
    
  - id: speed_0
    type: int
    restore_value: yes
    initial_value: '0'
    
  - id: speed_1
    type: int
    restore_value: yes
    initial_value: '33'
    
  - id: speed_2
    type: int
    restore_value: yes
    initial_value: '66'
    
  - id: speed_3
    type: int
    restore_value: yes
    initial_value: '100'
1 Like

What kind of rotary encoder did you use?

this type: Rotary Encoder Sensor — ESPHome

Yes I’ve mentioned that the tach-speed is a bit off :wink:
I have also mentioned that I changed the 4 pos-switch which was wrongly named an encoder to a true rotatry encoder with totally different wiring. I removed the original ikea-pcb completely as it’s not necessary in any way with my implementation.

oh and I have also added some decoupling capacitors to the the encoder pins on my wemos D1 to make sure there’s no false inputs. The internal pull-ups are a bit too weak without decoupling, and i’d also recommend use external pull-ups for the encoder, although I got away without them and just use decoupling caps.

Hi,

I understand now :wink: That’s the reason of the different code in the yaml file for the “knob”.
Got it working without the capacitors or pullups now, with the original knob.
Only thing left is the RPM, not really important (totally not) but would like to see a “correct-ish” value :slight_smile:

Keep up the good work!
Patrick

regarding rpm, yeah me too :slight_smile:

by the way, I’ve scoped the original pcb and found that at minimum rpm setting there is a brief turn on period that outputs 200hz which then lowers down to 100hz, and if there’s no rpm sensed on the tach-pin it turns off completely.

Ok so I went ahead, set the fanspeed to 20% (60hz) and filmed the fan in slowmotion, it turned out one revolution took 0.25s resulting 240rpm. The raw-reading of the pulse counter returns 4000, so the resulting scaling factor should be 0.06.

Please fee free to repeat my experiment and report back in case you find an error :slight_smile:

Heheh that’s a way of doing it :slight_smile:

Dear all! As I mentioned in my previous post, I was thinking about to test a custom PCB for this project mainly to avoid messy wires under the housing.

My PCBs have finally arrived and they are working great so far! The main idea was to make a longer board (ca. 130mm) and place the Wemos D1 mini at the end. I also added a 1A fuse for safety purposes as the original board had one. You can see I installed the D1 mini upside-down as I didn’t want to push the metal shield too close to the top of the case. I think the board fits just well inside. It cost me around 8$ for 5 pcs (including shipping, but without components like esp8266, voltage divider, rotary encoder, fuse, soldering iron, pins etc) that seems reasonable as I have multiple air purifiers at home.

Any feedback is appreciated. Later I’m going to upload more pictures. If anyone interested I can share the PCB design with the community (Gerber file or I think there is an option to share the whole project with EasyEda. First, I need to figure out how to do it)

Link to open source project:
Simple Smart Control Panel for IKEA Förnuftig Air Purifiers - EasyEDA open source hardware lab (oshwlab.com)

5 Likes

This is great. Any recommendation on where to order the PCB from and you ordered the parts you have used?
cheers

btw. i can see there is an order option from the EasyEDA editor; however, that seems not to work…?

just add this sensor to esphome config :slight_smile:

  - platform: total_daily_energy
    name: "Total Daily Energy"
    power_id: power_usage

it needs the time component eg.

time:
  - platform: homeassistant
    id: homeassistant_time

Hi! just fried my pcb… Is your mod possible without decoupling caps or anything extra? Just your wemos and converter connected straight to förnuftig? And maby add the rotary encoder after. Just to see if it works?

/Carl

Hi. are you using decoupling caps? Or is your PCB only connecting the wemos, rotary encoder, and converter to the förnuftig without the decoupling caps?

/Carl

When I designed the PCB the cheapest manufacturer was JLCPCB so I ordered from them. I’m quite satisfied with the result, the PCB material looks of a high quality. You don’t need EasyEDA, just upload the so called ‘Gerber’ file to any manufacturers website you like

Unfortunately you can’t order it assembled as i didn’t add all of the components to the PCB. So you can order only the plain PCB and you should buy and solder the items on it. (Please note that it has a pretty large ground plane thus soldering GNDs need extra patience :slight_smile: )

No, i didn’t use decoupling caps

nice work!
Could you provide a list of items needed (e.g. the fuse, d1mini, etc)?
Thanks :slight_smile: