9 мин на чтение

Так уж вышло, что в один солнечный день я осознал, что больше жить не могу без нотификаций от стиральной машины. Все дело в том, что в текущей квартире она стоит на балконе, и о ней как-то очень уж просто забыть или не услышать вовсе. Как итог, забытые вещи и горе в семье.

User story

Слышал, что без user story обречен почти любой проект, поэтому я не стал рисковать и представил себе идеальный flow:

Ага, ок. Звучит так, что нам нужно:

  • узнать, когда стиральная машина закончила работку
  • отправить нотификацию в приложеньку/часики/etc

В идеале еще узнавать бы, забрал ли кто вещи, т.к. пользователей у неё целых два. Но об этом позже. Сейчас бы узнать когда работа машины завершена. Для себя я это назвал как Washer Running State, или WRS.

WRS

Немного гуглежа, и список вариантов решений (скорее всего неполный) был у меня на столе. И так, можно было сделать:

  1. по-богатому: купить стиральную машину с wifi, meh :)
  2. по-рукастому: модифицировать стиральную машину
  3. по-модному: прикрутить sound recognition или computer vision
  4. по-классике: замерить потребление энергии
  5. по-всратому: прикрутить датчик вибрации

И мысли по этому поводу в тот момент времени:

  • по-богатому как-то слишком уж бессмысленно и беспощадно для съемной квартиры
  • рукастый варик отъехал по этой же причине - техника не моя + я довольно рукожопый
  • sound recognition хоть и звучит неплохо (кек ^^), но профит выглядит мелковатым для потраченных усилий
  • для computer vision не придумал, куда бы на балконе прикрутить камеру (например, ченить в духе ESP32 Cam). Видимо в другой раз ;)

Остались два варика, которые я и решил реализовать. Приступим!

WRS: vibration sensor

Начать я, конечно же, решил с всратого. Ничего не могу с собой поделать, всратости попадают мне в самое сердечко :)

В трех словах весь замысел заключается в:

  • подключаем датчик вибрации к MCU
  • когда датчик вибрации сработает, он отправит сигнал
  • если движение было, а потом пропало - стиральная машина (скорее, сушилка в ней) закончила свою работу

Для этого достал из шкафа ESP32-DevKitC, который часто использую для тестов, и заказал с алика vibration sensor SW-18010P за $1.6 (можно было бы и SW-420, который раза в три дешевле, но я решил как решил).

Подключил сию нехитрую конструкцию:

| ESP32 | SW-18010P |
|-------|-----------|
|  3.3V |     +     |
|  GND  |     -     |
|  D34  |    Dout   |

Написал&&Прошил минимальный конфиг для ESPHome (сорец):

esphome:
  name: dev01
  friendly_name: Dev01

esp32:
  board: esp32dev
  framework:
    type: arduino

api:
  encryption:
    key: "DEADBEEF"

ota:
  password: "deadbeef"

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

  manual_ip:
    static_ip: 10.11.9.1
    gateway: 10.11.0.1
    subnet: 255.255.0.0
    dns1: 10.11.1.2

# Больше логов богу логов
logger:

# Не люблю много думать о времени, потому всегда использую Home Assistant Time Source
# doc: https://esphome.io/components/time/homeassistant.html
time:
  - platform: homeassistant
    id: homeassistant_time

sensor:
  # WiFi Signal Sensor, дабы лучше понимать что с WiFi
  # doc: https://esphome.io/components/sensor/wifi_signal.html
  - platform: wifi_signal
    name: "WiFi Signal Strength"
    update_interval: 60s

   # Pulse Counter Sensor с медианой, т.к. у меня стиральная машина стоит на балконе
   # А там бывает и кот и птицы и люди, хотелось не реагировать на случайные срабатывания
   # doc: https://esphome.io/components/sensor/pulse_counter.html
   # doc: https://esphome.io/components/sensor/#median
  - platform: pulse_counter
    name: "Washer vibration pulse rate"
    internal: false
    id: vibration_pulse_rate
    icon: mdi:vibrate
    update_interval: 250ms
    pin:
      number: GPIO34
      mode: input
    filters:
    - median:
        window_size: 120 # скользяще окно около 30 секунд
        send_every: 4 # отправляем раз в секунду
        send_first_at: 3

binary_sensor:
  # Status Binary Sensor, дабы отрисовывать аптайм
  # doc: https://esphome.io/components/binary_sensor/status.html
  - platform: status
    name: Status

  # Template Binary Sensor, который будет зажигать лампочку на основе ответа лямбды
  # Так же устанавливаем ему "delayed_on_off" для минимизации фолзов
  # doc: https://esphome.io/components/binary_sensor/template.html
  # doc: https://esphome.io/components/binary_sensor/index.html#delayed-on-off
  - platform: template
    name: "Washer Running"
    device_class: vibration
    filters:
      - delayed_on_off:
          time_on: 20ms
          time_off: 5min # сглаживаем отпутствие вибрации
    lambda: |-
      return id(vibration_pulse_rate).state >= 240;

