Статьи

Самодельные Twitter и Gmail уведомления с PHP и Arduino

Эта статья была рецензирована Клаудио Рибейро . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!


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

Если только я не могу сделать и то и другое одновременно.

Еще с недели IoT я собирался работать над проектом, который даст мне знать, когда кто-то захочет со мной поговорить. Что-то, что выглядит круто, и в то же время неинвазивно. Это то, что я придумал …

Логотип Arduino

Большая часть этого кода может быть найдена на Github . Я проверил это с помощью PHP 7.1.

В этом посте я много говорю о булавках. Я использую это слово для обозначения выводов, выходящих из компонентов, а также разъемов Arduino, в которые эти выводы входят. Их часто называют терминалами или портами. Когда я говорю о выводах, я имею в виду место, к которому можно подключить компоненты или провода. Я каждый раз довольно конкретен, так что все будет хорошо!

Проэкт

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

Скажем, кто-то упомянул нас в Твиттере. Светодиод должен периодически переключаться в синий цвет Twitter. Затем, если кто-то отправит нам электронное письмо, индикатор должен переключаться между синим цветом Twitter и красным GMail. Мы могли бы распространить это на зеленый Fitbit, синий Facebook и так далее.

Если мы увидели уведомление Twitter, мы должны помахать рукой перед датчиком приближения, а синий Twitter должен быть удален из поворота, оставив GMail красным и т. Д.

Аппаратное обеспечение

Мы можем использовать практически любой светодиод RGB. Пока мы можем контролировать цвет, к которому он исчезает, мы можем моделировать уведомления социальной сети, которые нам нужны. На самом деле, почти любой обычный анод RGB LED . Если вы получаете тот, который не включает резисторы, обязательно добавьте их в свою схему. Я коснусь этого позже …

Чем больше IoT-работы вы выполняете, тем больше у вас шансов столкнуться с терминами анод и катод. Обычный анодный светодиод RGB — это тот, который соединяет один контакт с положительным контактом на вашем микроконтроллере или аккумуляторе и три контакта с землей, в зависимости от сочетания цветов, которые вы ищете.

Подключение красного контакта к земле замкнет цепь так, что ток протекает через красную часть светодиода. Подключение красного контакта к порту импульсной широтно-импульсной модуляции (или ШИМ) на нашем Arduino и заземление порта вдвое уменьшит количество тока, протекающего через красную часть светодиода.

Заземление различных пинов разным количеством приведет к множеству разных цветов — столько, сколько вы можете определить как цвета CSS.

Далее нам нужен какой-то датчик приближения. Самый простой (на мой взгляд) инфракрасный трансивер . Вы также можете использовать ультразвуковой приемопередатчик , но он лучше в среднем диапазоне (поскольку у них минимальное расстояние восприятия, поэтому вам придется находиться дальше от датчика).

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

Соединяя вещи вместе

Подключение цепи

Это относительно простая схема, насколько они идут. Я ни в коем случае не эксперт, но мне удалось найти спецификации для инфракрасного датчика и RGB-светодиода, который я купил; так что их соединение не слишком беспокоило.

Важным моментом является то, что наши светодиодные выводы подключены к портам PWN, так что мы можем постепенно регулировать количество красного, зеленого и синего, и что контакт датчика подключен к аналоговому порту (в данном случае A0 ).

Вы можете спросить, почему мы не подключаем выводы LEG к аналоговым портам или почему мы не подключаем датчик к ШИМ-порту. ШИМ предназначен для имитации степеней включения / выключения, но способ сделать это — включить что-то на долю секунды.

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

Нам нужен датчик, подключенный к аналоговому порту, потому что нам действительно нужно постепенное измерение, и A0 даст нам это.

Мой светодиод RGB в порядке с 3,3 В и током 200 мА (до резисторов). Таким образом, я могу подключить его к контакту 3,3 В и оставить контакт 5 В для питания датчика.

Мой датчик также имеет контакт для включения / отключения показаний датчика. Я напишу для этого код, но имейте в виду, что ваш датчик также может иметь это. Если это так, подключите его к любому неиспользуемому выходному контакту (например, 0 или 13 ) и убедитесь, что этот вывод установлен на высокий уровень.

Нам также необходимо подключить USB-порт Arduino к открытому USB-порту на компьютере разработчика.

Программное обеспечение

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

 namespace Notifier; interface Service { /** * Queries the service to trigger notification * alerts. Returns true if there are new * notifications to show. * * @return bool */ public function query(); /** * Marks the most recent notifications as seen. */ public function dismiss(); /** * Returns the name of this service. * * @return string */ public function name(); /** * Returns an array of pin color values, * for a common-anode RGB LED. * * @return int[] */ public function colors(); } 

