У ИКЕА достаточно давно существует «сенсор качества воздуха» с труднопроизносимым названием ВИНДРИКТНИНГ. Он построен на основе сенсора твердых частиц 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



С возвращением !
камбек детектед! Джаггер, приятно видеть, не бросайте проект, пишите еще, с удовольствием читаем Вас и паяем Ваше)
Спасибо! Отличная статья и реализация, обязательно повторим!)
Подскажите, а какие нормы на PM1.0?
Можно почитать здесь. В случае с VINDRIKTNING есть подозрение в ошибке в формуле расчета.
Где можно скачать конфиг со шрифтами?
А как там с уровнями сигналов ? ESP32 — 3.3V а этот датчик 5V TTL ?
Не хочется резать оригинальную лицевую часть корпуса. Может быть, у кого-то есть 3D-модель?
глянь здесь, может подойдет? https://www.thingiverse.com/thing:6362270
Спасибо, мил человек! Это то, что надо!
Скажите, а SMD светодиоды надо выпаивать? Не мешают они экрану и не подсвечивают ли через плату?
В статье явно написано «На оригинальной плате все элементы сохраняются без изменений». Ничего не просвечивает.
А вот еще идея возникла: отображать значения PM разным цветом в зависимости от концентрации частиц. Если норма, то зеленым, повышенная концентрация желтым, превышение ПДК — красным.
Только вот моих знаний в ESPHome не хватает, чтобы это реализовать.
Раскрасил https://github.com/ananyevgv/esphome-vindriktning-ikea/
Класс! Теперь только не хватает одной мелочи — чтобы при прикосновении к экрану он включался. А потом выключался по тайм-ауту (скажем, минута или три).
А я например так и сделел — просверлил отверстие и вставил винтик нержавеющий, и далее проводом к ножке есп:
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
Можно использовать сенсорную кнопку на чипе TTP223. Она чувствует даже через небольшую преграду (2-3 мм пластика, что как раз соответствует толщине стенки корпуса нашего девайса). То есть, ее можно приклеить изнутри корпуса — в верхней части, например. И тогда будет реакция на прикосновение пальца к корпусу.
К вышесказанному (у вас могут быть другие пины):
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
Ещё б в продаже найти этот датчик…
Новый вариант оформления https://github.com/ananyevgv/esphome-vindriktning-ikea/blob/main/ikea-circle-new.yaml
Хотел воспользоваться Вашей доработкой, но к сожалению , при валидации проекта прошивки получил вот это :
Could not download font at https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:ital,wght@0,400, please check the fonts exists at google fonts (HTTPSConnectionPool(host=’fonts.googleapis.com’, port=443): Max retries exceeded with url: /css2?family=Material+Symbols+Outlined:ital,wght@0,400 (Caused by SSLError(SSLCertVerificationError(1, ‘[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate is not yet valid (_ssl.c:992)’))))
Как бы это поправить ?
не достучался до гугла
ТОСП 79851 где купить?)
В любой компании рядом которая занимается изготовлением рекламой продукции
где купить акрила ТОСП 79851?