RSS блога
Подписка
Делаем секционные гаражные ворота более удобными
В данном топике решил описать пример автоматизации одной из типовых задач. Имеются автоматические секционные ворота (Came V900E), необходимо организовать управление ими через интернет и дополнительно кнопками. Все управление требуется связать в единую систему вдобавок обеспечить удобную индикацию состояния системы. Под катом вас ждет разработка законченного устройства (плата, корпус, провода), а также подход к разработке логики и программного обеспечения. В общем, все как вы любите, осторожно — много фото.
Все описанное применимо ко многим типам приводов, в частности все работает на воротах фирмы Hormann.
Краткое описание исходной ситуации.
Имеется подземный гараж с двумя воротами. Одни ворота располагаются вверху (при въезде на спуск), вторые ворота располагаются внизу (непосредственно, при въезде в сам гараж). Ворота оснащены электрическими приводами, управляются с радио брелоков. Чтобы было понятнее, приведу иллюстрацию:
Кнопки, изображенные на картинке, в исходной системе отсутствуют, но их хотелось бы иметь.
Недостатки исходного решения:
— ограниченная дальность работы брелоков (даже в доме не везде возможно нажать кнопку брелока и открыть/закрыть ворота)
— отсутствие обратной связи (открылись или закрылись ворота можно выяснить только визуальным контролем, что усугубляет первый недостаток)
— необходимость носить брелок (например при походе в магазин или еще куда-то, наличие связки ключей с брелоком может существенно ухудшить впечатление от похода)
— брелоки работают на частоте 433 МГц без обратной связи, данные передают в нешифрованном виде, что может привести к копированию кода брелока и неприятным последствиям
— даже находясь в гараже требуется применять брелок чтобы выйти на улицу и зайти обратно, а брелоки имеют тенденцию к потерям/поломкам, просадке батарейки и тп…
— потеря брелока на улице требует провести процедуру стирания всех брелоков и прописывания только существующих и дополнительного
— приглашение сторонних работников (например на ремонтные работы), требует выдачи им брелоков для доступа на территорию в отсутствии хозяев, что также не добавляет безопасности
Все эти недостатки по отдельности как-то решаются и без разработки своего решения, но либо дороги, либо создают нагромождения устройств, либо неудобны, либо требуют специфической эксплуатации.
Например, некоторые производители продают свои решения для управления воротами через интернет, но цена у таких решений весьма высока (особенно учитывая что в данном случае 2 независимых механизма ворот), они требуют установки своих приложений, завязаны на серверную инфраструктуру производителя ворот (а в наше время это тоже риски), имеют ряд ограничений (например, интерфейс уже сделан и его нельзя изменить). По брелокам без переменного кода — тоже есть решения, заменить на модули с переменным кодом и/или обратной связью. В качестве кнопок, например, можно использовать беспроводные выключатели — но останется проблема безопасности радио эфира. По обратной связи — можно конечно поставить какие-то датчики, не связанные с приводом ворот — но они опять потребуют какую-то инфраструктуру. Кнопки можно подключить напрямую к приводам ворот, но как показали тесты они чувствительны к времени нажатия и дребезгу контактов.
В итоге принято решение, сделать свое решение, максимально подходящее под конкретные условия. В качестве базы следует выбрать типовые компоненты, которые сравнительно легко поддаются замене и доступны на рынке.
Базовое описание решения
В качестве ядра системы решено использовать контроллер esp8266, который весьма бюджетен, имеет достаточную производительность, достаточное количество выводов, удобные среды разработки и встроенный интерфейс сетевого взаимодействия по WiFi. Большинство приводов ворот имеют встроенные кнопки для управления воротами, либо отдельные выводы для подключения устройств управления, изменение состояние происходит замыканием на короткий промежуток времени контактов, привод при этом меняет свое состояние (при закрытых воротах — начинается открывание, при открытых закрывание, при движении — происходит остановка привода. Для воздействия на эти контакты достаточно маломощного реле. Для отслеживания состояния ворот удобно использовать внешние герконы, которые весьма надежны, компактны и не требуют обслуживания. Наличие кнопок подключенных к контроллеру позволит на месте удобно управлять приводами. Наличие WiFi обеспечит связь нашего решения с внешним миром, интеграцию в инфраструктуру «умного дома» и поддержку нужного веб-интерфейса.
Устройство должно уметь работать с обоими приводами ворот, иметь малое энергопотребление.
Создание «железной» части устройства
В данном случае, нам потребуется контроллер (возьмем модуль esp-07), обвязка модуля согласно документации, два маломощных надежных реле (например HF49FD), подтягивающие резисторы датчиков и кнопок, питаться устройство будет от 5 Вольт постоянного тока, поэтому требуется понижающий преобразователь для питания контроллера от 3.3 Вольт, можно взять AMS1117. Для стабилизации питания установим электролитический и керамический конденсаторы. Для управления реле используем маломощные биполярные транзисторы 2N2222, управляющие выводы реле зашунтируем диодами M1. Принципиальная схема устройства выглядит таким образом:
R1, R2, R3 — подтягивающие резисторы, необходимы для корректной работы ESP-07
R4, R5, R9, R12 — подтягивают соответствующие выводы ESP-07 к питанию для корректной работы кнопок.
VR1(AMS1117) — линейный стабилизатор на 3.3 В
C1, C2 — электролитический и керамический конденсаторы для питания модуля
R6, HL3 — светодиодный сигнал наличия связи модуля по wifi
VT1, VT2 — биполярные транзисторы 2n2222, нужны для управления реле
R10, R11 — ограничивают ток управления транзисторами
R7, HL1 и R8, HL2 — для световой индикации состояния реле
S1, S2, S3, S4 — будущие кнопки: выключатели и герконы состояния ворот.
Разрабатываем нехитрую печатную плату, на которой предусмотрим все вышеописанное:
Платка универсальная, поэтому предусмотрены:
— 6 выводов питания 5 Вольт
— 8 выводов земли (-)
— 6 выводов питания питания 3.3 В
— 2 вывода модуля ESP-07: 4 и 5 задействованы в управлении реле
— выводы 13, 12, 14, 2 и аналоговый вывод подключены к общему разъему с возможностью подтяжки к питанию
— 0 вывод расположен между подтяжками к земле и к питанию, для обеспечения заданных подтяжек, в зависимости от режима (программирование, обычная работа)
— также на общий разъем выведены TX и RX для взаимодействия с внешним миром
— питание устройства и выводы реле подключены к винтовым клемникам
— добавлена индикация включения реле в виде светодиодов с токоограничительными резисторами
— шелкография платы позволяет легко ориентироваться в расположении контактов
— круглые отверстия позволяют легко производить фиксацию платы в корпусе
Печатная плата в виде герберов была отправлена производителю плат (JLCPCB) и получены вот такие изделия:
Первым делом требуется произвести монтаж SMD-элементов, поэтому берем паяльную пасту в шприце и наносим с помощью пневматического дозатора (решение по кнопке дозатора описано здесь).
Нажимая кнопку на шприце получаем капельки и колбаски такого вида:
Далее расставляем детальки на плату, мне удобно это делать с помощью такого пинцета:
После расстановки помещаем плату в «печку» и производим запекание согласно термопрофилю:
Можно конечно это проделать феном, но практика показывает, что качество изделия выше при использовании сего чудо-агрегата.
Результат запекания:
Сам модуль контроллера и ряд даталек я предпочитаю пропаять еще паяльником. Для этого задействуем паяльную станцию:
Результат получился вот такой:
Далее нужно припаять выводные элементы и контактные штыри. Я уже говорил об используемом реле, выглядит оно так:
Итог пайки выглядит так:
Переходим к водным процедурам, нужно смыть флюс, оптимальным для этого является ультразвуковая ванна, с последующим полосканием в дистиллированной воде. В качестве средства отмывки я использую Solins FA+ или Solins US:
Итог после мойки платы и сушки бытовым феном:
В итоге имеем компактное симпатичное устройство (на мой взгляд), в которое нужно вдохнуть жизнь заливкой кода.
Пару слов про перемычки. Мне гораздо удобнее вот такие удлиненные перемычки, особенно на этапе отладки:
Подготовка модуля
Первым делом подключим USB-to-Serial конвертер и переведем перемычку в положение заливки программы:
Так как у нас будут датчики на герконах и кнопки, то для отладки удобно использовать вот такой набор кнопочек на одной плате:
Геркон в пластиковом корпусе хорошо себя зарекомендовал, поэтому будем использовать его, предварительно проверив:
Удобный магнитик для подвижной части:
Прежде чем что-то делать, заливаем пустой файл, чтобы убрать все программные ценности идущие от производителя:
Процесс пошел:
Имеем готовый к новому коду модуль и приступаем к разработке логики.
Для разработки я буду использовать среду Visual Studio Code, вполне адекватная среда с большим количеством возможностей. Описывать подробно как настроить среду разработки для работы с PlatformIO я не буду. Скажу только что настроенная среда позволяет:
— компилировать программу под заданную архитектуру
— загружать как прошивку в модуль
— собирать образ файловой системы
— загружать образ в модуль
— вести отладку программы через встроенный терминал
Так выглядит панелька через которую все это делается:
Мой файлик platformio.ini:
Требования
Нам требуется:
— работа с сетью (WiFi как в режиме Station, так и в режиме AP)
— интерфейс должен быть адаптирован под мобильные устройства, легким и отображать все необходимое
— хочется связать web-интерфейс с модулем, чтобы оперативно понимать текущее состояние ворот (открыли кнопкой или на другом устройстве, а наш интерфейс оперативно отрисовал нужные изменения без перезагрузки страницы)
— работа с кнопками (изменение состояния ворот), в нашем случае 2 кнопки (по количеству ворот), также 2 датчика закрытого состояния ворот (герконы), которые условно можно считать кнопками
— управление нагрузками (2 реле и светодиод индикации состояния wifi-соединения)
— работа с конфигурационной информацией, которую требуется хранить в энергонезависимой памяти (сетевые настройки, настройка таймера реле)
— манипуляции с состоянием устройства во время его работы (safe-mode, закрыты или открыты ворота, доступна ли сеть)
— обеспечение API для того чтобы другие устройства могли общаться с нашим
— связать все перечисленное в единую систему решающую комплексную задачу управления воротами
Программная реализация
Структура проекта и рабочая область выглядят так:
Конструкция ADC_MODE(ADC_VCC); нужна для корректного программного измерения напряжения питания, что особенно актуально при питании от батарей.
Основные сущности нашей программы представлены объектами:
— CFG — хранит конфигурационную информацию и позволяет манипулировать ей
— ST — содержит текущее состояние системы и обеспечивает доступ к нему на запись/чтение из вне в разных формах
— LC — содержит информацию о нагрузках, позволяет менять их состояние и управлять поведением
— BC — манипулятор кнопок
— NM — менеджер сети (инициализация, контроль, обработка запросов)
В функции setup, которая выполняется один раз при старте программы, производится инициализация всех объектов.
В функции loop, которая выполняется в бесконечном цикле, реализовано поведение объектов в процессе жизни устройства.
Далее покажу некоторые принципиальные моменты по работе программы.
Объявление и реализация работы с конфигурационными файлами:
Видим что и как хранится в энергонезависимой памяти нашего модуля.
Показаны особенности работы с энергонезависимой памятью для esp8266. Здесь требуется задать объем выделяемой памяти. Также имеется механизм проверки актуальности данных. В специальную ячейку записывается некое число и если при чтении получено иное, то данные перезаписываются значениями по умолчанию.
Так как заголовочный файл класса состояний подключается во всем проекте, то я сюда вынес немногочисленные общие для всего проекта элементы. В частности:
— механизм включения и выключения отладочной информации
— универсальную функцию подсчета элементов в массиве любого типа
Кроме этого, конечно, в файлике содержится описание класса состояния с методами доступа к состояниям, причем часто в нескольких форматах, включая текстовый, это нужно для удобной работы с шаблонами клиентских файлов, где вместо переменных подставляются их значения.
Пара неочевидных моментов. Для определения находится ли наше устройство в защищенном режиме или нет используется следующий подход. Сам перевод устройства в защищенный режим осуществляется замыканием перемычкой выводов TX и RX. При старте мы посылаем в последовательный некое число и если выводы соединены, то ожидаем считать, то же самое число, если так происходит, значит ставим флаг работы в защищенном режиме (в этом режиме все доступно без пароля и модуль работает в режиме точки доступа без пароля). Второй момент заключается в счетчике времени. Захотелось знать время непрерывной работы устройства. Стандартный счетчик времени основанный на типе uint32_t переполняется и нам не подходит. Поэтому введем еще одну переменную типа uint32_t, при переполнении первой меняем на 1 вторую. Такого времени хватит на долго :). Также имеется функция которая возвращает время непрерывной работы (UPTIME) устройства в понятном текстовом виде.
Методов здесь не много. Следует сказать, что я предпочитаю асинхронную обработку http запросов, поэтому и используются соответствующие библиотеки.
Как видно, все запросы, которые выдают какие-то полезные данные, либо меняют состояние системы требуют авторизации. Авторизация требуется только в том случае, если не включен защищенный режим. Для хранения файлов я использовал файловую систему LittleFS.
По запросу '/set' выдается страница с настройками, при этом, если запрос типа 'POST', то осуществляется обработка принятых от клиента данных. При запросе файлов index.html и settings.html они прогоняются через простенький шаблонизатор, где переменные заменяются своими значениями.
Собственно, видны методы инициализации нагрузок, включения и выключения нагрузки по номеру. Каждая нагрузка имеет пару флагов: состояние и нуждается ли она в автоматическом отключении.
Здесь после заданного в конфигурации времени снимается напряжение с реле подключенного к приводу ворот. Также в этом методе в зависимости от состояния сети, включается и выключается светодиод выведенный на крышку устройства.
Метод check проверяет наличие изменений состояния кнопок, а метод logic — реализует логику в зависимости от этих изменений. Про работу с кнопками я как то писал тут.
Я кратко описал основные моменты работы программы на серверной стороне. Теперь про клиент.
Для того, чтобы интерфейс выглядел более-менее нормально и в то же время не занимал много места и работал быстро, я взял набор стилей PureCSS. Обработку логики на стороне клиента реализовал на чистом javascript, без использования сторонних библиотек.
У нас имеется небольшое верхнее меню состоящие из 2-х пунктов: Управление и Настройки, выбранный пункт меню подсвечивается. Далее идет небольшая табличка с текущим состоянием ворот. Данный файл, как и файл настроек на стороне сервера обрабатывается простеньким шаблонизатором, где на место %VAR% подставляется значение VAR. В данном файле подставляется состояние ворот и цвет состояний, для удобного визуального контроля. Так сказазать «Server-Side Rendering». После этого расположены две кнопки изменения состояния ворот.
Клиентская часть кода, как уже писал ранее написана на javascript. В данном файле всем кнопкам с классом ".pure-button" ставится обработчик нажатия, по нажатию на сервер с помощью функции fetch уходит запрос с именем кнопки. Во время нажатия кнопка меняет свой цвет, чтобы визуально понять что произошло нажатие. При получении ответа с кодом 200 исходный цвет кнопки восстанавливается через 800мс (иначе трудно уловить, что что-то произошло.
Далее идет код обработки реалтайм сообщений от сервера, с которым поддерживается связь все время пока страница открыта. При поступлении соответствующих сообщений текст состояния ворот и цвет ячейки состояния ворот меняются.
Страница настроек содержит то же меню что и страница управления. За меню следуют текущие свойства устройства, такие как:
— включен ли защищенный режим
— уровень WIFI сигнала
— количество свободной памяти
— напряжение питания
— время непрерывной работы устройства.
Далее идет форма, позволяющая изменять настройки устройства:
— имя хоста
— имя пользователя для авторизации на странице
— пароль пользователя для авторизации на странице
— режим WIFI (либо точка доступа, либо подключение к существующей точке)
— имя точки доступа к которой будет подключение, либо которая будет создана, зависит от режима
— пароль точки доступа к которой будет подключение, либо которая будет создана, зависит от режима
— режим IP адреса (статический или динамический)
— сетевые настройки
— время включенного состояния реле (определяет через сколько миллисекунд реле на плате будет выключено и контакты на приводе ворот разомкнуться).
Рядом с полями паролей расположена кнопка скрытия/показа паролей, нехитрый программный код на javascript реализует данное поведение. Помимо этого, на javascript реализуется валидация ip адресов формы, так как появление там некорректных данных может потребовать лишних телодвижений.
Вид интерфейса
Так выглядит основная страница управления:
Так выглядит страница с настройками:
Здесь можно посмотреть как интерфейс реагирует на изменение состояния ворот:
На видео виден механизм Server-Sent Events (SSE) в действии, когда браузер поддерживает открытым соединение с сервером и мгновенно реагирует на присылаемую сервером информацию.
Корпус
Многие из вас отлично понимают, что устройство без корпуса, это полуфабрикат. Нужно сделать корпус максимально подходящий нашему устройству, с отверстиями в нужных местах. Я считаю, что вполне разумно прибегнуть к 3Д-печати. Рисуем модель нижней части корпуса. Предусмотрев отверстия для ввода проводов, крепежные отверстия и стойки для нашей платы. В одной части корпуса будет располагаться наше устройство, в другой уложенные провода. Результат вот такой:
Чтобы сделать крышечку, нужно отступить 0.2 мм, чтобы она плотно покрывала
наше устройство. В крышечке предусматриваем отверстие для светодиода индикации состояния сети:
Результат печати:
Помимо корпуса для самого устройства, требуются корпуса для герконов и механика крепления магнитика к воротам. Так как корпус геркона требуется крепить к криволинейной направляющей ворот, то прибегнем к моделированию используя пластилин:
Заодно сделал примерный слепок ответной части, которую будем крепить к естественно торчащему болту конструкции ворот. Слепки выглядят так:
Чтобы иметь возможность регулировки расстояния от магнита до геркона предусмотрим крепежное отверстие в виде овала.
Далее по точкам, с помощью сплайна нетрудно воспроизвести криволинейную поверхность:
Как можно видеть, с обратной стороны крепежа геркона я выдавил углубление под сам геркон и под провода для его подключения.
Запускаем печать:
Результат:
Сравниваем кривизну со слепком и убеждаемся, что все норм:
Монтаж
Приводы ворот закреплены на потолке. Кроме приводов имеются уже смонтированные датчики препятствия в проеме ворот, соединенные витой парой. Задействованы только 4 проводка в сетевом кабеле. Чтобы не тянуть лишние провода, достаточно присоединиться к существующим свободным парам. Всего для каждых ворот нам требуется 4 провода: два провода будут замыкать нужные контакты привода и два провода пойдут к выключателю. Кинем еще одну витую пару от одного привода к нашему устройству, для управления вторым приводом расположим наше устройство в непосредственной близости от датчика.
Привод на картинке:
Вот так выглядит привод уличных ворот без крышки:
Вот так ближе интересующее нас место:
Колодка управления воротами:
Согласно документации, нам требуется к реле подключать выводы 2 и 7 колодки привода.
Монтируем выключатель данного привода, оставляя провод для геркона:
Выключатель используем кнопочного типа, без фиксации (часто используются для звонков). Теперь подойдя к воротам, можно легко их открыть нажатием кнопки, уходя, достаточно нажать кнопку с улицы и ворота закроются.
Возле нижних ворот монтируем наше устройство и два выключателя. Провода подходят к устройству сквозь стену. Ближний к воротам выключатель позволит управлять нижними воротами, второй дублирует верхний выключатель, позволяя, например, проветрить не поднимаясь наверх и не прибегая к пульту.
Как и было задумано, крышечка хорошо закрыла устройство вместе с проводами коммутации, по светодиоду видно что подключение к сети произошло и все норм:
На состояние стен не обращайте внимание, в гараже предстоит ремонт.
Монтаж осуществлен (осталось только приклеить герконы в корпусах и ответные магнитики), ворота управляются, как и задумано. Далее можно через VPN вынести интерфейс наружу, либо реализовать в какой-то системе (типа OpenHub) управление, благо api есть, или дописать взаимодействие по mqtt. В крайнем случае можно просто пробросить порт из внутренней сети наружу. По моему, получилось вполне полезное устройство, которое облегчит взаимодействие с воротами своему владельцу.
На этом заканчиваю. Спасибо тем кто дочитал до конца, надеюсь кому-то информация окажется полезной!
Все описанное применимо ко многим типам приводов, в частности все работает на воротах фирмы Hormann.
Краткое описание исходной ситуации.
Имеется подземный гараж с двумя воротами. Одни ворота располагаются вверху (при въезде на спуск), вторые ворота располагаются внизу (непосредственно, при въезде в сам гараж). Ворота оснащены электрическими приводами, управляются с радио брелоков. Чтобы было понятнее, приведу иллюстрацию:
Кнопки, изображенные на картинке, в исходной системе отсутствуют, но их хотелось бы иметь.
Недостатки исходного решения:
— ограниченная дальность работы брелоков (даже в доме не везде возможно нажать кнопку брелока и открыть/закрыть ворота)
— отсутствие обратной связи (открылись или закрылись ворота можно выяснить только визуальным контролем, что усугубляет первый недостаток)
— необходимость носить брелок (например при походе в магазин или еще куда-то, наличие связки ключей с брелоком может существенно ухудшить впечатление от похода)
— брелоки работают на частоте 433 МГц без обратной связи, данные передают в нешифрованном виде, что может привести к копированию кода брелока и неприятным последствиям
— даже находясь в гараже требуется применять брелок чтобы выйти на улицу и зайти обратно, а брелоки имеют тенденцию к потерям/поломкам, просадке батарейки и тп…
— потеря брелока на улице требует провести процедуру стирания всех брелоков и прописывания только существующих и дополнительного
— приглашение сторонних работников (например на ремонтные работы), требует выдачи им брелоков для доступа на территорию в отсутствии хозяев, что также не добавляет безопасности
Все эти недостатки по отдельности как-то решаются и без разработки своего решения, но либо дороги, либо создают нагромождения устройств, либо неудобны, либо требуют специфической эксплуатации.
Например, некоторые производители продают свои решения для управления воротами через интернет, но цена у таких решений весьма высока (особенно учитывая что в данном случае 2 независимых механизма ворот), они требуют установки своих приложений, завязаны на серверную инфраструктуру производителя ворот (а в наше время это тоже риски), имеют ряд ограничений (например, интерфейс уже сделан и его нельзя изменить). По брелокам без переменного кода — тоже есть решения, заменить на модули с переменным кодом и/или обратной связью. В качестве кнопок, например, можно использовать беспроводные выключатели — но останется проблема безопасности радио эфира. По обратной связи — можно конечно поставить какие-то датчики, не связанные с приводом ворот — но они опять потребуют какую-то инфраструктуру. Кнопки можно подключить напрямую к приводам ворот, но как показали тесты они чувствительны к времени нажатия и дребезгу контактов.
В итоге принято решение, сделать свое решение, максимально подходящее под конкретные условия. В качестве базы следует выбрать типовые компоненты, которые сравнительно легко поддаются замене и доступны на рынке.
Базовое описание решения
В качестве ядра системы решено использовать контроллер esp8266, который весьма бюджетен, имеет достаточную производительность, достаточное количество выводов, удобные среды разработки и встроенный интерфейс сетевого взаимодействия по WiFi. Большинство приводов ворот имеют встроенные кнопки для управления воротами, либо отдельные выводы для подключения устройств управления, изменение состояние происходит замыканием на короткий промежуток времени контактов, привод при этом меняет свое состояние (при закрытых воротах — начинается открывание, при открытых закрывание, при движении — происходит остановка привода. Для воздействия на эти контакты достаточно маломощного реле. Для отслеживания состояния ворот удобно использовать внешние герконы, которые весьма надежны, компактны и не требуют обслуживания. Наличие кнопок подключенных к контроллеру позволит на месте удобно управлять приводами. Наличие WiFi обеспечит связь нашего решения с внешним миром, интеграцию в инфраструктуру «умного дома» и поддержку нужного веб-интерфейса.
Устройство должно уметь работать с обоими приводами ворот, иметь малое энергопотребление.
Создание «железной» части устройства
В данном случае, нам потребуется контроллер (возьмем модуль esp-07), обвязка модуля согласно документации, два маломощных надежных реле (например HF49FD), подтягивающие резисторы датчиков и кнопок, питаться устройство будет от 5 Вольт постоянного тока, поэтому требуется понижающий преобразователь для питания контроллера от 3.3 Вольт, можно взять AMS1117. Для стабилизации питания установим электролитический и керамический конденсаторы. Для управления реле используем маломощные биполярные транзисторы 2N2222, управляющие выводы реле зашунтируем диодами M1. Принципиальная схема устройства выглядит таким образом:
R1, R2, R3 — подтягивающие резисторы, необходимы для корректной работы ESP-07
R4, R5, R9, R12 — подтягивают соответствующие выводы ESP-07 к питанию для корректной работы кнопок.
VR1(AMS1117) — линейный стабилизатор на 3.3 В
C1, C2 — электролитический и керамический конденсаторы для питания модуля
R6, HL3 — светодиодный сигнал наличия связи модуля по wifi
VT1, VT2 — биполярные транзисторы 2n2222, нужны для управления реле
R10, R11 — ограничивают ток управления транзисторами
R7, HL1 и R8, HL2 — для световой индикации состояния реле
S1, S2, S3, S4 — будущие кнопки: выключатели и герконы состояния ворот.
Разрабатываем нехитрую печатную плату, на которой предусмотрим все вышеописанное:
Платка универсальная, поэтому предусмотрены:
— 6 выводов питания 5 Вольт
— 8 выводов земли (-)
— 6 выводов питания питания 3.3 В
— 2 вывода модуля ESP-07: 4 и 5 задействованы в управлении реле
— выводы 13, 12, 14, 2 и аналоговый вывод подключены к общему разъему с возможностью подтяжки к питанию
— 0 вывод расположен между подтяжками к земле и к питанию, для обеспечения заданных подтяжек, в зависимости от режима (программирование, обычная работа)
— также на общий разъем выведены TX и RX для взаимодействия с внешним миром
— питание устройства и выводы реле подключены к винтовым клемникам
— добавлена индикация включения реле в виде светодиодов с токоограничительными резисторами
— шелкография платы позволяет легко ориентироваться в расположении контактов
— круглые отверстия позволяют легко производить фиксацию платы в корпусе
Печатная плата в виде герберов была отправлена производителю плат (JLCPCB) и получены вот такие изделия:
Первым делом требуется произвести монтаж SMD-элементов, поэтому берем паяльную пасту в шприце и наносим с помощью пневматического дозатора (решение по кнопке дозатора описано здесь).
Нажимая кнопку на шприце получаем капельки и колбаски такого вида:
Далее расставляем детальки на плату, мне удобно это делать с помощью такого пинцета:
После расстановки помещаем плату в «печку» и производим запекание согласно термопрофилю:
Можно конечно это проделать феном, но практика показывает, что качество изделия выше при использовании сего чудо-агрегата.
Результат запекания:
Сам модуль контроллера и ряд даталек я предпочитаю пропаять еще паяльником. Для этого задействуем паяльную станцию:
Результат получился вот такой:
Далее нужно припаять выводные элементы и контактные штыри. Я уже говорил об используемом реле, выглядит оно так:
Итог пайки выглядит так:
Переходим к водным процедурам, нужно смыть флюс, оптимальным для этого является ультразвуковая ванна, с последующим полосканием в дистиллированной воде. В качестве средства отмывки я использую Solins FA+ или Solins US:
Итог после мойки платы и сушки бытовым феном:
В итоге имеем компактное симпатичное устройство (на мой взгляд), в которое нужно вдохнуть жизнь заливкой кода.
Пару слов про перемычки. Мне гораздо удобнее вот такие удлиненные перемычки, особенно на этапе отладки:
Подготовка модуля
Первым делом подключим USB-to-Serial конвертер и переведем перемычку в положение заливки программы:
Так как у нас будут датчики на герконах и кнопки, то для отладки удобно использовать вот такой набор кнопочек на одной плате:
Геркон в пластиковом корпусе хорошо себя зарекомендовал, поэтому будем использовать его, предварительно проверив:
Удобный магнитик для подвижной части:
Прежде чем что-то делать, заливаем пустой файл, чтобы убрать все программные ценности идущие от производителя:
Процесс пошел:
Имеем готовый к новому коду модуль и приступаем к разработке логики.
Для разработки я буду использовать среду Visual Studio Code, вполне адекватная среда с большим количеством возможностей. Описывать подробно как настроить среду разработки для работы с PlatformIO я не буду. Скажу только что настроенная среда позволяет:
— компилировать программу под заданную архитектуру
— загружать как прошивку в модуль
— собирать образ файловой системы
— загружать образ в модуль
— вести отладку программы через встроенный терминал
Так выглядит панелька через которую все это делается:
Мой файлик platformio.ini:
[env:esp07]
platform = espressif8266
board = esp07
framework = arduino
board_build.filesystem = littlefs
monitor_speed = 115200
Требования
Нам требуется:
— работа с сетью (WiFi как в режиме Station, так и в режиме AP)
— интерфейс должен быть адаптирован под мобильные устройства, легким и отображать все необходимое
— хочется связать web-интерфейс с модулем, чтобы оперативно понимать текущее состояние ворот (открыли кнопкой или на другом устройстве, а наш интерфейс оперативно отрисовал нужные изменения без перезагрузки страницы)
— работа с кнопками (изменение состояния ворот), в нашем случае 2 кнопки (по количеству ворот), также 2 датчика закрытого состояния ворот (герконы), которые условно можно считать кнопками
— управление нагрузками (2 реле и светодиод индикации состояния wifi-соединения)
— работа с конфигурационной информацией, которую требуется хранить в энергонезависимой памяти (сетевые настройки, настройка таймера реле)
— манипуляции с состоянием устройства во время его работы (safe-mode, закрыты или открыты ворота, доступна ли сеть)
— обеспечение API для того чтобы другие устройства могли общаться с нашим
— связать все перечисленное в единую систему решающую комплексную задачу управления воротами
Программная реализация
Структура проекта и рабочая область выглядят так:
Чтобы удобнее было что-то рассказывать, приведу содержимое файлика main.cpp
#include "button_control.h"
#include "config.h"
#include "load_control.h"
#include "network.h"
#include "state.h"
ADC_MODE(ADC_VCC);
Config CFG;
State ST;
LoadsControl LC;
ButtonControl BC;
NetManager NM;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// init
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
// init state
ST.init();
// read config from EEPROM
CFG.get();
// init loads
LC.init();
// init buttons
BC.init();
// init network
NM.init();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// main loop
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void loop() {
// update time
ST.updateClock();
// loads control
LC.control();
// reaction fo change buttons state
BC.logic();
// controlling change state buttons
BC.check();
// network control
NM.control();
}
Конструкция ADC_MODE(ADC_VCC); нужна для корректного программного измерения напряжения питания, что особенно актуально при питании от батарей.
Основные сущности нашей программы представлены объектами:
— CFG — хранит конфигурационную информацию и позволяет манипулировать ей
— ST — содержит текущее состояние системы и обеспечивает доступ к нему на запись/чтение из вне в разных формах
— LC — содержит информацию о нагрузках, позволяет менять их состояние и управлять поведением
— BC — манипулятор кнопок
— NM — менеджер сети (инициализация, контроль, обработка запросов)
В функции setup, которая выполняется один раз при старте программы, производится инициализация всех объектов.
В функции loop, которая выполняется в бесконечном цикле, реализовано поведение объектов в процессе жизни устройства.
Далее покажу некоторые принципиальные моменты по работе программы.
Объявление и реализация работы с конфигурационными файлами:
Заголовочный файл config.h
#ifndef CONFIG_H
#define CONFIG_H
#include <Arduino.h>
// Config
class Config {
public:
char host[32];
char user[16];
char pass[16];
bool wifimode;
char ssid[16];
char ssidpass[16];
bool ipmode;
char ip[16];
char gateway[16];
char subnet[16];
char primaryDNS[16];
char secondaryDNS[16];
uint32_t maxon;
// read config from EEPROM, if this is the first start, then write
void get();
// write config to EEPROM
void put();
};
extern Config CFG;
#endif // CONFIG_H
Видим что и как хранится в энергонезависимой памяти нашего модуля.
Файл реализации config.cpp
#include "config.h"
#include <EEPROM.h>
#define INIT_EEPROM_ADDR 0
#define INIT_EEPROM_KEY 1
#define SAVE_EEPROM_ADDR 1
#define EEPROM_MAX_SIZE 512
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// read config from EEPROM, if this is the first start, then write the value in
// EEPROM
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Config::get() {
EEPROM.begin(EEPROM_MAX_SIZE);
if (EEPROM.read(INIT_EEPROM_ADDR) != INIT_EEPROM_KEY) { // first start
EEPROM.write(INIT_EEPROM_ADDR, INIT_EEPROM_KEY); // write key
// set default values
strcpy(host, "WIFI-RELAY");
strcpy(user, "user");
strcpy(pass, "pass");
wifimode = 1;
strcpy(ssid, "xxx");
strcpy(ssidpass, "xxxxxxx");
ipmode = 1;
strcpy(ip, "192.168.1.189");
strcpy(gateway, "192.168.1.1");
strcpy(subnet, "255.255.255.0");
strcpy(primaryDNS, "192.168.1.1");
strcpy(secondaryDNS, "8.8.8.8");
maxon = 2000;
// save
put();
}
EEPROM.get(SAVE_EEPROM_ADDR, CFG);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// write config to EEPROM
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Config::put() {
EEPROM.put(SAVE_EEPROM_ADDR, CFG);
EEPROM.commit();
}
Показаны особенности работы с энергонезависимой памятью для esp8266. Здесь требуется задать объем выделяемой памяти. Также имеется механизм проверки актуальности данных. В специальную ячейку записывается некое число и если при чтении получено иное, то данные перезаписываются значениями по умолчанию.
Заголовочный файл класса состояния устройства - state.h
#ifndef STATE_H
#define STATE_H
#include <Arduino.h>
#include "network.h"
#undef DEBUG
#define DEBUG // for debug: DPRINT & DPRINTLN
#ifdef DEBUG
#define DPRINT(...) Serial.print(__VA_ARGS__) // debug print
#define DPRINTLN(...) Serial.println(__VA_ARGS__) // debug print with new line
#define DPRINTF(...) Serial.printf(__VA_ARGS__) // debug printf
#else
#define DPRINT(...) // blank line
#define DPRINTLN(...) // blank line
#define DPRINTF(...) // blank line
#endif
// return amount object
template <typename T, size_t n>
inline size_t arraySize(const T (&arr)[n]) {
return n;
}
class State {
public:
// set start state
void init();
// get safe mode
inline bool isSafeMode() { return FLAGS.safeMode; }
// get safe mode text
inline String getSafeModeText() { return FLAGS.safeMode ? "ON" : "OFF"; }
// set open gate by num
inline void open(uint8_t num) {
extern NetManager NM;
if (num == 0)
FLAGS.firstState = true;
else if (num == 1)
FLAGS.secondState = true;
NM.sendEvents(getData(num), "change");
}
// set close gate by num
inline void close(uint8_t num) {
extern NetManager NM;
if (num == 0)
FLAGS.firstState = false;
else if (num == 1)
FLAGS.secondState = false;
NM.sendEvents(getData(num), "change");
}
// get state gate by num
inline char* getData(uint8_t num) {
static char _return[24];
sprintf(_return, "{\"num\":%d,\"state\":%d}", (num + 1),
(uint8_t)getState(num));
return _return;
}
// get state gate by num
inline bool getState(uint8_t num) {
if (num == 0)
return FLAGS.firstState;
else if (num == 1)
return FLAGS.secondState;
return false;
}
// get state gate Text by num
inline String getStateText(uint8_t num) {
bool flag = FLAGS.firstState;
if (num == 1) flag = FLAGS.secondState;
return flag ? "ОТКРЫТО" : "ЗАКРЫТО";
}
// get color gate by num
inline String getColor(uint8_t num) {
bool flag = FLAGS.firstState;
if (num == 1) flag = FLAGS.secondState;
return flag ? "#F08080" : "#98FB98";
}
// on flag wifi connect
inline void setConnect() { FLAGS.connectWIFI = true; }
// off flag wifi connect
inline void setDisconnect() { FLAGS.connectWIFI = false; }
// get flag wifi connect
inline bool isConnect() { return FLAGS.connectWIFI; }
// fill current time
void updateClock();
// get low part current time
inline uint32_t getTime() { return lowPartCurrentTime; }
// get full current time
inline uint64_t getFullTime() {
return (uint64_t)highPartCurrentTime << 32 | lowPartCurrentTime;
}
// get uptime string
char *getUptimeText();
private:
// low part uptime
uint32_t lowPartCurrentTime;
// high part uptime
uint32_t highPartCurrentTime;
// flags state
struct {
bool safeMode : 1; // run without password in AP mode
bool firstState : 1; // state near gate
bool secondState : 1; // state far gate
bool connectWIFI : 1; // is connect to WIFI
} FLAGS;
};
extern State ST;
#endif // STATE_H
Так как заголовочный файл класса состояний подключается во всем проекте, то я сюда вынес немногочисленные общие для всего проекта элементы. В частности:
— механизм включения и выключения отладочной информации
— универсальную функцию подсчета элементов в массиве любого типа
Кроме этого, конечно, в файлике содержится описание класса состояния с методами доступа к состояниям, причем часто в нескольких форматах, включая текстовый, это нужно для удобной работы с шаблонами клиентских файлов, где вместо переменных подставляются их значения.
Реализация методов состояния устройства - state.cpp
#include "state.h"
#define SERIAL_SPEED 115200
#define SERIAL_DATA 45
#define SERIAL_TIME_SLEEP 1000
#define SERIAL_TIME_WAIT 10
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// first init state
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void State::init() {
Serial.begin(SERIAL_SPEED);
#ifdef DEBUG
// Serial.setDebugOutput(true);
delay(SERIAL_TIME_SLEEP);
#endif
DPRINTLN(F("start"));
Serial.write(SERIAL_DATA);
delay(SERIAL_TIME_WAIT);
uint8_t incomingByte = 0;
if (Serial.available() > 0) {
incomingByte = Serial.read();
if (SERIAL_DATA == incomingByte)
FLAGS.safeMode = true;
else
FLAGS.safeMode = false;
}
updateClock();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// set current time
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void State::updateClock() {
uint32_t newCurrentTime = millis();
if (newCurrentTime < lowPartCurrentTime) highPartCurrentTime++;
lowPartCurrentTime = newCurrentTime;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// get string uptime
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
char* State::getUptimeText() {
uint64_t mills = getFullTime();
static char _return[32];
uint32_t secs = mills / 1000;
uint32_t mins = secs / 60;
uint16_t hours = mins / 60;
uint16_t days = hours / 24;
mills -= secs * 1000;
secs -= mins * 60;
mins -= hours * 60;
hours -= days * 24;
sprintf(_return, "%d days %2.2d:%2.2d:%2.2d %3.3d", (uint8_t)days,
(uint8_t)hours, (uint8_t)mins, (uint8_t)secs, (uint16_t)mills);
return _return;
}
Пара неочевидных моментов. Для определения находится ли наше устройство в защищенном режиме или нет используется следующий подход. Сам перевод устройства в защищенный режим осуществляется замыканием перемычкой выводов TX и RX. При старте мы посылаем в последовательный некое число и если выводы соединены, то ожидаем считать, то же самое число, если так происходит, значит ставим флаг работы в защищенном режиме (в этом режиме все доступно без пароля и модуль работает в режиме точки доступа без пароля). Второй момент заключается в счетчике времени. Захотелось знать время непрерывной работы устройства. Стандартный счетчик времени основанный на типе uint32_t переполняется и нам не подходит. Поэтому введем еще одну переменную типа uint32_t, при переполнении первой меняем на 1 вторую. Такого времени хватит на долго :). Также имеется функция которая возвращает время непрерывной работы (UPTIME) устройства в понятном текстовом виде.
Заголовочный файл класса сетевого менеджера - network.h
#ifndef NETWORK_H
#define NETWORK_H
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
class NetManager {
private:
static String processor(const String &var);
void connect();
void setAnsvers();
public:
void init();
void control();
void sendEvents(const char *data, const char *tag);
void postProcessing(AsyncWebServerRequest *request);
};
#endif // NETWORK_H
Методов здесь не много. Следует сказать, что я предпочитаю асинхронную обработку http запросов, поэтому и используются соответствующие библиотеки.
Реализация метода устанавливающего обработчики запросов NetManager::setAnsvers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// setting ansvers for network requests
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void NetManager::setAnsvers() {
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
if (!request->authenticate(CFG.user, CFG.pass) && !ST.isSafeMode())
return request->requestAuthentication();
request->send(LittleFS, "/index.html", String(), false, processor);
});
// Route for /set web page
server.on("/set", HTTP_ANY, [this](AsyncWebServerRequest* request) {
if (!request->authenticate(CFG.user, CFG.pass) && !ST.isSafeMode())
return request->requestAuthentication();
if (request->method() == HTTP_POST) {
postProcessing(request);
}
request->send(LittleFS, "/settings.html", String(), false, processor);
});
// Route for /reset web page
server.on("/reset", HTTP_GET, [](AsyncWebServerRequest* request) {
if (!request->authenticate(CFG.user, CFG.pass) && !ST.isSafeMode())
return request->requestAuthentication();
request->send(LittleFS, "/settings.html", String(), false, processor);
ESP.restart();
});
// Button 1 control
server.on("/button1", HTTP_GET, [](AsyncWebServerRequest* request) {
if (!request->authenticate(CFG.user, CFG.pass) && !ST.isSafeMode())
return request->requestAuthentication();
LC.on(L_NEAR);
request->send(200, "application/json", "{'ok':1}");
});
// Button 2 control
server.on("/button2", HTTP_GET, [](AsyncWebServerRequest* request) {
if (!request->authenticate(CFG.user, CFG.pass) && !ST.isSafeMode())
return request->requestAuthentication();
LC.on(L_FAR);
request->send(200, "application/json", "{'ok':1}");
});
// api
server.on("/sensors", HTTP_GET, [](AsyncWebServerRequest* request) {
if (!request->authenticate(CFG.user, CFG.pass) && !ST.isSafeMode())
return request->requestAuthentication();
String answer = "hostname:" + String(CFG.host);
answer += ";GATE1:" + String(ST.getState(0));
answer += ";GATE2:" + String(ST.getState(1));
answer += ";";
request->send(200, "text/html", answer);
});
// route roe all static files
server.serveStatic("/", LittleFS, "/").setCacheControl("max-age:6000000");
// if page not found
server.onNotFound([](AsyncWebServerRequest* request) {
request->send(404, "404: Not found");
});
}
Как видно, все запросы, которые выдают какие-то полезные данные, либо меняют состояние системы требуют авторизации. Авторизация требуется только в том случае, если не включен защищенный режим. Для хранения файлов я использовал файловую систему LittleFS.
По запросу '/set' выдается страница с настройками, при этом, если запрос типа 'POST', то осуществляется обработка принятых от клиента данных. При запросе файлов index.html и settings.html они прогоняются через простенький шаблонизатор, где переменные заменяются своими значениями.
Заголовочный файл класса работы с нагрузками - load_control.h
#ifndef LOAD_CONTROL_H
#define LOAD_CONTROL_H
#include <Arduino.h>
class LoadsControl {
public:
// init load control
void init();
// set on
void on(uint8_t num);
// set off
void off(uint8_t num);
// check
void control();
// get state load
inline bool isOn(uint8_t num) { return L[num].flag.state; }
private:
// one load
struct Load {
uint8_t pin; // pin load
uint32_t start; // start time
// flags for load
struct LoadFlag_t {
bool state : 1;
bool autoOff : 1;
} flag; // load's flags
};
// array loads
static Load L[];
// amount loads
uint8_t countL = 0;
};
extern LoadsControl LC;
enum loadNum_t {
L_NEAR, // 0
L_FAR, // 1
L_LED, // 2
};
#endif // LOAD_CONTROL_H
Собственно, видны методы инициализации нагрузок, включения и выключения нагрузки по номеру. Каждая нагрузка имеет пару флагов: состояние и нуждается ли она в автоматическом отключении.
Метод обработки нагрузок, вызываемый в loop - LoadsControl::control
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// control loads
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void LoadsControl::control() {
// auto off relay
for (uint8_t i = 0; i < countL; i++) {
if (L[i].flag.state && L[i].flag.autoOff && CFG.maxon &&
((ST.getTime() - L[i].start) > CFG.maxon)) {
off(i);
}
}
// switching the connection led
if (ST.isConnect() && !L[L_LED].flag.state) {
on(L_LED);
} else if (!ST.isConnect() && L[L_LED].flag.state) {
off(L_LED);
}
}
Здесь после заданного в конфигурации времени снимается напряжение с реле подключенного к приводу ворот. Также в этом методе в зависимости от состояния сети, включается и выключается светодиод выведенный на крышку устройства.
Заголовочный файл класса работы с кнопками - button_control.h
#ifndef BUTTON_CONTROL_H
#define BUTTON_CONTROL_H
#include <Arduino.h>
struct Button {
uint8_t pin;
bool state; // current state
uint32_t startChange; // time begin change
uint32_t timeProtect; // bounce protect timer
bool change;
};
class ButtonControl {
private:
uint8_t countB;
static Button B[];
public:
void init();
void check();
void logic();
inline uint8_t getCount() { return countB; }
inline bool isChangeDown(uint8_t num) {
return B[num].change && !B[num].state;
}
inline bool isChangeUp(uint8_t num) {
return B[num].change && B[num].state;
}
};
enum buttonNum_t {
BT_FIRST, // 0
BT_SECOND, // 1
BT_FIRST_SENS, // 2
BT_SECOND_SENS, // 3
};
#endif // BUTTON_CONTROL_H
Метод check проверяет наличие изменений состояния кнопок, а метод logic — реализует логику в зависимости от этих изменений. Про работу с кнопками я как то писал тут.
Я кратко описал основные моменты работы программы на серверной стороне. Теперь про клиент.
Для того, чтобы интерфейс выглядел более-менее нормально и в то же время не занимал много места и работал быстро, я взял набор стилей PureCSS. Обработку логики на стороне клиента реализовал на чистом javascript, без использования сторонних библиотек.
Основной файлик интерфейса нашей системы - index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Управление воротами</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
</head>
<body>
<div class="pure-menu pure-menu-horizontal">
<ul class="pure-menu-list">
<li class="pure-menu-item pure-menu-selected">
<a href="/" class="pure-menu-link">Управление</a>
</li>
<li class="pure-menu-item">
<a href="/set" class="pure-menu-link">Настройки</a>
</li>
</ul>
</div>
<div class="pure-g">
<div class="pure-u-2-3">
<div>
<h1>СОСТОЯНИЕ ВОРОТ</h1>
<table class="pure-table pure-table-bordered">
<tbody>
<tr>
<td>УЛИЦА</td>
<td id="gate1" style="background-color:%BGCOLOR1%;">%GATE1%</td>
</tr>
<tr>
<td>ГАРАЖ</td>
<td id="gate2" style="background-color:%BGCOLOR2%;">%GATE2%</td>
</tr>
</tbody>
</table>
<h1>УПРАВЛЕНИЕ ВОРОТАМИ</h1>
<p>
<button name="button1" class="pure-button pure-button-primary button-xlarge">УЛИЦА</button>
</p>
<p>
<button name="button2" class="pure-button pure-button-primary button-xlarge">ГАРАЖ</button>
</p>
</div>
</div>
</div>
</body>
<script>
const buttons = document.querySelectorAll(".pure-button");
buttons.forEach(function (bt) {
bt.addEventListener('click', function (event) {
bt.classList.remove("pure-button-primary");
fetch('/' + bt.name).then(function (response) {
if (response.status == 200) {
setTimeout(() => {
bt.classList.add("pure-button-primary");
}, 800);
} else {
alert("Error HTTP: " + response.status);
}
})
.catch(function (err) {
alert("Error network: " + err);
});
})
})
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', (e) => {
console.log("Events Connected");
}, false);
source.addEventListener('error', (e) => {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('change', (e) => {
console.log(e.data);
const data = JSON.parse(e.data);
const { num, state } = data;
const id = 'gate' + num;
document.getElementById(id).innerHTML = state ? 'ОТКРЫТО' : 'ЗАКРЫТО';
document.getElementById(id).style.backgroundColor = state ? '#F08080' : '#98FB98';
}, false);
}
</script>
</html>
У нас имеется небольшое верхнее меню состоящие из 2-х пунктов: Управление и Настройки, выбранный пункт меню подсвечивается. Далее идет небольшая табличка с текущим состоянием ворот. Данный файл, как и файл настроек на стороне сервера обрабатывается простеньким шаблонизатором, где на место %VAR% подставляется значение VAR. В данном файле подставляется состояние ворот и цвет состояний, для удобного визуального контроля. Так сказазать «Server-Side Rendering». После этого расположены две кнопки изменения состояния ворот.
Клиентская часть кода, как уже писал ранее написана на javascript. В данном файле всем кнопкам с классом ".pure-button" ставится обработчик нажатия, по нажатию на сервер с помощью функции fetch уходит запрос с именем кнопки. Во время нажатия кнопка меняет свой цвет, чтобы визуально понять что произошло нажатие. При получении ответа с кодом 200 исходный цвет кнопки восстанавливается через 800мс (иначе трудно уловить, что что-то произошло.
Далее идет код обработки реалтайм сообщений от сервера, с которым поддерживается связь все время пока страница открыта. При поступлении соответствующих сообщений текст состояния ворот и цвет ячейки состояния ворот меняются.
Интерфейс настройки устройства - settings.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Управление воротами</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
</head>
<body>
<div class="pure-menu pure-menu-horizontal">
<ul class="pure-menu-list">
<li class="pure-menu-item">
<a href="/" class="pure-menu-link">Управление</a>
</li>
<li class="pure-menu-item pure-menu-selected">
<a href="/set" class="pure-menu-link">Настройки</a>
</li>
</ul>
</div>
<div class="pure-g">
<div class="pure-u-2-3">
<div>
<h1>STATE</h1>
<table class="pure-table pure-table-bordered">
<tbody>
<tr>
<td>Safe mode</td>
<td>%SAFEMODE%</td>
</tr>
<tr>
<td>RSSI</td>
<td>%RSSI% dBm</td>
</tr>
<tr>
<td>Free memory</td>
<td>%MEM% B</td>
</tr>
<tr>
<td>Voltage</td>
<td>%VOLTAGE% mV</td>
</tr>
<tr>
<td>Uptime</td>
<td>%UPTIME%</td>
</tr>
</tbody>
</table>
<h1>SETTINGS</h1>
<form class="pure-form pure-form-aligned" method="post" action="/set" onsubmit="return validate()">
<fieldset>
<legend>Base</legend>
<div class="pure-control-group">
<label for="host">Host</label>
<input name="host" type="text" id="host" placeholder="Host name" value="%HOST%">
</div>
<legend>HTTP basic authentication</legend>
<div class="pure-control-group">
<label for="name">Username</label>
<input name="name" type="text" id="name" placeholder="Username" value="%USER%">
</div>
<div class="pure-control-group">
<label for="password">Password</label>
<input name="password" type="password" id="password" placeholder="Password" value="%PASSWORD%">
<button class="pure-button toggle-button" type="button">show</button>
</div>
<legend>WIFI settings</legend>
<div class="pure-control-group">
<label for="wifimode">WIFI mode</label>
<select name="wifimode" id="wifimode">
<option %APSELECTED% value="ap">AP</option>
<option %STSELECTED% value="station">STATION</option>
</select>
</div>
<div class="pure-control-group">
<label for="ssid">SSID</label>
<input name="ssid" type="text" id="ssid" placeholder="SSID" value="%SSID%">
</div>
<div class="pure-control-group">
<label for="ssidpass">WIFI-Password</label>
<input name="ssidpass" type="password" id="ssidpass" placeholder="WIFI password" value="%SSIDPASS%">
<button class="pure-button toggle-button" type="button">show</button>
</div>
<div class="pure-control-group">
<label for="ipmode">IP mode</label>
<select name="ipmode" id="ipmode">
<option %DHCPSELECTED% value="dhcp">DHCP</option>
<option %FIXSELECTED% value="static">STATIC</option>
</select>
</div>
<div class="pure-control-group">
<label for="ip">IP</label>
<input name="ip" type="text" id="ip" placeholder="xxx.xxx.xxx.xxx" value="%IP%" class="ipaddress">
</div>
<div class="pure-control-group">
<label for="gateway">Gateway</label>
<input name="gateway" type="text" id="gateway" placeholder="xxx.xxx.xxx.xxx" value="%GATEWAY%"
class="ipaddress">
</div>
<div class="pure-control-group">
<label for="subnet">Subnet</label>
<input name="subnet" type="text" id="subnet" placeholder="xxx.xxx.xxx.xxx" value="%SUBNET%"
class="ipaddress">
</div>
<div class="pure-control-group">
<label for="primaryDNS">Primary DNS</label>
<input name="primaryDNS" type="text" id="primaryDNS" placeholder="xxx.xxx.xxx.xxx" value="%PRIMARYDNS%"
class="ipaddress">
</div>
<div class="pure-control-group">
<label for="secondaryDNS">Secondary DNS</label>
<input name="secondaryDNS" type="text" id="secondaryDNS" placeholder="xxx.xxx.xxx.xxx"
value="%SECONDARYDNS%" class="ipaddress">
</div>
<legend>Device settings</legend>
<div class="pure-control-group">
<label for="maxon">On time</label>
<input name="maxon" type="text" id="maxon" placeholder="On time" value="%MAXON%">
</div>
<div class="pure-controls">
<button type="submit" class="pure-button pure-button-primary">Save</button>
</div>
</fieldset>
</form>
</div>
<p>
<a href="/reset">reset</a>
</p>
</div>
</div>
<script>
const toggleButtons = document.querySelectorAll(".toggle-button");
toggleButtons.forEach(function (tb) {
tb.addEventListener('click', function () {
if (this.previousElementSibling.type === 'password') {
this.previousElementSibling.type = 'text';
this.textContent = 'hide';
} else if (tb.previousElementSibling.type === 'text') {
this.previousElementSibling.type = 'password';
this.textContent = 'show';
}
})
})
function validate() {
const ips = document.querySelectorAll(".ipaddress");
const ipformat = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
let retval = true;
ips.forEach(function (ip) {
if (!ip.value.match(ipformat)) {
ip.classList.add("invalid");
retval = false;
ip.onfocus = function () {
if (this.classList.contains('invalid')) {
this.classList.remove('invalid');
}
}
}
})
return retval;
}
</script>
</body>
</html>
Страница настроек содержит то же меню что и страница управления. За меню следуют текущие свойства устройства, такие как:
— включен ли защищенный режим
— уровень WIFI сигнала
— количество свободной памяти
— напряжение питания
— время непрерывной работы устройства.
Далее идет форма, позволяющая изменять настройки устройства:
— имя хоста
— имя пользователя для авторизации на странице
— пароль пользователя для авторизации на странице
— режим WIFI (либо точка доступа, либо подключение к существующей точке)
— имя точки доступа к которой будет подключение, либо которая будет создана, зависит от режима
— пароль точки доступа к которой будет подключение, либо которая будет создана, зависит от режима
— режим IP адреса (статический или динамический)
— сетевые настройки
— время включенного состояния реле (определяет через сколько миллисекунд реле на плате будет выключено и контакты на приводе ворот разомкнуться).
Рядом с полями паролей расположена кнопка скрытия/показа паролей, нехитрый программный код на javascript реализует данное поведение. Помимо этого, на javascript реализуется валидация ip адресов формы, так как появление там некорректных данных может потребовать лишних телодвижений.
Вид интерфейса
Так выглядит основная страница управления:
Так выглядит страница с настройками:
Здесь можно посмотреть как интерфейс реагирует на изменение состояния ворот:
На видео виден механизм Server-Sent Events (SSE) в действии, когда браузер поддерживает открытым соединение с сервером и мгновенно реагирует на присылаемую сервером информацию.
Корпус
Многие из вас отлично понимают, что устройство без корпуса, это полуфабрикат. Нужно сделать корпус максимально подходящий нашему устройству, с отверстиями в нужных местах. Я считаю, что вполне разумно прибегнуть к 3Д-печати. Рисуем модель нижней части корпуса. Предусмотрев отверстия для ввода проводов, крепежные отверстия и стойки для нашей платы. В одной части корпуса будет располагаться наше устройство, в другой уложенные провода. Результат вот такой:
Чтобы сделать крышечку, нужно отступить 0.2 мм, чтобы она плотно покрывала
наше устройство. В крышечке предусматриваем отверстие для светодиода индикации состояния сети:
Результат печати:
Помимо корпуса для самого устройства, требуются корпуса для герконов и механика крепления магнитика к воротам. Так как корпус геркона требуется крепить к криволинейной направляющей ворот, то прибегнем к моделированию используя пластилин:
Заодно сделал примерный слепок ответной части, которую будем крепить к естественно торчащему болту конструкции ворот. Слепки выглядят так:
Чтобы иметь возможность регулировки расстояния от магнита до геркона предусмотрим крепежное отверстие в виде овала.
Далее по точкам, с помощью сплайна нетрудно воспроизвести криволинейную поверхность:
Как можно видеть, с обратной стороны крепежа геркона я выдавил углубление под сам геркон и под провода для его подключения.
Запускаем печать:
Результат:
Сравниваем кривизну со слепком и убеждаемся, что все норм:
Монтаж
Приводы ворот закреплены на потолке. Кроме приводов имеются уже смонтированные датчики препятствия в проеме ворот, соединенные витой парой. Задействованы только 4 проводка в сетевом кабеле. Чтобы не тянуть лишние провода, достаточно присоединиться к существующим свободным парам. Всего для каждых ворот нам требуется 4 провода: два провода будут замыкать нужные контакты привода и два провода пойдут к выключателю. Кинем еще одну витую пару от одного привода к нашему устройству, для управления вторым приводом расположим наше устройство в непосредственной близости от датчика.
Привод на картинке:
Вот так выглядит привод уличных ворот без крышки:
Вот так ближе интересующее нас место:
Колодка управления воротами:
Согласно документации, нам требуется к реле подключать выводы 2 и 7 колодки привода.
Монтируем выключатель данного привода, оставляя провод для геркона:
Выключатель используем кнопочного типа, без фиксации (часто используются для звонков). Теперь подойдя к воротам, можно легко их открыть нажатием кнопки, уходя, достаточно нажать кнопку с улицы и ворота закроются.
Возле нижних ворот монтируем наше устройство и два выключателя. Провода подходят к устройству сквозь стену. Ближний к воротам выключатель позволит управлять нижними воротами, второй дублирует верхний выключатель, позволяя, например, проветрить не поднимаясь наверх и не прибегая к пульту.
Как и было задумано, крышечка хорошо закрыла устройство вместе с проводами коммутации, по светодиоду видно что подключение к сети произошло и все норм:
На состояние стен не обращайте внимание, в гараже предстоит ремонт.
Монтаж осуществлен (осталось только приклеить герконы в корпусах и ответные магнитики), ворота управляются, как и задумано. Далее можно через VPN вынести интерфейс наружу, либо реализовать в какой-то системе (типа OpenHub) управление, благо api есть, или дописать взаимодействие по mqtt. В крайнем случае можно просто пробросить порт из внутренней сети наружу. По моему, получилось вполне полезное устройство, которое облегчит взаимодействие с воротами своему владельцу.
На этом заканчиваю. Спасибо тем кто дочитал до конца, надеюсь кому-то информация окажется полезной!
Самые обсуждаемые обзоры
+77 |
3911
147
|
+57 |
4090
73
|
К ТЗ есть вопросы. И мелкие спорные моменты по конструкции. К примеру, индикация срабатывания реле нафиг не нужна в итоговом изделии, а для отладки положить на плату пару деталек не проблема. В качестве итоговой индикации я бы использовал звуковое оповещение, огонек не всегда информативен. Ну или более заметный проблесковый.
ЗЫ. Вроде паста нормальная, но на фото припой не очень блестит.
Может ракурс фото не очень, паста блестит.
А недоступность модуля лучше мониторить внешними средствами. И оперативно информировать пользователя.
И про какой правильный припой вы говорите?
Но если применить безотмывоочный флюс и припой и не мыть. Все будет блестеть.
1. Вариант вайфай реле с поддержкой чего-то типа Tuya делает не то же самое?
2. «VT1, VT2 — биполярные транзисторы 2n2222, нужны для управления реле» а для чего так? Я не спец в радиоэлектронике, просто любопытно. Что такое и для чего подтягивающие резисторы я понимаю, а это вот зачем?
1 если сделать прошивку под эти модули (нам требуется на короткое время включить реле и выключить) то будет похоже (в том варианте что продается не подходит), но там придется повозиться с выводом gpio и их подтяжкой, реле там большое силовое и одно, а здесь двое ворот ну и еще ряд моментов
2 во первых реле 5 вольтовые, во вторых ток удержания реле превышает возможности контроллера
Этот геморрой с выдумыванием велосипеда нужен только когда это гараж с большим количеством людей.
Подключал свет, секционные ворота и блокировку секционных ворот. То есть чтобы открыть ворота надо включить блокировку и запустить открытие в режиме кнопки. Настроил Алису она вообще по сценарию все сама делает и блокировку снимает и ворота открывает и свет выключает. Блокировку сделал на всякий пожарный чтобы случайно не открылись.
датчики реального положения ворот и калитки вы как реализовали?
второй момент — там реле силовые — они имеют ограничения на ток как снизу так и сверху… и им может стать нехорошо от отсутствия нагрузки через время.
ну и это менее компактно, к тому же без своей прошивки зависит от облака вендора и без интернета совсем не будет работать
пример, снег попал под ворота или ледышка отвалилась от машины, вы закрыли — ворота отработали — нашли препятствие и открылись — вы никак не узнаете о проблеме, а итог — при теплом гараже может быть очень печальным.
Знать текущее состояние ворот всегда полезно. Конкретно в этих воротах из обзора имеются еще оптические датчики препятствия в проеме ворот, которые останавливают ворота, не имея альтернативной обратной связи выяснить реальное состояние ворот — не тривиальная задача.
А причем тут блок на 433? во первых он не поддерживает переменный код, во вторых добавит еще ряд проблем…
А электричество тут не причем, это отдельный вопрос
Но еще раз напишу — делайте как хотите, я показал как в этом месте сделал я и не утверждаю, что решение подойдет всем. Для меня оно в этой ситуации наиболее удобно
Но почти уверен что повторяемости не будет.
Это же всё надо делать или на работе или убивать выходные на это дело.
Или вообще быть безработным.
Какой смысл в этом.
Все блоки готовые продаются.
Осталось только собрать и потратить на это день.
А так конечно молодец
1 — к вам приехала доставка, вас нет — открыли ворота (хоть из другого города) доставка выложила все — сам многократно этим пользовался
2 — пришли например строители (гости, родственники и тп), в ваше отсутствие — вы им открыли ворота — они делают работу, не нужно им давать брелок
3 — брелок занимает место в кармане и нужно следить за батарейками — пошли вы в магазин — взяли телефон, в нем уже есть ключ от ворот
и так можно продолжать довольно долго, к тому же брелок то не перестал работать, появились альтернативы
пс п.1 и 2 у меня по старинке: дети или супруга)))
Дети и супруга дороже описанного решения :) к тому же и они могут отлучиться
У всех по разному — мне удобнее так.
Брелок у меня лежит в машине — там быстрее на нем, а в остальных случаях предпочту достать телефон. Телефон то вы все-равно берете.
Впрочем я никого ни к чему не призываю, кому то удобнее так, кому то иначе. Я только показал как сделал и мне это подходит.
Я такое фиг соберу ))) хотя пригодилось бы тоже.
я правильно понимаю, что теперь можно и со смартфона управлять воротами?
И про выключатели не очень понял: выключателем внутри можно и открыть и закрыть, а выключателем снаружи только закрыть?
Внутри первых ворот можно их открыть и закрыть, но выйдя на улицу при открытых воротах можно нажать на выключатель и закрыть
Внутри вторых открыть и закрыть можно как первые так и вторые ворота
Конечно, со смартфона можно управлять и динамически видеть состояние ворот — для этого все и затевалось
Спасибо за инфу. Пощупаю.
Как минимум в качестве пруфа можно найти даташит на ЕХ от 2015го года
cdn-shop.adafruit.com/product-files/2471/0A-ESP8266__Datasheet__EN_v4.3.pdf
И он уже версии 4.3 к тому времени был. Значит ЕХ шли ещё раньше.
И уже тогда был аппаратный вотчдог:
When in sleep mode, only the calibrated real-time clock and watchdog remains active
Как только в проекте или i2c или SSL или оба, то каждые 3..4 месяца глюки и перезагрузка.
Для него все закончилось хорошо, даже не сильно били.
за прямые руки 5.
пы.сы. сам разочаровался в esp8266 и потихоньку перевожу проекты на esp32.
Пы.сы. Я не критикую, просто спрашиваю тк постоянно учусь на чужом коде. Вот и интересно стало почему у вас так.
Да и масштабировать проще, объявив несколько экземпляров.
Хотя я и для одного экземпляра всовываю все в класс. Если критично (время исполнения / размер кода) — делаю все члены статичными.
так переменные тоже можно спрятать, просто объявив их не в заголовке *.h, а в *.cpp. функцию тоже не проблема — объявив ее static.
поясните пожалуйста, не понял что вы имели ввиду?
Всякие там потоки, работу с файлами можно пропускать. Наследование, виртуальные функции можно отложить на потом.
Я начал переползать на С++ в микроконтроллерах в тот момент, когда понял что на С «переизобретаю» С++. Кстати, переход с ассемблера на С происходил аналогичным образом.
уличные устройства лучше делать с опторазвязкой, хотя они тоже не 100% помогают
Собираюсь делать такое де для шлагбаума, прочитал про герконы, надо видимо тоже добавить, но выходит что магнит на палке будет, в геркон внутри ящика где привод и управление. Так вот вопрос — будет работать геркон через металлический корпус?
Вот что по этому поводу пишут профи на профильном сайте:
Хорошо хоть есть возможность проверки на необходимость перегрузки, а не просто «раз в N часов».
Но проблема жесткой привязи к своему облаку есть, и она везде. Америку тут пинать нет резона- в случае чего, ваша Алиса превратится в такую же тыкву, как и наша Алекса.
Ясно, понятно. Штош, так и запишем.
В принципе это ожидаемо. Если облако компании сдохнет, то 90% пользователей все равно не стали бы заморачиваться с локальным интерфейсом. К тому же дохлое облако скорее всего означает смерть всей фирмы, и судьба девайсов уже никого не волнует. Я стараюсь не покупать зависящее от облака железо, но обычно такого просто нет в природе.
www.theverge.com/23949612/chamberlain-myq-smart-garage-door-controller-homebridge-integrations
Боюсь, что дальше будет только хуже, начнут требовать подписку даже за базовые функции.
Вы можете ругаться, но есть ли приемлемая альтернатива?
И как всегда там будет целый букет «нюансов», здесь же решение которое четко соответствует поставленной задаче и легко модифицируется под изменяемые требования. Себестоимость его, думаю, сами можете прикинуть
Ищите по ключевым словам Wifi RF converter
Себестоимость ~20$ Нюансов не обнаружил. Работает локально и через интернет.
Вот и прикиньте по вашим затратам) Но плюс есть — ваше решение это и хобби, и решение проблемы)
по сути в приложение перетащит брелок — ни обратной связи ни кнопок… ничего
останется открытый эфир…
это мимо
Кнопки просто дублируются дешёвыми переключателями с поддержкой Tuya. Но их я не делал, так как не видел для себя обходимости. Мне проще голосом)
а можно решить что нужно этим устройством
сами же знаете недостатки всего этого
— это оно?
Я так умею. Но не умею именно от камеры статус получать. Хочу научиться. Или камера какая-то специальная нужна? Мои вводные: frigate, home assistant, камеры реолинк. Направьте, пожалуйста, в какую сторону мне читать?
Как этот сигнал будет обрабатываться камерой — другой вопрос. Можно поставить на ворота нормально закрытый геркон в крайнем нижнем положении ворот. Все остальные положения, кроме полностью закрытых ворот, будут считаться тревогой
Но камера определяла просто движение во всём кадре или на участке кадра.
Статус «процент открытия ворот» оно не выдаст.
Но статус «закрыто»\«не закрыто» камера отдаст
Цена от 4 до 8 долларов за шт, есть и на 4,8,16 каналов. Свои приложения и агрегация с Алисой, Алехой, самсунг, и тп
А вот у меня почему-то возник вопрос совершенно не в тему, глядя на фото гаража — а это у Вас гараж теплый? Если теплый, то почему крыша только жестяным профлистом защищает от окружающей среды?
теплый сам гараж, а показан привод тамбура, см первый рисунок
Будь мне нужно что-то такое — с удовольствием бы вам понес тенге)
Выше же выкладывали ссылки на готовые устройства. Купите Sonoff, прошейте Tasmota, настройте. Нужно больше GPIO (например, для датчиков) — придётся браться за паяльник.
Ну, или купите готовую плату ESP32, плату с реле, соедините проводочками… И дерзайте! :)
Я со всякими Ардуинами, еспшками и пиками как-то не взлетел..)
который задается в интерфейсе
Но зачастую достаточно парооль и доступ из внутренней сети, из вне впн.
С брелоками вон вообще открытым текстом все в эфир
а брелоки разные бывают.
Когда сравниваю свое решение с вашим, у меня текут слезы. Понижайка, блок реле, esp8266 — завернуты в пупырку(иначе висло в сильные морозы) и втиснуты в стандартную монтажную коробку 80*80. Зашита тасмота, управляется из Home Assistant. Соседи (ворота общие на въезд) задолбали просьбами поработать швейцаром — прикрутил им открытие из телеги(обратная связь сообщением о событии и/или кнопка снапшота с камеры). Вроде бы и работает все это «и так сойдёт» несколько лет, но такие вот посты заставляют задуматься, что я где-то свернул не туда.
Сейчас стоит примерно такая же задача — наружные ворота + две гаражные двери (через гараж можно войти в дом, то есть безопасность критична).
Планирую всё управление сделать исключительно по knx, и радиоканал тоже гонять через knx, купил контроллер с rolling code, завязал ради пробы с homelink в машине — отлично работает, надо теперь будет просто подключить к бинарным входам.
Ну а из полезного что пока сделал — вместо блокировочной перемычки на гаражных воротах воткнул реле, которые отключают возможность открытия ворот если включена сигнализация (у меня ring с z-wave, так что это было достаточно просто).
Летом вообще бесплатно всё, даже доплачивают за электричество сданное в сеть (не смотрел сколько начислили, если честно), зимой платим треть от того что должны были бы.
Не, ничего не меняли и не переделывали, в этом году много других забот было, к сожалению.
Давеча четырёхзначный счёт за воду пришёл за несколько месяцев. Оказывается дюймовую стальную трубу, проложенную к дому почти 60 лет назад, где-то прорвало. Кинули по верху времянку пока, но до декабря надо бы полноценно закопать, а то придут минусовые температуры и всё.
Ну и можно запросить от компании-поставщика воды чтобы списали эти потери, потому что никто не заметил и это неумышленная трата, но вначале надо оплатить всё и починить всё.
Ну и чот весь год в таком духе.