Это из src/Service.php

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

Мы должны иметь возможность идентифицировать каждую услугу по понятному имени, а также нам нужно знать, какие светлые цвета ассоциируются с этой службой.

Подключение к Twitter

Общение с Twitter означает общение с OAuth . Нет смысла писать еще одну абстракцию для этого, поэтому мы будем использовать довольно популярную библиотеку Twitter:

 composer require endroid/twitter 

Нам также потребуется создать и сохранить различные ключи API. Зайдите на https://apps.twitter.com/app/new и создайте новое приложение. Вы можете использовать любой URL обратного вызова, так как мы все равно переопределим его.

Создание новых приложений Twitter

Создание новых приложений Twitter

Обратите внимание на потребителя и токены доступа и ключи. Передача таких вещей в Github обычно ужасная идея, поэтому вместо этого мы будем хранить их как переменные среды. Давайте создадим пару файлов (называемых .env и .env.example ):

 SERVICE_GMAIL_USERNAME= SERVICE_GMAIL_PASSWORD= SERVICE_TWITTER_CONSUMER_KEY= SERVICE_TWITTER_CONSUMER_SECRET= SERVICE_TWITTER_ACCESS_TOKEN= SERVICE_TWITTER_ACCESS_TOKEN_SECRET= 

Это из .env.example

Прежде чем делать что-либо еще, создайте файл .gitignore и добавьте к нему .env . Вот где мы будем хранить секретные вещи, поэтому мы определенно не хотим передавать их в Github.

Далее, давайте создадим сервис уведомлений Twitter:

 namespace Notifier\Service; use Notifier\Service; use Endroid\Twitter\Twitter as Client; class Twitter implements Service { /** * @var Client */ private $client; /** * @var bool */ private $new = false; /** * @var int */ private $since; /** * @param string $consumerKey * @param string $consumerSecret * @param string $accessToken * @param string $accessTokenSecret */ public function __construct($consumerKey,  $consumerSecret, $accessToken, $accessTokenSecret) { $this->client = new Client( getenv("SERVICE_TWITTER_CONSUMER_KEY"), getenv("SERVICE_TWITTER_CONSUMER_SECRET"), getenv("SERVICE_TWITTER_ACCESS_TOKEN"), getenv("SERVICE_TWITTER_ACCESS_TOKEN_SECRET") ); } } 

Это из src/Service/Twitter.php

Как правило, это хорошая идея — создавать зависимости типа Client вне класса и вводить их в качестве параметров конструктора. Но этот клиент — просто деталь реализации, и у меня нет интерфейса, на который можно было бы намекнуть. Я думаю, что это нормально, чтобы создать новый экземпляр внутри.

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

Давайте создадим метод query :

 /** * @inheritdoc * * @return bool */ public function query() { if ($this->new) { return true; } $parameters = [ "count" => 1, ]; if ($this->since) { $parameters["since_id"] = $this->since; } $response = $this->client->query( "statuses/mentions_timeline", "GET", "json", $parameters ); $tweets = json_decode($response->getContent()); if (count($tweets) > 0) { $this->new = true; $this->since = (int) $tweets[0]->id; } return $this->new; } 

Это из src/Service/Twitter.php

Мы будем часто запрашивать этот сервис, и пока мы не отклоним твит-уведомления, мы хотим, чтобы они продолжали показываться на светодиодном индикаторе. Поэтому, если мы ранее нашли новые твиты, мы можем вернуться рано.

Если мы уже запросили в Твиттере новые твиты, мы добавим его идентификатор в параметры запроса. Это означает, что мы будем возвращать новые твиты только в последующих запросах API.

Мы уже связались с клиентом в конструкторе. Таким образом, мы можем немедленно вызвать метод client->query , выбирая твиты из временной шкалы. Если since идентификатора с тех since появились новые твиты, мы сообщаем о новых твитах.

Нам просто нужно завершить интерфейс:

 /** * @inheritdoc */ public function dismiss() { $this->new = false; } /** * @inheritdoc * * @return string */ public function name() { return "twitter"; } /** * @inheritdoc * * @return int[] */ public function colors() { return [1 - 0.11, 1 - 0.62, 1 - 0.94]; } 

Это из src/Service/Twitter.php

Мы увидим, как использовать этот класс в ближайшее время.

Подключение к Gmail

