- PVSM.RU - https://www.pvsm.ru -

Управляем дюймовыми жалюзи дешево

Еще летом, когда светит яркое солнце мне надоело крутить ручку закрытия/открытия жалюзи в офисе как на этой гифке и пришла идея их автоматизировать.

Управляем дюймовыми жалюзи дешево - 1

До покупки жалюзи из Леруа Мерлен [1] у меня была идея поставить шторы с электроприводом, но цена на них несколько лет назад, когда делал выбор, была довольно кусачая. К тому же, из-за высоты и ширины окна размер штор получался нестандартный, что тоже увеличило стоимость.

Результат работы моего проекта по автоматизации жалюзи из Leroy Merlin

После этого прошло пару лет и после того, как количество “умных” вещей в офисе на базе Home Assistant [2] начало разрастаться я вернулся к идее автоматизации жалюзи.

Мониторинг вариантов особенно ничего не дал. Все проекты автоматизации жалюзи которые я видел были для двухдюймовых жалюзи, в то время как практически все жалюзи которые продаются в РФ шириной в один дюйм.

Управляем дюймовыми жалюзи дешево - 2
Автоматизированные жалюзи на окне офиса

1. Выбор двигателя и управляющего микроконтроллера

Сначала непонятно было с чего вообще начинать. Для автоматизации, в других проектах часто использовали шаговый двигатель 28BYJ-48 [3] за примерно 130 руб за штуку (в Китае). С управляющим контроллером у меня вопрос не стоял, поскольку практически везде использую LOLIN (WEMOS) D1 mini [4].

Управляем дюймовыми жалюзи дешево - 3
Переделанные и стандартные жалюзи: вид сверху

2. Прошивка для микроконтроллера ESP8266 китайского производителя Espressif Systems

На следующем шаге — прошивке мне не хотелось заморачиваться со сложным кодингом, а привычная Tasmota [5] не выдавала готовых вариантов. Тогда я познакомился с ESPHome [6] — прошивкой которая нативно и без MQTT поддерживается Home Assistant.

Управляем дюймовыми жалюзи дешево - 4
Переделанные и стандартные жалюзи: вид сбоку

Приятным бонусом ESPHome было, что она имеет компонент работы с шаговыми двигателями [7], который в свою очередь поддерживает работу с микросхемой ULN2003, которая может быть применена для управления нагрузкой значительной мощности, включая электромагнитные реле, двигатели постоянного тока, электромагнитные клапаны, в схемах управления различными шаговыми двигателями.

Управляем дюймовыми жалюзи дешево - 5
Переделанные жалюзи: вид сбоку

Поскольку я использую Hass.io [8], то для компиляции прошивок использовал самый простой для этого вариант — ESPHome Hass.io Add-On [9].
На окне три жалюзи и получилось три микроконтроллера. Вот получившиеся прошивки:
После тестов обнаружил что, для корректного открытия/закрытия жалюзи необходимо задавать разное число шагов для каждой жалюзи.

Управляем дюймовыми жалюзи дешево - 6

window_1.yaml

substitutions:
  devicename: window_1
  upper_devicename: Window 1

esphome:
  name: $devicename
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: "xxx"
  password: "xxx"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Window 1 Fallback Hotspot"
    password: "xxx"

captive_portal:

# web_server:
#   port: 80
#   css_url: http://192.168.15.10:8123/local/webserver-v1.min.css
#   js_url: http://192.168.15.10:8123/local/webserver-v1.min.js

# Enable Home Assistant API
api:
  services:
    - service: control_stepper
      variables:
        target: int
      then:
        - stepper.set_target:
            id: my_stepper
            target: !lambda 'return target;'

# Enable OTA Access
ota:

# Enable verbose logging over serial
logger:

# physical connection
stepper:
  - platform: uln2003
    id: my_stepper
    pin_a: D0
    pin_b: D5
    pin_c: D6
    pin_d: D7
    max_speed: 250 steps/s
    sleep_when_done: true    
    acceleration: inf
    deceleration: inf

i2c:

