IKEA VINDRIKTNING

У ИКЕА достаточно давно существует «сенсор качества воздуха» с труднопроизносимым названием ВИНДРИКТНИНГ. Он построен на основе сенсора твердых частиц  pm1006, что это за частицы и зачем нужно знать их концентрацию  можно почитать здесь. Изначально устройство не умеет точно показывать измеренные значения, ограничиваясь цветовой индикацией уровня загрязнения воздуха, так же отсутствуют какие-либо интерфейсы для получения точных данных.  Используя готовые и доступные модули можно добавить расширенный функционал, не имея особых навыков в пайке или заказе печатных плат.

Яндекс маркет дает возможность приобрести устройство за очень небольшие деньгиЧто бы сделать устройство способным отображать измеренные значения и передавать их в УД понадобится докупить модуль с чипом ESP32 и LCD дисплей. В моем случае это ESP32 Lolin Lite и LCD  c контроллером st7735 (доступны и на отечественных торговых площадках)

На LCD модуле необходимо удалить слот для карты памяти и контактный разъем.

Электрическая часть собирается на проводах, без использования дискретных элементов. Общая схема соединений выглядит так

Практически получается такая конструкция

На оригинальной плате все элементы сохраняются без изменений. Так как на моем варианте модуля с ESP32 нет выведенного контакта для питания платы от 5 вольт, можно подпаяться к точке на плате, как на картинке выше.

Для монтажа дисплея в корпус на лицевой панели необходимо сделать вырез любым доступным способом, дремель, лазер и т.д.

Лицевая панель вырезана лазером из акрила ТОСП 79851 толщиной 2мм. и закреплена с помощью тонкого двухстороннего скотча 3М. Модуль с ESP32  плотно встает в корпус по диагонали

Для плотного прилегания модифицированной лицевой панели необходимо удалить часть выступов

Сам сенсор pm1006 может отдавать не только значения РМ2.5 используемые в оригинальном устройстве, но и значения РМ1 и РM10. Прошивка собрана в ESPHome, код из примеров и дополнена стараниями @Constantine

esphome:
  name: vindriktning
  friendly_name: VINDRIKTNING

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

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

ota:
  password: ""


wifi:
  ssid: !secret wifi_sside
  password: !secret wifi_password
 
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${devicename} Fallback Hotspot"
    password: !secret ap_password

captive_portal:

########## Setup #######################

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23

uart:
  rx_pin: GPIO16
  baud_rate: 9600

  debug:
    direction: RX
    dummy_receiver: true
    after:
      delimiter: "\r\n"
    sequence:
      - lambda: |-
          UARTDebug::log_string(direction, bytes);  //Still log the data
          uint8_t checksum = 0;
          for (int i=0; i < bytes.size(); i++) {
            checksum+= bytes[i];
          }
          if (checksum==0){
            ESP_LOGD("UART","PM1006|PM1006k checksum validated, have %d", checksum);
            if (bytes.size()==20 and (bytes[0]==22 and bytes[1]==17 and bytes[2]==11)) {
              ESP_LOGD("UART", "Correct PM1006 response recieved. Updating sensors");
              id(pm2).publish_state(bytes[5]*256+bytes[6]); 
              id(pm1).publish_state(bytes[9]*256+bytes[10]);
              id(pm10).publish_state(bytes[13]*256+bytes[14]);
            }
            if (bytes.size()==16 and (bytes[0]==22 and bytes[1]==13 and bytes[2]==2)) {
              ESP_LOGD("UART", "Correct PM1006K response recieved. Updating sensors");
              id(pm2).publish_state(bytes[5]*256+bytes[6]); 
              id(pm1).publish_state(bytes[9]*256+bytes[10]);
              id(pm10).publish_state(bytes[13]*256+bytes[14]);
            }
          }
          else{
            ESP_LOGW("UART","PM1006|PM1006k checksum is wrong: %02x, expected zero. Sensors will not be updated", checksum);
          }
font:
  - file: "Roboto-Thin.ttf"
    id: font0
    size: 15
    glyphs: |-
      !"%()+=,-_.:°0123456789АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧЩЬЫЪЭЮЯABCDEFGHIJKLMNOPQRSTUVWXYZ абвгдеёжзийклмнопрстуфхцчщьыъэюяabcdefghijklmnopqrstuvwxyz'éèàòùç/&ôœìïöñ
    # gfonts://family[@weight]
  - file: "gfonts://Roboto"
    id: font3
    size: 15 
  - file:
      type: gfonts
      family: Roboto
      weight: 700
    id: font2
    size: 30    
 