Нам не нужно использовать OAuth для подключения к GMail, но нам нужно включить IMAP и сгенерировать пароль для конкретного приложения. Если вы еще не включили IMAP с PHP, обратитесь к справке вашей установочной / операционной системы. При сборке из исходного кода вы обычно можете установить его с флагом --with-imap . Это также работает с Homebrew на OS X:

 brew install phpXX --with-imap 

XX — это номер вашей версии PHP, такой как 56 или 71

Чтобы создать новый пароль для конкретного приложения, перейдите на страницу https://security.google.com/settings/security/apppasswords:

Создание новых паролей для приложений Google

Создание новых паролей для приложений Google

Получив новый пароль, установите его в .env вместе с адресом электронной почты, для которого вы его создали. Далее, давайте подключимся к GMail:

 namespace Notifier\Service; use Notifier\Service; class Gmail implements Service { /** * @var bool */ private $new = false; /** * @var array */ private $emails = []; /** * @var string */ private $username; /** * @var string */ private $password; /** * @param string $username * @param string $password */ public function __construct($username, $password) { $this->username = $username; $this->password = $password; } } 

Это из src/Service/Gmail.php

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

Как я обнаружил ранее, нам нужно переподключаться каждый раз, когда мы хотим проверить новые электронные письма. Любопытный побочный эффект этого клиента IMAP …

Далее нам нужно создать метод query :

 /** * @inheritdoc * * @return bool */ public function query() { if ($this->new) { return true; } $inbox = imap_open( "{imap.gmail.com:993/imap/ssl}INBOX", $this->username, $this->password ); if (!inbox) { return false; } $emails = imap_search($inbox, "ALL", SE_UID); if ($emails) { foreach ($emails as $email) { if (!in_array($email, $this->emails)) { $this->new = true; break; } } $this->emails = array_values($emails); } return $this->new; } 

Это из src/Service/Gmail.php

Как я упоминал ранее, нам нужно переподключаться к серверу IMAP каждый раз, когда мы хотим видеть новые электронные письма. Итак, мы делаем это, ища все электронные письма в папке «Входящие». Затем мы сравниваем то, что возвращается, со списком ранее кэшированных идентификаторов сообщений. Если есть какие-либо новые, мы сообщаем о новых электронных письмах.

Давайте закончим остальную часть реализации интерфейса:

 /** * @inheritdoc */ public function dismiss() { $this->new = false; } /** * @inheritdoc * * @return string */ public function name() { return "gmail"; } /** * @inheritdoc * * @return int[] */ public function colors() { return [1 - 0.89, 1 - 0.15, 1 - 0.15]; } 

Это из src/Service/Gmail.php

Любопытно, что цвета RGB, используемые в логотипах YouTube и GMail, равны 226, 38 и 28. Мы вычитаем их из одного, потому что светодиоды с общим анодом ярче, чем нижнее значение ШИМ, которое мы установили. Это потому, что чем ниже мы устанавливаем значение ШИМ, тем больше мы заземляем цветные выводы, а заземление выводов приводит к более сильному току, протекающему через светодиоды.

Давайте соединим их с кодом Arduino…

Подключение к Arduino

У нас не так много времени, чтобы пройти через основы программирования на Arduino PHP. К счастью, я написал еще один отличный пост об этом . Следуйте инструкциям там, обязательно установив расширение Gorilla (если вы используете OS X).

После установки Firmata мы можем установить библиотеки Carica :

 composer require carica/io dev-master@dev composer require carica/firmata dev-master@dev 

Кроме того, нам также нужно установить библиотеку переменных среды:

 composer require vlucas/phpdotenv 

Мы можем начать работу, загрузив переменные окружения и подключившись к Arduino:

 require __DIR__ . "/vendor/autoload.php"; use Dotenv\Dotenv; (new Dotenv(__DIR__))->load(); use Carica\Io; use Carica\Firmata; $loop = Io\Event\Loop\Factory::get(); $board = new Firmata\Board( Io\Stream\Serial\Factory::create( "/dev/cu.usbmodem1421", 57600 ) ); print "connecting to arduino..."; $board ->activate() ->done(function () use ($loop, $board) { print "done" . PHP_EOL; }); $loop->run(); 

Это из notifier.php

Если вы не уверены, какой порт использовать (вместо моего /dev/cu.usbmodem1421 ), введите следующее:

 ls /dev | grep usbmodem 

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

 // diode pins $board->pins[10]->mode = Firmata\Pin::MODE_PWM; $board->pins[10]->analog = 1; $board->pins[11]->mode = Firmata\Pin::MODE_PWM; $board->pins[11]->analog = 1; $board->pins[9]->mode = Firmata\Pin::MODE_PWM; $board->pins[9]->analog = 1; // sensor pins $board->pins[12]->mode = Firmata\Pin::MODE_OUTPUT; $board->pins[12]->digital = 1; $board->pins[14]->mode = Firmata\Pin::MODE_ANALOG; 

Это из notifier.php

Не так много, чтобы сказать об этом. Каждый вывод светодиода RGB установлен в режим ШИМ, а их значения установлены на 1 (так что светодиоды погасли). Так как мой инфракрасный датчик имеет вывод включения / выключения, мне нужно установить этот вывод на 1 (включен). Наконец, мы устанавливаем аналоговый режим вывода датчика считывания.

Далее, давайте подключимся к Twitter и GMail:

 print "connecting to services..."; $services = new SplQueue(); $services->enqueue([ new Notifier\Service\Twitter( getenv("SERVICE_TWITTER_CONSUMER_KEY"), getenv("SERVICE_TWITTER_CONSUMER_SECRET"), getenv("SERVICE_TWITTER_ACCESS_TOKEN"), getenv("SERVICE_TWITTER_ACCESS_TOKEN_SECRET") ), false ]); $services->enqueue([ new Notifier\Service\Gmail( getenv("SERVICE_GMAIL_USERNAME"), getenv("SERVICE_GMAIL_PASSWORD") ), false ]); print "done" . PHP_EOL; 

Это из notifier.php

Мы можем поставить в очередь каждую службу, к которой мы хотим подключиться, в SPLQueue . Это полезный реферат для хранения объектов «первым пришел — первым вышел» (или FIFO). Второй логический параметр — есть ли у сервиса новые уведомления для отображения. Мы изменим это, когда новые уведомления будут обнаружены и отклонены.

Теперь давайте настроим повторяющуюся проверку новых уведомлений:

 $loop->setInterval(function () use (&$services) { $remaining = count($services); while ($remaining--) { $next = $services->dequeue(); $next[1] = $next[0]->query(); $services->enqueue($next); } }, 1000 * 5); 

Это из notifier.php

Мы можем использовать метод setInterval цикла обработки событий, который повторяется каждые 1000 миллисекунд * 5 (или 5 секунд). Мы выполняем все службы в очереди, извлекаем их, устанавливаем, должны ли они отображать новые уведомления, и затем помещаем их обратно в очередь.

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

Теперь нам нужно снова пройтись по сервисам, меняя их светодиоды по очереди. Мы можем установить это, чтобы показывать один тип уведомления каждые 4 секунды:

 $service = null; $next = function () use ($loop, $board, &$next,  &$services, &$service) { $remaining = count($services); while ($remaining--) { $next = $services->dequeue(); $services->enqueue($next); if ($next[1]) { print "showing {$next[0]->name()}" . PHP_EOL; $service = $next; break; } } if (!$service) { print "no notifications" . PHP_EOL; return; } $colors = $service[0]->colors(); $board->pins[10]->analog = $colors[0]; $board->pins[11]->analog = $colors[1]; $board->pins[9]->analog = $colors[2]; $loop->setTimeout(function () use ($board, &$service) { $board->pins[10]->analog = 1; $board->pins[11]->analog = 1; $board->pins[9]->analog = 1; $service = null; }, 1000 * 1.5); }; $loop->setInterval($next, 1000 * 4); 

Это из notifier.php

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

Затем мы выбираем его цвета и устанавливаем контакты на соответствующее значение. Через 1.5 секунды мы снова отключаем контакты (устанавливая их обратно в 1 ). Эта функция $next вызывается каждые 4 секунды.

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

 $loop->setInterval(function() use ($board,  &$services, &$service) { if ($service !== null &&  $board->pins[14]->analog < 0.1) { $remaining = count($services); while ($remaining--) { print "dismissing {$service[0]->name()}"  . PHP_EOL; $next = $services->dequeue(); if ($next[0]->name() === $service[0]->name()) { $service = null; $next[0]->dismiss(); $next[1] = false; } $services->enqueue($next); } } }, 50); 

Это из notifier.php

Если в данный момент существует служба, отображающая уведомление, и датчик считывает значение ниже 0.1 , мы считаем, что это означает, что перед датчиком есть рука (и что он отклоняет тип уведомления).

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

Вывод

Есть так много интересных вещей, которые мы можем сделать, используя Arduino и PHP. Это всего лишь один полезный проект. Пока я заканчивал этот пост, он дал мне знать о множестве новых писем и твитов. Теперь все, что мне нужно сделать, это упаковать его в коробку проекта, и я смогу взять его на работу!

Вам понравилось это? Возможно, у вас есть идеи для следующего проекта, над которым я могу работать. Дайте нам знать в комментариях ниже.