Raspberry Pi + Pushover + Puppeteer = автоматизация повседневных задач

В данной статье расскажу, как я настроил автоматизацию обычных повседневных задач. Эти задачи варьируются от предварительной записи в прачечную до уведомлений о тренировках в местном тренажерном зале CrossFit.

Главное внимание уделим общей настройке, процессу разработки и добавления задач, при этом не будем углубляться в специфику автоматизации каждой задачи. В основе настройки: Raspberry Pi для выполнения скриптов задач, Pushover для отправки нативных уведомлений на телефон и Puppeteer для взаимодействия с веб-сайтами и извлечения с них данных.

Типы задач для автоматизации

В данном случае все автоматизируемые задачи делятся на 2 общие категории: запланированные и непрерывные

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

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

Непрерывные задачи более сложные, но при этом скучные. Они подразумевают постоянное проведение определенной проверки и выполнение действия по ее результатам. Именно этим мне пришлось заниматься, когда я сдавал на права. Возможность записаться на экзамен по вождению предоставлялась только через 3 месяца. Однако время от времени люди отменяли свои записи и появлялись новые даты, доступные для бронирования. Мне приходилось несколько раз в день мониторить сайт, чтобы выяснить, освободилось ли место на ближайшие пару недель. 

Общая настройка 

Для каждой задачи я написал скрипт, который выполняет всю необходимую логику. Как правило, пишу их, используя Node.js (простой JavaScript или TypeScript) и библиотеку Puppeteer. Этот фреймворк для автоматизации взаимодействий пользователя с веб-сайтами отлично подходит для решения поставленной задачи. 

Фактическая процедура написания каждого скрипта зависит от сайта, но общий принцип всегда один и тот же. Применяю инструменты разработчика в Chrome/Brave, вручную взаимодействую со страницей и выполняю задачу, подлежащую автоматизации. При каждом взаимодействии отслеживаю происходящее в DOM и переношу это в скрипт, задействуя соответствующие методы Puppeteer. В большинстве случаев сделать это не намного сложнее, чем найти нужный HTML-элемент для взаимодействия или просмотреть DOM в поисках определенной строки текста. Углубленное изучение принципов работы с Puppeteer для извлечения данных с сайтов выходит за рамки данной статьи.

Разработка скриптов для каждой задачи выполняется на MacBook, а запуск их в продакшн осуществляется с помощью старенького Raspberry Pi 3 2016 года. По современным меркам он уже немного устарел, но его вычислительной мощности хватает для выполнения требуемых задач. Готовый скрипт я переношу на Pi либо через scp, либо получаю доступ к Pi напрямую через SSH. После этого связываюсь с репозиторием GitHub, куда был отправлен скрипт с MacBook.

Настройка автоматизации скриптов 

Если скрипт соответствует запланированной задаче, тогда я планирую его с помощью cron: выполняю $ crontab -e, редактирую файл, в который добавляется новый скрипт, и указываю время и день выполнения. 

# Пример файла crontab для скрипта
# бронирования места в прачечной. В данном случае
# скрипт запускается в 21:05 каждое воскресенье посредством cron.

# m h dom mon dow command
5 21 * * 7 node /home/pi/bookLaundry.js

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

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

Рассмотрим наглядный пример. Ниже представлена упрощенная версия скрипта, проверяющего наличие новых доступных записей на экзамен по вождению: 

// `getAvailableAppointments()` возвращает
// массив записей.
let appointments = getAvailableAppointments();
while (true) {
delay(300); // Ожидает 5 минут
const currentAppointments = getAvailableAppointments();
// `setDiff(A, B)` возвращает разность множеств A-B
const newAppointments = setDiff(currentAppointments, appointments);

if (newAppointments.length > 0) {
notifyAboutNewAppointments(newAppointments);
}
appointments = currentAppointments;
}

Сохранение необходимого состояния между запусками посредством переменной appointments сильно упрощает большую часть требуемой логики. Нужно лишь получить текущее состояние и проверить разницу по сравнению с предыдущим. 

Как насчет обработки ошибок? Чаще всего заключаю всю логику в try-catch и отправляю сообщение об ошибке в виде уведомления. Не самый изящный способ, зато простой и действенный. 

Запускаю скрипты для непрерывных задач командой screen. $ screen -S <name-of-session> создает сеанс виртуального терминала, где выполняется скрипт. Выполнение одного из этих скриптов в виртуальном терминале с помощью screen позволяет отключить SSH-соединение с Pi и при этом продолжать работу запущенных процессов.

Чтобы остановить выполнение скрипта, нужно подключиться к Pi по SSH, выполнить команду $ screen -R <name-of-session> для присоединения к сеансу и отключить его сочетанием клавиш Ctrl+Z.

Создание сеанса, его отключение и возобновление. Отключение происходит нажатием ^Ad при подключении к сеансу

Отправка уведомлений на телефон с помощью Pushover

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

Какое-то время я рассматривал возможность использования электронной почты для доставки уведомлений: либо посредством curl, запускающего сервер SMTP для личного адреса Gmail, либо путем установки mailutils и применения соответствующей команды email.

Однако после тщательного изучения вопроса решил воспользоваться Pushover (предоставляется 30-дневный бесплатный пробный период и единовременная плата в 5$ при продолжении работы с сервисом). 

С настройкой все просто. Устанавливаем приложение Pushover на устройство iPhone или Android, которое служит клиентом, получающим отправляемые уведомления. После создания аккаунта на сайте Pushover регистрируем приложение

Создав приложение, выбираем имя и иконку, которые будут отображаться на экране телефона при получении уведомлений. После этого отправляем уведомления на телефон посредством простого запроса POST

const sendNotice = async (msg, prio = 0) => {
const data = {
message: msg,
token: PUSHOVER_TOKEN,
user: PUSHOVER_USER,
html: 1,
priority: prio,
};
const url = "https://api.pushover.net/1/messages.json"
await axios.post(url, data);
};

На самом базовом уровне потребуются только 3 параметра: токен приложения PUSHOVER_TOKEN, токен пользователя PUSHOVER_USER и отправляемое сообщение. В данном случае я включил дополнительные необязательные параметры: отправка сообщений в формате HTML и приоритет priority, определяющий способ отображения сообщения на клиенте. 

Теперь, когда потребуется отправить информацию, например об успешном или неудачном выполнении задачи, можно в любом скрипте использовать метод sendNotice. А поскольку сообщение отправляется в формате HTML, то можно включить активные гиперссылки и форматирование, а именно размер и цвет текста. 

Рассмотрим пример. Запускаем скрипт для получения содержания сегодняшней тренировки в зале Crossfit, и на телефон приходят следующие уведомления: 

Длительное нажатие на каждое уведомление открывает все сообщение целиком, что очень удобно. Как видно, я назвал приложение AutoButler и добавил иконку жилета для эффекта персонализации. 

Заключение 

Вы можете улучшить разные аспекты, чтобы добиться более надежной и изящной настройки. Представленный подход соответствует моим требованиям и предполагает минимальное сопровождение. Что касается общей стоимости, то она составляет 45$ (40$ за Raspberry Pi, 5$ за Pushover). Эксплуатационные расходы близки к нулю, так как Raspberry Pi потребляет очень мало электричества. 

При необходимости скорректировать расписание запуска скрипта приходится подключаться к Raspberry по SSH и выполнять команду crontab -e. Однако такая необходимость возникает нечасто, а если и возникает, то вопрос решается за пару минут. Весь процесс автоматизации новой задачи, от написания скрипта до передачи его в Raspberry Pi и “развертывания”, занимает около 30 минут (“плюс-минус” в зависимости от сложности базового сайта). Учитывая все выше сказанное, текущая настройка является для меня оптимальным вариантом. 

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Marko Cotra: How I Use a Raspberry Pi, Pushover, and Puppeteer to Streamline Mundane Tasks

Предыдущая статьяОптимизация ресурсов в Node.js
Следующая статьяРуководство по AWS: настройка экземпляра EC2 для развертывания приложений Java