cover:
  - platform: template
    name: "Zhaliuzi 1"
    id: window1
    device_class: blind

    open_action:
      - stepper.report_position:
          id: my_stepper
          position: 0
      - stepper.set_target:
          id: my_stepper
          target: -1550

    close_action:
      - stepper.report_position:
          id: my_stepper
          position: 0
      - stepper.set_target:
          id: my_stepper
          target: 1550

    stop_action:
      - stepper.set_target:
          id: my_stepper
          target: !lambda return id(my_stepper).current_position;      
    optimistic: true  
    assumed_state: true

sensor:
  - platform: bh1750
    name: "Osveshchennost u okna"
    address: 0x23
    update_interval: 40s

# General device data
  - platform: uptime
    id: uptime_sec
  - platform: wifi_signal
    name: ${upper_devicename} WiFi Signal
    id: wifis_signal
    update_interval: 900s

text_sensor:
  - platform: template
    name: ${upper_devicename} Uptime
    lambda: |-
      int seconds = (id(uptime_sec).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 { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
    icon: mdi:clock-start
    update_interval: 113s
  - platform: template
    name: ${upper_devicename} Wifi Strength
    icon: "mdi:wifi"
    lambda: |-
      if (id(wifis_signal).state > -50 ) {
        return {"Excellent"};
      } else if (id(wifis_signal).state > -60) {
        return {"Good"};
      } else if (id(wifis_signal).state > -70) {
        return {"Fair"};
      } else if (id(wifis_signal).state < -70) {
        return {"Weak"};
      } else {
        return {"None"};
      }
    update_interval: 900s
  - platform: version
    name: ${upper_devicename} Version
  - platform: template
    name: ${upper_devicename} MAC Address
    lambda: 'return {WiFi.macAddress().c_str()};'
    icon: mdi:fingerprint
    update_interval: 1d

switch:
  - platform: restart
    name: ${upper_devicename} Restart

window_2.yaml

substitutions:
  devicename: window_2
  upper_devicename: Window 2

esphome:
  name: $devicename
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: "xxx"
  password: "xxx"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Window 2 Fallback Hotspot"
    password: "xxx"

captive_portal:

# web_server:
#   port: 80
#   css_url: http://192.168.15.10:8123/local/webserver-v1.min.css
#   js_url: http://192.168.15.10:8123/local/webserver-v1.min.js

# Enable Home Assistant API
api:
  services:
    - service: control_stepper
      variables:
        target: int
      then:
        - stepper.set_target:
            id: my_stepper
            target: !lambda 'return target;'

# Enable OTA Access
ota:

# Enable verbose logging over serial
logger:

# physical connection
stepper:
  - platform: uln2003
    id: my_stepper
    pin_a: D0
    pin_b: D5
    pin_c: D6
    pin_d: D7
    max_speed: 250 steps/s
    sleep_when_done: true    
    acceleration: inf
    deceleration: inf

cover:
  - platform: template
    name: "Zhaliuzi 2"
    id: window2
    device_class: blind

    open_action:
      - stepper.report_position:
          id: my_stepper
          position: 0
      - stepper.set_target:
          id: my_stepper
          target: -1800

    close_action:
      - stepper.report_position:
          id: my_stepper
          position: 0
      - stepper.set_target:
          id: my_stepper
          target: 1800

    stop_action:
      - stepper.set_target:
          id: my_stepper
          target: !lambda return id(my_stepper).current_position;      
    optimistic: true  
    assumed_state: true

# General device data
sensor:
  - platform: uptime
    id: uptime_sec
  - platform: wifi_signal
    name: ${upper_devicename} WiFi Signal
    id: wifis_signal
    update_interval: 900s

text_sensor:
  - platform: template
    name: ${upper_devicename} Uptime
    lambda: |-
      int seconds = (id(uptime_sec).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 { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
    icon: mdi:clock-start
    update_interval: 113s
  - platform: template
    name: ${upper_devicename} Wifi Strength
    icon: "mdi:wifi"
    lambda: |-
      if (id(wifis_signal).state > -50 ) {
        return {"Excellent"};
      } else if (id(wifis_signal).state > -60) {
        return {"Good"};
      } else if (id(wifis_signal).state > -70) {
        return {"Fair"};
      } else if (id(wifis_signal).state < -70) {
        return {"Weak"};
      } else {
        return {"None"};
      }
    update_interval: 900s
  - platform: version
    name: ${upper_devicename} Version
  - platform: template
    name: ${upper_devicename} MAC Address
    lambda: 'return {WiFi.macAddress().c_str()};'
    icon: mdi:fingerprint
    update_interval: 1d

switch:
  - platform: restart
    name: ${upper_devicename} Restart

window_3.yaml

substitutions:
  devicename: window_3
  upper_devicename: Window 3

esphome:
  name: $devicename
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: "xxx"
  password: "xxx"

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Window 3 Fallback Hotspot"
    password: "xxx"

captive_portal:

# web_server:
#   port: 80
#   css_url: http://192.168.15.10:8123/local/webserver-v1.min.css
#   js_url: http://192.168.15.10:8123/local/webserver-v1.min.js

# Enable Home Assistant API
api:
  services:
    - service: control_stepper
      variables:
        target: int
      then:
        - stepper.set_target:
            id: my_stepper
            target: !lambda 'return target;'

# Enable OTA Access
ota:

# Enable verbose logging over serial
logger:

# physical connection
stepper:
  - platform: uln2003
    id: my_stepper
    pin_a: D0
    pin_b: D5
    pin_c: D6
    pin_d: D7
    max_speed: 250 steps/s
    sleep_when_done: true    
    acceleration: inf
    deceleration: inf

cover:
  - platform: template
    name: "Zhaliuzi 3"
    id: window3
    device_class: blind

    open_action:
      - stepper.report_position:
          id: my_stepper
          position: 0
      - stepper.set_target:
          id: my_stepper
          target: -1600

    close_action:
      - stepper.report_position:
          id: my_stepper
          position: 0
      - stepper.set_target:
          id: my_stepper
          target: 1600

    stop_action:
      - stepper.set_target:
          id: my_stepper
          target: !lambda return id(my_stepper).current_position;      
    optimistic: true  
    assumed_state: true

# General device data
sensor:
  - platform: uptime
    id: uptime_sec
  - platform: wifi_signal
    name: ${upper_devicename} WiFi Signal
    id: wifis_signal
    update_interval: 900s

text_sensor:
  - platform: template
    name: ${upper_devicename} Uptime
    lambda: |-
      int seconds = (id(uptime_sec).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 { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
    icon: mdi:clock-start
    update_interval: 113s
  - platform: template
    name: ${upper_devicename} Wifi Strength
    icon: "mdi:wifi"
    lambda: |-
      if (id(wifis_signal).state > -50 ) {
        return {"Excellent"};
      } else if (id(wifis_signal).state > -60) {
        return {"Good"};
      } else if (id(wifis_signal).state > -70) {
        return {"Fair"};
      } else if (id(wifis_signal).state < -70) {
        return {"Weak"};
      } else {
        return {"None"};
      }
    update_interval: 900s
  - platform: version
    name: ${upper_devicename} Version
  - platform: template
    name: ${upper_devicename} MAC Address
    lambda: 'return {WiFi.macAddress().c_str()};'
    icon: mdi:fingerprint
    update_interval: 1d

switch:
  - platform: restart
    name: ${upper_devicename} Restart

3. Установка привода в жалюзи

Шаговый двигатель не помещался полностью в жалюзи, но поскольку в моем случае они не были прижаты к стене, то была возможность вынести часть двигателя в прорезь с задней стороны жалюзи.

Управляем дюймовыми жалюзи дешево - 7
Установленные на окне жалюзи из Леруа Мерлен с электроприводом

4. Установка конструкции на окне

Поскольку жалюзи уже были установлены строителями мне оставалось только установить коробку с микроконтроллерами и блоком питания [10] рядом с окном. Блок питания не самый мощный, поскольку шаговые двигатели включаются последовательно — сначало одна, потом другая, потом третья. Общее время работы около 20 секунд.

Управляем дюймовыми жалюзи дешево - 8
Коробка с тремя ESP8266 и блоком питания на стене офиса

5. Правила для автоматизации закрытия жалюзи из Home Assistant

При превышении определенного порога жалюзи поворачиваются на 90 градусов и потом соответственно обратно .

automations.yaml

- alias: Covers OPEN
  trigger:
    - platform: numeric_state
      entity_id: sensor.osveshchennost_u_okna
      above: 0
      below: 2500
  condition:
    - condition: state
      entity_id: cover.zhaliuzi_3
      state: 'closed'
    - condition: state
      entity_id: cover.zhaliuzi_2
      state: 'closed'
    - condition: state
      entity_id: cover.zhaliuzi_1
      state: 'closed'
  action:
    - service: cover.open_cover
      data:
        entity_id: cover.zhaliuzi_3
    - delay: '00:00:06'
    - service: cover.open_cover
      data:
        entity_id: cover.zhaliuzi_2
    - delay: '00:00:06'
    - service: cover.open_cover
      data:
        entity_id: cover.zhaliuzi_1

- alias: Covers CLOSE
  trigger:
    - platform: numeric_state
      entity_id: sensor.osveshchennost_u_okna
      above: 2501
      below: 50000
  condition:
    - condition: state
      entity_id: cover.zhaliuzi_3
      state: 'open'
    - condition: state
      entity_id: cover.zhaliuzi_2
      state: 'open'
    - condition: state
      entity_id: cover.zhaliuzi_1
      state: 'open'
  action:
    - service: cover.close_cover
      data:
        entity_id: cover.zhaliuzi_3
    - delay: '00:00:06'
    - service: cover.close_cover
      data:
        entity_id: cover.zhaliuzi_2
    - delay: '00:00:06'
    - service: cover.close_cover
      data:
        entity_id: cover.zhaliuzi_1

Итог

Перед вами проект автоматизации жалюзи, который требует только временных затрат, но сами компоненты недороги. У проекта есть определенные достоинства. Самое главное достоинство: дешевизна.
Но есть и недостатки — ESP8266 никогда не знает текущего положения жалюзи. Иногда, когда например вал прокручивается, приходится вручную подгонять под начальное положение нажатием кнопки в интерфейсе Home Assistant.

P.S. Уже после окончания работы мне подсказали, что есть специальные соединительные втулки, которые позволят жестко соединить вал двигателя и вал жалюзи. Это позволит избежать прокручивания, которое может возникнуть в моем текущем случае из-за недостаточного закрепления соединительной трубки.

Дополнительные подробности можно найти на GitHub [11].

Автор: Михаил Шардин [12],
17 декабря 2019 г.

Автор: Михаил

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/diy/340295

Ссылки в тексте:

[1] жалюзи из Леруа Мерлен: https://perm.leroymerlin.ru/product/zhalyuzi-inspire-100h160-sm-alyuminiy-cvet-belyy-16262144/

[2] Home Assistant: https://www.home-assistant.io/

[3] шаговый двигатель 28BYJ-48: https://www.aliexpress.com/item/32896006818.html

[4] LOLIN (WEMOS) D1 mini: https://www.aliexpress.com/item/32529101036.html

[5] Tasmota: https://github.com/arendst/Tasmota

[6] ESPHome: https://esphome.io/index.html

[7] компонент работы с шаговыми двигателями: https://esphome.io/components/stepper/index.html

[8] Hass.io: https://www.home-assistant.io/hassio/

[9] ESPHome Hass.io Add-On: https://github.com/esphome/hassio

[10] блоком питания: https://www.aliexpress.com/item/32898716031.html

[11] GitHub: https://github.com/empenoso/diy-cheap-automated-blinds

[12] Михаил Шардин: https://www.facebook.com/mikhail.shardin

[13] Источник: https://habr.com/ru/post/480690/?utm_source=habrahabr&utm_medium=rss&utm_campaign=480690