# Restart Switch, чтобы мочь рестартить MCU из HA
# doc: https://esphome.io/components/switch/restart.html
switch:
  - platform: restart
    name: Restart

Добавил его в Home Assistant:

На весу проверил работоспособность и осуществил сопле- монтаж в тайском стиле:

Приемлемо? Приемлемо!

WRS: power consumption

Но, не смотря на то, что концепция с датчиком вибрации мне довольно сильно приглянулась, я ожидал наличие проблем. Из очевидных:

  • должно быть сложно избавиться от всех false negative/positive, особенно с тусичами котейки на балконе
  • вибрация зависит от слишком большого числа параметров - от программы стирки до, что хуже, загруженности машины

Поэтому вместе с датчиком вибрации я решил прикупить и какую-нить умную розетку с мониторингом потребления энергии, чисто чтоб запустить их вместе и не наблюдать за стиркой x2 времени. Но тут меня ждал сюрприз: Да-да, это Type-O, который никому за пределами Таиланда не интересен. Если вы увидите шутника про появление 15го стандарта у программистов - выдайте ему затрещину и напомните про электриков. Про электриков, которые не смогли договориться о количестве штырьков, их размере и расположении. В Таиланде зачастую можно встретить type O / C / A/ B (иногда еще I кстати), и type-O худшая из карт для поиска умной розетки :( Но деваться некуда, отыскал приемлемую с виду розетку SMATRUL 20A/16A Tuya/Smart life WiFi Socket, которая обошлась мне примерно в $4:

Из картинки, собсно, все спеки и ясны :) Я сильно рассчитывал, что прошивка окажется какой-то древней, и я прошью туда Tasmota или ESPHome по воздуху с помощью tuya-convert. Но обломался :( В историю с аккуратненько разобрать розетку (собранную прессом, на минуточку!) я совсем не верю, поэтому осталось только воспользоваться localtuya, который без проблем завелся: Выглядит рабочим. Время втыкать вилку в розетку, розетку в розетку и проверять в деле.

WRS: IRL

Хе, коль уж оба варианта реализованы и задеплоены, самое время начинать полевые испытания. Пожалуй, это были самые интересные постирушки в моей жизни :)

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

WRS: final thoughts

Остались последние штрихи, т.к. графичек потребления это хорошо, но это еще не готовый источник событий для нотификаций. К счастью, в интернетах можно найти приличное количество автоматизаций подобного типа (замерили потребление -> приняли решение о состоянии).

  • Сам же я взял blueprint Appliance has finished от metbril@: appliance_has_finished.yaml
  • Добавил его в Home Assistant и чуть поднастроил:
  • Получил на выходе логику:
    • если 3 и более минуты потребление более 3W - машина начала работу
    • если же 5 и более минут потребление менее 3W - машина закончила работу
    • если машина закончила работу - стриггерить событие washer_state

Довольно незамысловато и ровно то, что нужно. На этом часть с определением “работает ли стиральная машина” я решил закончить и начать заниматься состоянием дверцы.

Door state

Помните, я в самом начале говорил, что хорошо было бы знать, когда кто-то достал вещи и нет смысла топать на балкон? Вот этот раздел как раз об этом. Бонусом подумалось, что это же решение позволит еще больше снизить риск появления фолзов, т.к. мы можем увеличивать время простоя без ущерба для UX.

Учитывая, что машина у меня с вертикальной загрузкой - решение этой задачи пришло почти само собой:

  • берем какой-нить ESP32. У меня ждал своего часа Super Mini ESP32-C3 (розовый, офк) за $3, который идеально вписался
  • лепим к нему датчик расстояния. Я прикупил на алике VL53L0X за $1
  • измеряя расстояние, принимаем решение, открыта ли дверца стиральной машины

Схематично, выглядит примерно так: Осталось дело за малым - подключаем VL53L0X к питанию и I²C шине ESP32:

| ESP32          |  VL53L0X  |
|----------------|-----------|
|  3.3V          |    VIN    |
|  GND           |    GN     |
|  GPIO10        |    SCL    |
|  GPIO9         |    SDA    |
|  X (not used)  |    GPIO1  |
|  X (meh)       |    XSHUT  |

На XSHUT я забил, т.к. датчик у меня подключается только один и этот pin использоваться не будет. После небольшой пайки и клейки у меня получилась такая милаха:

