Включаем WiFi голосом через Алексу на Raspberry Pi
Всем привет! Сегодня будем реализовывать функционал включения и выключения радиомодуля (а-ля WiFi) с помощью голосового помощника Алекса на Raspberry Pi (RPi). Как поставить Алексу на RPi вы можете прочитать в инструкции “Голосовой помощник Alexa на Raspberry Pi”. Весь мой интернет дома разведён по проводам, которые проложены в скрытой проводке, а беспроводным соединением пользуется мой 9-и летний сын по мере необходимости, в остальное время радиомодуль находится в выключенном состоянии.
Роутер у меня фирмы Asus, модель RT-N15U и данный пример будет рассмотрен для роутера именно этой модели. По аналогии вы сможете это сделать для своего роутера, хотя нет стопроцентной гарантии, что получится. Дома также у меня стоит оптический терминал (оптоволоконный интернет) и там есть свой радиомодуль, так вот, используя данную схему, у меня не получилось включать и выключать беспроводной интернет. Роутер Asus я подключил в общую локальную подсеть и реализовал данную схему на нем.
Как вы знаете, включать и выключать беспроводное соединение можно через веб-интерфейс роутера зайдя на его страницу в локальной сети, например, 192.168.0.1. Подключиться программно к роутеру через протокол SSH было невозможно, т.к. Asus не внедрил такой функционал в RT-N15U. Я сначала прошил роутер кастомной прошивкой от проекта OpenWrt, т.к. она позволяла заходить на роутер через протокол SSH, а затем через терминальную команду включать/выключать радиомодуль. Но в итоге оказалось, что для RT-N15U драйвера прошивки не поддерживали функцию радиомодуля вообще. Пришлось вернуть стоковую прошивку от Asus и использовать следующую схему. Будем снимать дамп пакетов в локальной сети от RPi до роутера с помощью tcpdump, а затем с помощью функции curl_exec в PHP воспроизводить передачу POST запросов.
Что Вам понадобится:
- Роутер с радиомодулем — 1шт.
- ОС Raspbian c графической оболочкой (GUI) — 1шт.
- RPi c установленной Алексой — 1шт.
- Сервер Apache c PHP на RPi
- Программа tcpdump на RPi
- Программа Wireshark на RPi
Шаг 1 : Снятие дампа пакетов между RPi и роутером
Сначала установим tcpdump на RPi. Открываем терминал и вводим команду:
sudo apt install tcpdump
Установим Wireshark:
sudo apt install wireshark
Переходим в домашнюю папку:
cd ~
Открываем браузер на RPi и вводим туда локальный IP-адрес нашего роутера, например 192.168.0.1. Вводим логин/пароль и входим в роутер. Идём в Administration и ставим Auto Logout на 0 (отключить). Это нужно для того, чтобы текущая сессия нахождения в админке сохранялась неограниченное время. Если в настройках вашего роутера вы не сможете отключить Auto Logout, то переменная сессии будет обнуляться после определённого периода. Такое я наблюдал на своём оптическом терминале, таймаут отключить нельзя и он выставляется от 1-й до 30-и минут. В роутере Asus отключить можно, но скорее всего (пока ещё не проверял) переменная сессии будет удаляться при перезагрузке роутера.
Выходим из роутера и заходим на 192.168.0.1, снова вводим логин и пароль, но на кнопку Sign in пока не жмём.
Открываем терминал и вводим команду:
ifconfig
Нам нужно узнать имя сетевого адаптера на RPi. В моем случае он eth0.
Далее, вводим команду:
tcpdump -w eth0_dump.pcap -i eth0 host 192.168.0.1
Данная команда будет сканировать TCP-трафик как от RPi до роутера, так и от роутера до RPi и сохранять результаты в файле eth0_dump.pcap
Затем возвращаемся в браузер с открытой страницей входа на роутер и нажимаем кнопку Sign in. Далее, идём в раздел Wireless, вкладка Professional, ставим радиокнопку Enable Radio на Yes и жмём Apply, затем ставим снова на No и жмём Apply.
Далее, возвращаемся в терминал и останавливаем tcpdump, нажимая CTRL+C.
Все, на этом дамп пакетов закончен. Возвращаемся в окно браузера и закрываем его, при этом не выходя из админки роутера.
Шаг 2 : Анализ пакетов в Wireshark
Запускаем Wireshark и в нем открываем наш сохранённый файл eth0_dump.pcap.
Нас интересует следующая строчка в столбце Info:
POST /start_apply.htm HTTP/1.1 (application/x-www-form-urlencoded)
Нажимаем по ней правой кнопкой мышки, выбираем Copy -> …as Printable Text. Далее открываем текстовый редактор, вставляем скопированное и сохраняем.
Результат копирования должен быть похожим на аналогичный в данном примере:
POST /start_apply.htm HTTP/1.1 Host: 192.168.0.1 Connection: keep-alive Content-Length: 632 Cache-Control: max-age=0 Origin: http://192.168.0.1 Upgrade-Insecure-Requests: 1 DNT: 1 Content-Type: application/x-www-form-urlencoded User-Agent: # Данные скрыты Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.0.1/Advanced_WAdvanced_Content.asp Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Cookie: asus_token=2905583498947782384812342334534; wireless_list=; traffic_warning_0=2020.1:1; nwmapRefreshTime=1582606239936; clock_type=1 productid=RT-N15U&wl_nmode_x=1&wl_gmode_protection_x=¤t_page=Advanced_Wadvanced_Content.asp&next_page=Advanced_Wadvanced_Content.asp&group_id=&modified=0&first_time=&action_mode=apply_new&action_script=restart_wireless&action_wait=3&preferred_lang=EN&firmver=3.0.0.4&wl_subunit=-1&wl_amsdu=auto&wl_TxPower=&wl1_80211h_orig=&acs_dfs=&w_Setting=1&wl_unit=0&wl_radio=1&wl_timesched=0&wl_ap_isolate=0&wl_rate=0&wl_user_rssi=0&wl_igs=0&wl_mrate_x=0&wl_rateset=default&wl_plcphdr=long&wl_rts=2347&wl_dtim=3&wl_bcn=100&wl_frameburst=on&wl_PktAggregate=0&wl_wme_apsd=on&wl_DLSCapable=0&wl_noisemitigation=0&wl1_80211h=0&wl_txpower=100
P.S. В данном примере, данные User-Agent cкрыты, а переменая asus_token изменена.
Параметр asus_token=2905583498947782384812342334534, это идентификатор сессии на стороне роутера. Сессия будет постоянной, пока отключён таймер выхода из админки роутера. Она изменится, если вы выйдете из админки и зайдёте заново. Процедуру сканирования пакетов нужно будет проходить заново.
Ищем следующий POST-пакет (на отключение радиомодуля), анализируем его данные и понимаем, что заголовки у него идентичные, а отличается он только телом на одно значение переменной. В POST-пакете на включение это wl_radio=1, а на выключение wl_radio=0.
Все, на этом снятие данных закончено.
Шаг 3 : PHP-скрипт на выполнение функции curl_exec
Далее, пишем вот такой PHP-скрипт и подставляем в него свои данные сессии:
<? if (isset($_POST['code'])) $code = $_POST['code']; else $code = ""; $code_wifi_on = "4BvZGFz6"; $code_wifi_off = "ctHUAopI"; if ($code == $code_wifi_on || $code == $code_wifi_off) { $headers = array( 'Connection: keep-alive', 'Content-Length: 632', 'Cache-Control: max-age=0', 'Origin: http://192.168.0.1', 'Upgrade-Insecure-Requests: 1', 'DNT: 1', 'Content-Type: application/x-www-form-urlencoded', 'User-Agent: ', # Данные скрыты 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 'Referer: http://192.168.0.1/Advanced_WAdvanced_Content.asp', 'Accept-Encoding: gzip, deflate', 'Accept-Language: en-US,en;q=0.9', 'Cookie: asus_token=2905583498947782384812342334534; wireless_list=; traffic_warning_0=2020.1:1; nwmapRefreshTime=1582606239936; clock_type=1' ); if ($code == $code_wifi_on) $radio =1; else $radio = 0; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL,"http://192.168.0.1/start_apply.htm"); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, "productid=RT-N15U&wl_nmode_x=1&wl_gmode_protection_x=¤t_page=Advanced_WAdvanced_Content.asp&next_page=Advanced_WAdvanced_Content.asp&group_id=&modified=0&first_time=&action_mode=apply_new&action_script=restart_wireless&action_wait=3&preferred_lang=EN&firmver=3.0.0.4&wl_subunit=-1&wl_amsdu=auto&wl_TxPower=&wl1_80211h_orig=&acs_dfs=&w_Setting=1&wl_unit=0&wl_radio=".$radio."&wl_timesched=0&wl_ap_isolate=0&wl_rate=0&wl_user_rssi=0&wl_igs=0&wl_mrate_x=0&wl_rateset=default&wl_plcphdr=long&wl_rts=2347&wl_dtim=3&wl_bcn=100&wl_frameburst=on&wl_PktAggregate=0&wl_wme_apsd=on&wl_DLSCapable=0&wl_noisemitigation=0&wl1_80211h=0&wl_txpower=100"); $server_output = curl_exec($ch); curl_close ($ch); if ($server_output == "true" && $code == $code_wifi_on) { exec("/usr/bin/ledon"); } if ($server_output == "true" && $code == $code_wifi_off) { exec("/usr/bin/ledoff"); } } ?>
В данном скрипте я также указал путь к 2-м файлам для управления яркостью встроенного светодиода на RPi, чтобы визуально знать, когда радиомодуль включен, а когда нет.
Создадим эти файлы:
sudo nano /usr/bin/ledon
Вставляем в него следующее:
#!/bin/bash sudo sh -c 'echo 255 > /sys/class/leds/led1/brightness'
Сохраняем его и делаем исполняемым:
sudo chmod +x /usr/bin/ledon
Затем создаем файл ledoff:
sudo nano /usr/bin/ledoff
Вставляем в него следующее:
#!/bin/bash sudo sh -c 'echo 0 > /sys/class/leds/led1/brightness'
Сохраняем его и делаем исполняемым:
sudo chmod +x /usr/bin/ledoff
Шаг 4 : Интеграция с Алексой
Далее, данный PHP-скрипт связываем с Алексой через сервис IFTTT. О том как это сделать можно прочитать в статье «Я есть универсальный ИК-пульт с голосовым управлением Алексой…»
Теперь чтобы включить WiFi достаточно сказать – “Alexa, trigger wifi on…”
Profit! :)
P/S
Друзья, сегодня, как я и ожидал, сессия при перезагрузке роутера уничтожилась и данная схема становится рабочей наполовину. Я ещё раз попробовал найти кастомную прошивку с SSH для RT-N15U и в этот раз мне это удалось сделать! Я скачал и прошил роутер прошивкой от Tomato и надо отдать должное, что это очень крутая прошивка с кучей настроек. Самое главное, что в ней есть доступ к роутеру через протокол SSH, а значит можно свободно включать и выключать радиомодуль через терминал и привязать голосовое управление через Алексу.
Итак, качаем прошивку отсюда.
Я скачал прошивку tomato-K26-1.28.RT-N5x-MIPSR2-140-Max.zip, т.к. прошивка весит ~7 мегабайт, а размер Flash-памяти моего роутера всего 8 мегабайт.
Далее скачиваем программу Asus Firmware Restoration:
Т.к. программа восстановления прошивок от Asus для Windows, то будет прошивать наш роутер в этой операционной системе. Соединяем компьютер с роутером через любой LAN-порт.
Открываем программу Asus Firmware Restoration, указываем путь к прошивке, но на кнопку Upload пока не жмём!
Отключаем роутер от розетки, зажимаем кнопку Reset и, не отпуская её, включаем роутер в розетку и так держим секунд 10, пока синий светодиод на роутере не начнёт медленно мигать. Как только мы наблюдаем такую картину, сразу же возвращаемся в окно с программой и нажимаем Upload. Это нужно сделать быстро, т.к. у вас будет ограниченное “окно” нахождения в данном режиме. После нажатия кнопки Upload, начнётся процесс прошивки, в это время не прикасайтесь к роутере и компьютеру, не шевелите LAN кабель, иначе можно “закирпичить” девайс!
После прошивки роутера нам нужно очистить энергонезависимую память NVRAM от прошлых настроек старой прошивки. Для этого выключаем роутер из розетки, зажимаем кнопку WPS на роутере и, не отпуская её, включаем роутер в розетку и ждём около 10 секунд. После этого отпускаем WPS и ждём около 2-3-х минут.
После данной процедуры ещё раз выключим и включим роутер, а потом пробуем зайти в админку по адресу 192.168.1.1 c логином admin и паролем admin. После захода в админку предлагаю сразу сменить пароль. Предлагаю сразу сменить LAN IP роутера на 192.168.0.1 и заходить в админку по уже привычному IP-адресу. Снова выключаем роутер, также подключим LAN кабель, идущий к вам в квартиру, в WAN порт и снова включим роутер.
Идём в раздел Basic и настраиваем данные радиомодуля.
Далее заходим через SSH на RPi и создаём SSH-ключи:
ssh pi@192.168.0.XXX ssh-keygen
Нас интересует публичный ключ, который сгенерируется в файле id_rsa.pub. Открываем данный файл:
nano ~/.ssh/id_rsa.pub
Копируем его содержимое, возвращаемся в админку роутера, идём в раздел Administration / Admin Access и вставляем содержимое файла в поле Authorized Keys. Также на странице Admin Access в разделе SSH Daemon отмечаем галочкой Remote Access и убираем галочку с Limit Connection Attempts.
После этого нажимаем кнопку Save, и теперь можно входить на роутер по SSH c RPi без запроса пароля:
ssh root@192.168.0.1
Теперь наоборот. Для захода по SSH с роутера на RPi также без пароля, нужно сделать следующее. Сначала активируем раздел JFFS на роутере. Это кусочек свободной флеш-памяти на микроконтроллере, которая осталась после заливки прошивки ~7 мегабайт и данный раздел можно примонтировать как мини-диск на роутере, который будет хранить данные даже после перезагрузки. Заходим в Administration / JFFS, активируем его и делаем очистку (Erase). После этого на него можно зайти в терминале роутера:
cd /jffs
Секретные ключи SSH хранятся на роутере в папке /etc/dropbear. На роутере находится урезанная версия OpenSSH и она называется Dropbear. Публичные ключи у обеих программ одинаковой структуры.
Генерируем публичный ключ роутера и сохраняем его на нашем мини-диске:
dropbearkey -y -t rsa -f /etc/dropbear/dropbear_rsa_host_key > /jffs/id_rsa.pub
Копируем публичный ключ на RPi:
scp /jffs/id_rsa.pub pi@192.168.0.XXX:/home/pi
Затем по SSH заходим на RPi в /home/pi:
ssh pi@192.168.0.XXX cd ~
и добавляем данные публичного ключа роутера в файл authorized_keys:
cat id_rsa.pub >> .ssh/authorized_keys
Далее открываем файл authorized_keys:
nano .ssh/authorized_keys
И убираем из него строки, нажимая CTRL+K:
Public key portion is: Fingerprint:
Сохраняем файл.
Всё, теперь из роутера на RPi можно входить без ввода пароля. Это будет нужно для выключения красного светодиода диода на RPi.
Ещё один момент, при заходе на RPi с роутера, SSH скажет вам, что IP-адрес RPi не находится в списке проверенных хостов и попросит вас продолжить:
Do you want to continue connecting? (y/n)
Похоже это баг dropbear, и при перезагрузке роутера сообщение будет появляться снова, а значит, будет влиять на выполнение терминальной команды. Чтобы это обойти, будем соединяться c RPi по SSH c параметром -y и обязательным указанием секретного ключа на роутере, например:
ssh -y -i /etc/dropbear/dropbear_rsa_host_key pi@192.168.0.XXX
Итак, все необходимые настойки сделаны и теперь на сервере Apache на RPi пишем такой скрипт:
<? if (isset($_POST['code'])) $code = $_POST['code']; else $code = ""; $code_wifi_on = "4BvZGFz6"; $code_wifi_off = "ctHUAopI"; $cmd_wifi_on = "/usr/bin/ssh root@192.168.0.1 wl radio on"; $cmd_wifi_off = "/usr/bin/ssh root@192.168.0.1 wl radio off"; if ($code == $code_wifi_on) { shell_exec($cmd_wifi_on); exec("/usr/bin/ledon"); } if ($code == $code_wifi_off) { shell_exec($cmd_wifi_off); exec("/usr/bin/ledoff"); } ?>
Чувствуете разницу в размере этого и предыдущего скрипта в основной инструкции? :)
Далее, возвращаемся в админку роутера и идём в Administration / Sсheduler и в разделе Custom 1 вставляем такой скрипт:
#!/bin/sh RADIO=`wl isup` if [ $RADIO == 1 ] then /usr/bin/ssh -y -i /etc/dropbear/dropbear_rsa_host_key pi@192.168.0.XXX ledon else /usr/bin/ssh -y -i /etc/dropbear/dropbear_rsa_host_key pi@192.168.0.XXX ledoff fi
Затем отмечаем галочку Enabled и ставим выполнение скрипта каждую минуту. Это нужно для того, чтобы роутер включал/выключал светодиод на RPi даже после перезагрузки RPi. Если вам не нужна индикация светодиода на RPi, то вы этого можете не делать. Мне она была необходима, т. к. роутер у меня спрятан в труднодоступном месте и мой сын постоянно забывает выключать WiFi. :)
Далее идём в Administration / Script и в Shutdown ставим:
/usr/bin/ssh -y -i /etc/dropbear/dropbear_rsa_host_key pi@192.168.0.XXX ledoff
Жмём Save.
Возможно, это можно и не ставить, т.к. уже есть планировщик выполнения таких же действий каждую минуту, но в этом случае не будет чувствоваться моментального эффекта выключения светодиода после начала перезагрузки или выключения роутера.
Также на RPi выключим все светодиоды после загрузки системы:
sudo nano /etc/rc.local
И до exit 0 вставляем строки:
sh -c 'echo 0 > /sys/class/leds/led0/brightness' sh -c 'echo 0 > /sys/class/leds/led1/brightness'
Пожалуй, на этом настройка системы будет закончена! Ещё раз напомню, что это самая надёжная схема включения и выключения радиомодуля через протокол SSH и ей не страшны перезагрузки оборудования. Схема включения/выключения приведённая в начале инструкции подойдёт всем тем, кто не сможет получить доступ к своему роутеру через SSH, но в данном случае нужно следить за его бесперебойной работой иначе идентификатор сессии нужно будет получать заново.
Понравилась инструкция? Тогда поделитесь ей со своими друзьями или попробуйте написать свою собственную!