display:
  - platform: st7735
    model: "INITR_18BLACKTAB"
    reset_pin: GPIO14
    cs_pin: GPIO17
    dc_pin: GPIO2
    rotation: 0
    device_width: 128
    device_height: 160
    col_start: 0
    row_start: 0
    eight_bit_color: true
    update_interval: 5s
    id: my_display
    pages:
      - id: page1
        lambda: |-
          it.strftime(it.get_width() / 2, 8, id(font3), my_white, TextAlign::CENTER, "%H:%M", id(ntp).now());
          it.print(64, 22, id(font0), my_white, TextAlign::CENTER,  "PM1 мкг/m3");
          it.printf(64, 44, id(font2), my_white, TextAlign::CENTER, "%.0f", id(pm1).state);
          it.print(64, 70, id(font0), my_white, TextAlign::CENTER,  "PM2.5 мкг/m3");
          it.printf(64, 92, id(font2), my_yellow, TextAlign::CENTER, "%.0f ", id(pm2).state);
          it.print(64, 123, id(font0), my_white, TextAlign::CENTER,  "PM10 мкг/m3");
          it.printf(64, 145, id(font2), my_white, TextAlign::CENTER, "%.0f", id(pm10).state);
      - id: page2
        lambda: |-
          it.strftime(it.get_width() / 2, 8, id(font3), my_white, TextAlign::CENTER, "%H:%M", id(ntp).now());
          it.print(64, 22, id(font0), my_white, TextAlign::CENTER,  "НА УЛИЦЕ");
          it.printf(64, 44, id(font2), my_white, TextAlign::CENTER, "%.1f", id(temp_outdoor).state);
          it.print(64, 70, id(font0), my_white, TextAlign::CENTER,  "В ДОМЕ");
          it.printf(64, 92, id(font2), my_yellow, TextAlign::CENTER, "%.1f ", id(temp_hall).state);
          it.print(64, 123, id(font0), my_white, TextAlign::CENTER,  "СО2");
          it.printf(64, 145, id(font2), my_white, TextAlign::CENTER, "%.1f", id(co2).state);
interval:
  - interval: 5s
    then:
      - display.page.show_next: my_display
      - component.update: my_display        
   
color:
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%
  - id: my_yellow
    red: 100%
    green: 100%
    blue: 0%
  - id: my_green
    red: 0%
    green: 100%
    blue: 0%
  - id: my_blue
    red: 0%
    green: 0%
    blue: 100%
  - id: my_white
    red: 100%
    green: 100%
    blue: 100%
  - id: my_black
    red: 0%
    green: 0%
    blue: 0%    

sensor:
  - platform: template
    name: "PM 1.0"
    id: "pm1"
    device_class: PM1
    accuracy_decimals: 0
    unit_of_measurement: µg/m³

  - platform: template
    name: "PM 2.5"
    id: "pm2"
    device_class: PM25
    accuracy_decimals: 0
    unit_of_measurement: µg/m³
  - platform: template
    name: "PM 10 "
    id: "pm10"
    device_class: PM10
    accuracy_decimals: 0
    unit_of_measurement: µg/m³
  - platform: homeassistant   # Температура на улице
    id: temp_outdoor
    entity_id: sensor.93_01_temp 
  - platform: homeassistant   # Температура в доме
    id: temp_hall
    entity_id: sensor.0xa4c1387ab81dc9bd_temperature
  - platform: homeassistant   # CO2
    id: co2
    entity_id: sensor.0x00124b001d3b8107_co2  

switch:
  - platform: gpio
    pin: GPIO19
    name: Light
    id: light
       
time:
  - platform: homeassistant
    id: ntp

 

В Home Assistant устройство видится так

Подсветку дисплея можно включать/выключать по событиям, движение в помещении, превышение порога и т.д. Так как дисплей достаточно крупный, то на него можно выводить дополнительную информацию от сенсоров Home Assistant.

Таким образом, буквально за вечер устройство обретает вторую жизнь

Для желающих вырезать подобное файл в формате .cdr

20 комментариев для “IKEA VINDRIKTNING

  1. камбек детектед! Джаггер, приятно видеть, не бросайте проект, пишите еще, с удовольствием читаем Вас и паяем Ваше)

  2. Спасибо! Отличная статья и реализация, обязательно повторим!)

  3. Не хочется резать оригинальную лицевую часть корпуса. Может быть, у кого-то есть 3D-модель?

  4. Скажите, а SMD светодиоды надо выпаивать? Не мешают они экрану и не подсвечивают ли через плату?

    1. В статье явно написано «На оригинальной плате все элементы сохраняются без изменений». Ничего не просвечивает.

  5. А вот еще идея возникла: отображать значения PM разным цветом в зависимости от концентрации частиц. Если норма, то зеленым, повышенная концентрация желтым, превышение ПДК — красным.
    Только вот моих знаний в ESPHome не хватает, чтобы это реализовать.

      1. Класс! Теперь только не хватает одной мелочи — чтобы при прикосновении к экрану он включался. А потом выключался по тайм-ауту (скажем, минута или три).

        1. А я например так и сделел — просверлил отверстие и вставил винтик нержавеющий, и далее проводом к ножке есп:

          esp32_touch:
          setup_mode: False

          binary_sensor:
          — platform: esp32_touch
          name: «Touch Pad»
          id: «touch_pad»
          pin: GPIO12
          threshold: 20000
          on_press:
          then:
          — display.page.show_next: my_display
          — component.update: my_display

          1. Можно использовать сенсорную кнопку на чипе TTP223. Она чувствует даже через небольшую преграду (2-3 мм пластика, что как раз соответствует толщине стенки корпуса нашего девайса). То есть, ее можно приклеить изнутри корпуса — в верхней части, например. И тогда будет реакция на прикосновение пальца к корпусу.

          2. К вышесказанному (у вас могут быть другие пины):

            binary_sensor:
            — platform: gpio
            pin: 4
            name: «Touch Button»
            id: touch_button
            device_class: light
            on_press:
            then:
            — light.turn_on: back_light
            output:
            — platform: ledc
            pin: 19
            id: gpio_19_backlight_pwm
            light:
            — platform: monochromatic
            output: gpio_19_backlight_pwm
            name: «Display Backlight»
            id: back_light

Добавить комментарий