Я очень люблю ардуино и подобные штуки. Но редко когда удаётся по-настоящему что-то такое применить на практике. Ну, потому что, обычно придумать что-то новое и полезное в хозяйстве, чего ещё нет – это очень сложно. А то, что уже придумано и существует проще купить, особенно в век маркетплейсов. Но вот, наконец, настал тот редкий случай, когда получилось сделать простую, но полезную штуку. Но как всегда, когда делаешь что-то простое, обязательно всплывает куча всяких неочевидных нюансов и в итоге всё кратно усложняется, чем думалось в голове.
У меня есть домашний сервер: git, nextcloud и т.п. Изначально и последние несколько лет он существовал в виде Raspberry Pi, с подключёнными к ней внешними жёсткими дисками. А ещё иногда случаются небольшие перебои с электричеством. Поэтому первым делом сервер был запитан от источника бесперебойного питания (ИБП). Но всегда существовала проблема: когда электричество пропадает, то сервер продолжает работать. Мало того, что в этом нет смысла, т.к. без электричества нет и интернета, так ещё, если свет дать не успеют, то ИБП высаживается в ноль и сервер жёстко завершит свою работу, что однажды привело к тому, что он больше не загрузился. Поэтому требовалось придумать как завершить работу сервера на время отключения электричества. Первая мысль, конечно, это подключить бесперебойник к компьютеру через USB. Но, к сожалению, из этого ничего не вышло. Я провозился пару вечеров, но так и не смог заставить Raspberry Pi управлять ИБП по USB ни через NUT ни как-то иначе. В итоге настало время хардового решения. Все файлы проекта можно скачать из репозитория на Github
Смысл очень простой. Состояние электросети отслеживает микроконтроллер в виде ардуино. Когда электричество отключают, то ардуино подаёт команду серверу. Тот спокойно завершает свою работу, после этого ардуино при помощи сервомашинки нажимает на кнопку на ИБП и тот вырубается. Когда электричество снова появляется, то ардуино снова нажимает на кнопку и малинка включается. Если электричество вернут в процессе отключения сервера, то кнопку потребуется нажать дважды, чтобы снова его включить. Ожидая появления электричества, ардуино будет работать от батареек. В результате появилась следующая схема прибора (кликабельно):
Мозгом устройства является Arduino Pro Mini. Для переключения источника питания устройства применяется пятивольтовое реле. Я взял самое маленькое маленькое реле, что у меня было HK4100F. Его катушка потребляет примерно 40 мА. Диод D1 служит для защиты от обратного тока при переключении катушки. При подаче питания на катушку устройство питается от внешнего источника. Катушка сразу подключена к внешнему питанию, поэтому как только оно появляется, то прибор сразу переходит на него.
При отключении питания катушка отключается и реле переключает питание на батарейное. Я использую 4 батарейки AA, что в сумме даёт напряжение 6В, поэтому требуется уменьшить его до 5 вольт. Для этого используется линейный 5В преобразователь L79L05 в корпусе TO92. Этот преобразователь в холостом режиме потребляет примерно 2,5 мА, поэтому будет сажать батарейки даже если устройство питается от внешнего питания. Чтобы этого не было, предусмотрен транзистор Q1, который полностью размыкает цепь связанную с батарейками, когда они не должны использоваться и открывается при переходе на батарейное питание при помощи сигнала от ардуино. О наличии внешнего питания ардуино судит по сигналу PWR_STAT. Ещё для экономии батареек с ардуины удалены светодиод - индикатор питания ардуино и преобразователь напряжения, который в данном проекте не используется, но потребляет ток. Каждый раз при переключении на батарейки микроконтроллер проверяет напряжение питания. И если оно будет ниже допустимого, то выдаст сообщение об ошибке путём моргания оставшегося светодиода (13-й пин). Проверка напряжения реализована путём переключения АЦП микроконтроллера на измерение внутреннего опорного напряжение 1,1 вольта. При этом опорное напряжение самого АЦП остаётся как обычно - напряжение питания. Поэтому результат измерения 1,1 вольта будет зависеть от напряжения питания.
Чтобы ардуинка не выключалась в момент переключения питания предусмотрен конденсатор C2 ёмкостью 3000 мкФ. Но одного конденсатора недостаточно, т.к. из схемы видно, что при отключении внешнего питания, катушка реле продолжит питаться уже от конденсатора и питание быстро не переключится. Чтобы этого избежать установлен диод Шоттки. Он съедает примерно 0,2 вольта входного напряжения, поэтому на ардуино приходит примерно 4,8 вольта.
Сервомашинка питается только от батареек, поэтому никак не нагружает блок питания сервера. Также, чтобы электроника сервомашинки не сажала батарейки в простое, предусмотрен транзистор Q2, который открывается ардуиной только на время её работы. Я выбрал, наверно, самую распространённую 9-граммовую серву, которую кладут в разные наборы начинающих ардуинщиков. Её я просто приклеил к ИБП на толстый двухсторонний скотч. Для работы с сервой используется стандартная для ардуино библиотека Servo. Для настройки угла поворота, достаточного для нажимания кнопки, я сделал небольшую тестовую прошивку, которую можно залить в удобную для тестрования ардуино (Arduino UNO).
#define LED 13
#define SERVO_CONTROL 4
#include <Servo.h>
Servo _srv;
void setup()
{
pinMode(LED, OUTPUT);
pinMode(SERVO_CONTROL, OUTPUT);
_srv.attach(SERVO_CONTROL);
Serial.begin(9600);
_srv.write(0);
}
int _angle = 0;
void loop()
{
if (Serial.available())
{
_angle = Serial.readString().toInt();
Serial.println(_angle);
_srv.write(_angle);
delay(500);
_srv.write(0);
}
}
В сериал мониторе нужно вводить градус поворота. Сервомашинка будет на этот угол поворачиваться, а затем возвращаться в нулевое положение. Нужно добиться, чтобы кнопка успешно нажималась и отжималась и сделать на градус два больше для запаса.
Ардуино общается с сервером при помощи GPIO. Для сервера на Raspberry Pi - это не вызывает никаких проблем, т.к. у малинки есть GPIO входы и выходы. Но недавно я перешёл на мини ПК без GPIO. В этом случае потребуется использовать ещё одну ардуино. Но об этом позже. Всего используется два GPIO контакта: COMP_OFF и COMP_STAT.
- COMP_OFF - выходной сигнал ардуино (5-й pin). При высоком уровне на нём сервер получает сигнал о необходимости выключится. Т.к. Raspberry Pi использует 3-х вольтовую логику, то COMP_OFF преобразуется во входной сигнал COMP_GPIO_PWR_OFF для Raspberry Pi (pin 27) напряжением чуть меньше 3,3 вольта. Для этого используется делитель, в верхнем плече которого резистор на 56 кОм, а в нижнем - на 100 кОм.
- COMP_STAT - это входной сигнал ардуино (6-й pin) с внутренней подтяжкой к высокому уровню. Когда сервер включается, он должен установить на этот контакт низкий уровень. Так ардуино поймёт, что сервер работает, т.к. когда Raspberry Pi отключается нулевой уровень на GPIO пропадает. COMP_STAT переходит в выходной для Raspberry Pi контакт COMP_GPIO_STAT (pin 22). На всякий случай связь сделана через резистор в 1 кОм.
Ну и конечно прибор должен получать информацию о наличии напряжения в розетке, чтобы понять питаемся мы от бесперибойника или всё нормально: напряжение в сети есть. Для этого был изобретён 😄 самодельный индикатор напряжения в сети в виде обычной неоновой лампочки NE-2 (такая обычно ставится в выключатели, в кнопки удлинителей и т.п.) и фоторезистора. Лампочка питается от сети 230 вольт через резистор 470 кОм. При меньшем сопротивлении яркость лампочки на глаз не меняется, но нагрев резистора становится больше. При 1 МОм яркость заметно снижается. Поэтому 470 кОм вроде как оптимальный вариант, в этом случае на резисторе рассеивается всего 64 мВт. Вообще я провёл измерения тока для нескольких конкретных резисторов, ниже эти данные в таблице, там же представлено расчётное значение выделяемой на резисторе мощности. Видно, например, что четвертьваттный резистор на 150 кОм будет работать на пределе своей мощности.
I (мА) | R (кОм) | P (мВт) |
---|---|---|
1,17 | 149,5 | 204,7 |
0,98 | 178,1 | 171,0 |
0,37 | 470,3 | 64,4 |
0,18 | 973,4 | 31,5 |
Всё это дело обматывается изолентой и потом алюминиевой фольгой, чтобы надёжно убрать внешний свет.
Но потом, я решил переделать эту часть. Сделать понадёжней и по красивей. Лампочка и фоторезистор помещаются в пластиковый корпус, изготовленный на 3D принтере. Их выводы помещаются в отдельные корпуса, которые служат для изоляции вывоводов. Ещё две детали служат для помещения в них подводящих проводов: 230В - к лампе и от прибора к фоторозеистору. Всего пять частей. После сборки все части склеиваются цианокрилатным клеем с содой. Моделировал в OpenScad. Исходный код и stl файлы также есть в репозитории.
Все оголённые выводы я дополнительно поместил в термоусадку:
Детектор в сборе:
Но какой бы толщины не были стенки, PETG пластик всё равно пропускает свет. Поэтому, чтобы всё работало, нужно обернуть полученное изделие в фольгу. Опять же для этого идеально подходит алюминиевый скотч.
В моём случае фоторезистор в полной темноте имеет сопротивление больше 200 МОм, а при освещение неоновой лампочкой - сопростивление падает примерно до 2 кОм. Сигнал LINE_STAT формируется из делителя напряжения, где фоторезистор входит в нижнее плечо. Верхнее плечо - резистор на 100 кОм. В этом случае сигнал LINE_STAT является инверсным: когда электричество есть на нём уровень около нуля, а при отключении сети: почти 5 вольт. Для того, чтобы убрать пульсации и случайные срабатывания ставится конденсатор C3. Мне попался на глаза на 22 мкФ. В этом случае детектор срабатывает аж почти через две секунды после отключения электричества в сети.
Серверная часть для Raspberry Pi написана на языке Tcl (файл watcher.tcl) и в работе потребляет всего единицы МБ оперативной памяти. Программа запускается автоматически при загрузке сервера. Обычно для управления GPIO Raspberry Pi разработчики используют распространённый, но уже устаревший метод через sysfs. Т.е. записывают значения выводов в специальные файлы, которые предоставляет операционная система. Этот метод используется даже в некоторых библиотеках по работе с GPIO. В тоже время, у работы через sysfs много недостатков, наверное главным из которых можно назвать не монопольный доступ к GPIO контактактам, т.е. любые процессы могут писать и читать в GPIO независимо друг от друга. Хоть этот и другие недостатки не очень существенны в рамках данного проекта (теоретически, конечно, может завестись на сервере программа, которая подаст высокий уровень сигнала на пин, отвечающий за выключение и сервер выключится), всё же я решил использовать более современный метод, а именно: управление GPIO через библиотеку libgpiod. Это C-библиотека, которую можно использовать в своих программах, но также и готовые утилиты для работы с GPIO. Именно последние я и использовал в своём коде. Для отслеживания входных сигналов применяется утилита gpiomon, а для установки выходных сигналов - gpioset. Одним из преимуществ такого подхода в управлении GPIO является то, что скрипт watcher.tcl имеет монопольный доступ к GPIO и пока его процесс не завершён, никакой другой процесс не может иметь доступа к используемым watcher.tcl GPIO контактактам, что положительно сказывается на безопасности. Библиотека libgpiod уже предустановлена в последних версиях Raspbian OS, но если нет, то её можно установить.
sudo apt install gpiod
В нашем случае входным контактом для малинки будет 27-ой пин, напомню, что высокий уровень на этом контакте означает, что сервер должен выключится. Выходным сигналом будет 22-й пин. При включении малинки, этот контакт должен подтягиваться к земле. Для мониторинга за 27-м пином используется утилита gpiomon:
gpiomon -b -B disable -F %e 0 27
- -b этот ключ необходим для считывания сторонней программой данных из потока стандартного вывода утилиты gpiomon.
- -В disable - указание, что на пине не используется подтяжка.
- -F %e - вместо подробной информации в поток стандартного вывода, будет выводится только “1” или “0” в зависимости от статуса пина.
- 0 - номер контроллера GPIO (обычно всегда 0).
- 27 - номер контакта GPIO
Функция для мониторинга пина на Tcl с использованием gpiomon
proc watch {handler pin} {
if {[catch {open [list | gpiomon -b -B disable -F %e 0 $pin 2>@stdout] r} progStream]} {
puts stderr "Could not open gpio$pin."
exit 2
}
fconfigure $progStream -blocking 0 -buffering none -translation lf -eofchar {}
fileevent $progStream readable [list $handler $progStream]
return $progStream
}
Здесь handler - имя функции, которая будет вызываться при изменении состояния пина, а pin - номер пина.
Для установки уровня на выходной контакт используем gpioset.
gpioset -m signal 0 22=0
- -m signal означает, что программа не будет завершаться до получения сигнала завершения (SIGTERM). Это необходимо, чтобы никакой другой процесс не смог установить значение для этого пина.
- 0 - номер контроллера GPIO (обычно всегда 0).
- 22=0 - установить низкий уровень на контакт 22.
Ну и код на Tcl для установки GPIO
proc write {pin value} {
if {[catch {open [list | gpioset -m signal 0 $pin=$value 2>@stdout] r} progStream]} {
puts stderr "Could not open gpio$pin: $progStream."
exit 2
}
fconfigure $progStream -blocking 0 -buffering none -translation lf -eofchar {}
fileevent $progStream readable [list writeHandler $progStream]
return $progStream
}
Построив всю систему для работы с Raspberry Pi, я вдруг решил за много лет поменять сервер. Вместо малинки теперь трудится мини ПК, который во много раз шустрее, и в то же время также бесшумный и по размеру примерно такой же. Один минус, в нём нет GPIO! Не беда: заменим GPIO на ещё одну ардуино. На этот раз Arduino nano или любую другую на микроконтроллере Atmega328 или 168, со встроенным usb портом. У меня, кстати, nano на Atmega168p. Серверная программа теперь получает команду на выключение через последовательный порт, а за обмен данными с основным устройством через GPIO отвечает новая Arduino. В git репозитории есть код для этой Arduino nano и серверной части (также на языке Tcl). Алгоритм прост: ардуинка всё время шлёт “a\r\n”. Когда серверная программа запускается и получает с последовательного порта эту команду, то она отсылает “+”. Ардуинка перестаёт слать “a\r\n” и уходит в глубокий сон (почти перестаёт потреблять энергию). Когда настаёт время выключить сервер, то ардуино просыпается и всё время шлёт “-\r\n”.
Но есть и проблема. Если раньше устройство питалось от малинки, то теперь выключенный мини ПК не подаёт питание на USB порты и не включается автоматически при подаче питания. Если последнее легко победить правкой настроек биоса, то с питанием придётся либо переписывать код основного устройства, ухудшая его характеристики, т.к. оно теперь не сможет отслеживать переключение бесперебойника. Либо просто разделить питание от блока питания мини ПК. Я выбрал последний вариант. Во первых, я заменил штатный блок питания (12 вольт, 2,5 ампера), т.к. он сильно грелся и явно не годился для круглосуточной работы. Я же поставил хороший на 5 ампер. В качестве понижающего преобразователя для питания ардуины я решил ничего не придумывать и взял готовый модуль. Теперь всё работает также как и Raspberry Pi. Устройство получилось в каком-то смысле универсальным.
Итак, для начала работы устройства, нажимаем кнопку reset на ардуино. Если всё нормально, то сначала сервомашинка издаст звук, означающий что устройство включилось и что качалка установлена на нулевой угол. Также, загорится светодиод - обозначающий, что ардуина включилась и пока ещё не ушла в сон. И через некоторое время он погаснет, обозначая, что микроконтроллер ушёл в глубокий сон. Всё. Мониторинг электросети включён. Если же после нажатия reset светодиод начал моргать, то значит нужно искать ошибку. При включении, микроконтроллер проверяет основные сигналы. В случае обнаружения ошибки, ардуино сообщит об этом путём мигания светодиода. Прежде всего устройство должно питаться от основного питания (не батарейки). В противном случае светодиод будет моргать раз в секунду. Эта же ошибка возникнет при сбоях при переключении бесперебойника, если он не включится или не выключится. Далее, проверяется наличие сетевого напряжения. В случае его отсутствия светодиод замигает 10 раз в секунду. Если всё нормально, устройство будет ждать включения сервера (пока не будет притянут к земле соответствующий контакт). Если в течении 3-х минут этого не произойдёт, то светодиод начнёт мигать 2 раза в секунду. И наконец, последняя ошибка - это низкий уровень батареек. Он проверяется при переключении на батарейное питание. В этом случае диод будет мигать 1 раз в 5 секунд. Во всех случаях мигание будет продолжаться 10 минут, потом ардуино уйдёт в сон.