Осталось написать конфиг для ESPHome (сорец):

esphome:
  name: washeresp
  friendly_name: WasherESP

esp32:
  board: esp32-c3-devkitm-1
  framework:
    type: arduino

# Enable logging
logger:

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

ota:
  password: "deadbeef"

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

  manual_ip:
    static_ip: 10.11.4.3
    gateway: 10.11.0.1
    subnet: 255.255.0.0
    dns1: 10.11.1.2

# I²C Bus необходим, т.к. к нему подключается vl53l0x
i2c:
  sda: GPIO9
  scl: GPIO10
  id: bus
  scan: true

# Не люблю много думать о времени, потому всегда использую Home Assistant Time Source
# doc: https://esphome.io/components/time/homeassistant.html
time:
  - platform: homeassistant
    id: homeassistant_time

# Status LED Light для того чтобы понимать текущее состояние
# doc: https://esphome.io/components/light/status_led
status_led:
  pin:
    number: GPIO8
    inverted: true

switch:
  # Restart Switch, чтобы мочь рестартить MCU из HA
  # doc: https://esphome.io/components/switch/restart.html
  - platform: restart
    name: Restart

binary_sensor:
  # Status Binary Sensor, дабы отрисовывать аптайм
  # doc: https://esphome.io/components/binary_sensor/status.html
  - platform: status
    name: Status

  # С помощью template перенесем логику обнаружения непосредственно на ESP
  # doc: https://esphome.io/components/binary_sensor/template.html
  - platform: template
    id: washer_window_opened
    publish_initial_state: true
    name: "Washer Window"
    device_class: window

sensor:
  # WiFi Signal Sensor, дабы лучше понимать что с WiFi
  # doc: https://esphome.io/components/sensor/wifi_signal.html
  - platform: wifi_signal
    name: "WiFi Signal Strength"
    update_interval: 60s

  # VL53L0X Time Of Flight Distance Sensor, собсно ради него весь и сыр-бор
  # doc: https://esphome.io/components/sensor/vl53l0x
  - platform: vl53l0x
    name: "Washer Door Distance"
    id: washer_door_distance
    address: 0x29
    update_interval: 5s
    long_range: false
    unit_of_measurement: "m"
    internal: true
    filters:
      - lambda: !lambda |-
          // считаем дверцу открытой, если растояние до нее менее 20 см
          bool isOpened = x <= 0.20;

          // дальше меняем состояние бинарного сенсора "washer_window_opened"
          bool curState = id(washer_window_opened).state;
          if (isOpened != curState) {
            id(washer_window_opened).publish_state(isOpened);
          }

          return {};

Прошить и добавить в Home Assistant:

Самое время проверить в бою:

Это был последний компонент пазла. Теперь самое время объединить все вместе во имя счастья пользователей :)

Нотификашки

И так, к этому моменту я располагал:

  • кастомным событием типа washer_state от умной розетки, состояние которого может запаздывать на 5 минут
  • бинарным сенсором washer_window_opened от датчика расстояния о состоянии дверцы
  • часы Ulanzi TC001, на которые надо бы доставить нотификашку
  • телефоны, на которые надо бы доставить пушик

Звучит, как время доставать JSONклей. И так оно и есть, потому как для этого я написал flow для Node-RED (сорец):

За кубиками я спрятал следующую логику работы:

  • у нас есть два источника событий: washer_state и washer_window_opened + их имитации для ручного запуска в целях отладки
  • когда наступает washer_state (машина отработала по данным от розетки), то:
    • проверяем не было ли недавно события об открытии дверцы
    • если было - считаем за фолз или отставание (тип уже достали вещи)
    • если не было - отправляем в телефон и часики нотификашку
  • когда наступает washer_window_opened (был открыта дверца), то:
    • записываем время наступления события, дабы учесть его при обработке washer_state
    • отменяем нотификашку на часах

Да, нотификации на часах получились не многопоточные, но я [пока] и не планировал добавлять новых источников :) Пробуем в деле:

Что и требовалось:

  • сначала вручную стриггерил событие washer_state так, как это сделала бы розетка
  • прилетела нотификашка на телефон и часики
  • потом вручную стриггерил событие washer_window_opened так, как это сделал бы датчик расстояния
  • нотификашка пропала с часов
  • затем еще пару раз стриггерил событие washer_state, чтобы убедиться, что кеширование состояния дверцы работает и события будут отброшены как фолзовые
  • Profit!!11

Closure

Это был довольно мучительный проект. Нет, не потому что сложный, а потому что эксперименты со стиральной машиной отнимают слишком много времени :(

Но, надеюсь, пользователи будут довольны, и эти усилия себя окупят ;)

Разделы:

Дата изменения: