Оптимизация расходов Yota: попытка #3

в 11:16, , рубрики: LTE, OpenWrt, Raspberry Pi, Yota, автоматизация

Привет!

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

В моем регионе крупных провайдеров LTE всего два: Мегафон и Yota. Мегафон существенно дешевле, но у него есть одна крайне неприятная особенность: ограничение в 20гб трафика в месяц даже на максимальном тарифе.

Поэтому, выбор оператора, на мой взгляд, очевиден. Но все же платить за 20 мегабит в два раза больше чем за 100 дома — сомнительное удовольствие. Но при этом, в отличии от других операторов, Yota позволяет в в любой момент бесплатно изменять текущий тариф в личном кабинете. Нужна скорость — выкручиваем ползунок на максимум. Нет? Тогда можно снизить скорость и платить меньше. Притом тарификация, судя по всему, посекундная. Ну как тут можно удержаться и не автоматизировать этот процесс?

На хабре уже были статьи (раз, два), описывающие попытки йотоводов-автоматизаторов. Однако, по определенным причинам, их творения мне не подошли и пришлось написать свой, довольно кардинально отличающийся, велосипед.

Всех йотоводов и автоматизаторов прошу под кат.

А начнем мы, пожалуй, с разбора причин, по которым мне не подошли чужие велосипеды. Особо нетерпеливые / материально заинтересованные могут сразу перейти к разделу, описывающему реализацию.

Постановка задачи

Вариант linx56 мне не подошел ну совсем никак, потому как он работает phantomjs, для работы которого нужно довольно нехилое железо (которым Raspberry PI не является). Да и даже если я поставлю это на свой основной компьютер и оставлю его работать круглосуточно, забив в cron расписание по которому нужно отключать интернет, то я получу счет за электричество больше, чем сэкономлю все равно не получу нормального результата, потому как интернет используется крайне ненормировано (при том не только мной, но и членами семьи, для которых главное чтобы «Просто работало»).

Второй вариант от bambrman уже лучше хотя бы тем, что использует curl вместо phantomjs. Но по функциональности остается на том же уровне.

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

Реализация

В основе всего лежит Raspberry Pi, на котором крутится демон, написанный на Python. Он каждые n секунд (задается в конфиге, котрый будет приведен в следующем разделе) подключается по SSH к главному домашнему роутеру (TP-LINK WDR4300 перепрошитому на OpenWrt) и при помощи команды ifconfig стягивает статистику нужного интерфейса.

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

modes: {
0: 320,
290: 512,
495: 768,
700: 1.0,
900: 2.1,
1900: 3.1,
2900: 4.1,
3900: 6.5,
6500: 8.0,
8000: 10.0,
10000: 15.0,
14800: 20.0
}

Как вы видите, в качестве индекса словаря выступает скорость в килобитах. Я называю это режимом. В качестве значения — тариф йоты, который нужно установить для данного режима. Попробую объяснить на примере:

По результатам замера текущей скорости у нас получилось, скажем, 1530 килобит. Округляем в меньшую сторону до ближайшего режима. Получается, что эта скорость соответствует режиму 900, то есть тарифу 2.1 мегабит.

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

Новый == текущий

В этом случае цикл просто продолжится

Новый > текущий

В этом случае запускается процесс апгрейда. По сути, текущий тариф просто меняется на указанный в режиме.

Но и тут есть одна особенность. Дело в том, что в конфиге есть такой параметр как:

speed_increase_step: 1

Он указывает на размер шага апгрейда между тарифами. То есть обычно скорость с предыдущего тарифа может переключиться только на следующий (потому как например при тарифе в 10 мегабит невозможно достигнуть скорости в 14800 для переключения сразу на 20. То есть между 10 и 20 все равно должен быть 15). Иногда это не очень удобно. Попробую объяснить на примере:

Вы не пользовались интернетом некоторое время, а потом пришли и сразу включили поток с YouTube в 720-1080p. Соответственно, скорости в 320 килобит, которая была установлена в ваше отсутствие для этого явно не хватает. И тут 2 варианта: либо демон будет постепено повышать тариф (320 — 512 — 768 — 1.0 и т.д.) или же он увеличит его с запасом скажем, до 1 мегабита, а затем уменьшит до нужной скорости (см. механизм даунгрейда).

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

Особенности:

  • Параметр speed_increase_step в 0 выставлять нельзя. Минимальное и стандартное значение — 1
  • При значении 2 и новом режиме 900 (на основе текущей скорости) тариф будет переключен не на 2.1, а на 3.1
  • Если новый режим нельзя увеличить на указанное количество режимов, то он будет увеличен до максимально возможного / не будет увеличен (если максимальный)
Новый < текущий

В этом случае запускается механизм даунгрейда. Он сохраняет текущую статистику трафика (не скорости) роутера (вместе с таймстемпом создания), а затем создает задачу на исполнение даунгрейда через определенное время. Это время задается в минутах в параметре:

hold_high_speed_time: 3

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

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

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

Для чего вообще нужен весь этот механизм и почему по умолчанию именно 3 минуты? Объясню, опять же, на примере:

Вы смотрите ролик на YouTube. Он небольшой. Всего пара минут. И пока он скачивался скорость подскочила до 8 мегабит. И вот вы досмотрели ролик и переходите к следующему. Если бы не было этой задержки в механизме, то демон бы уже понизил скорость и при загрузке второго ролика ему пришлось бы снова поднимать скорость.То есть вам придется дожидаться пока скорость вновь будет повышена. В то же время это еще и нагружает и так не шибко стабильный ЛК Yota. Да и согласитесь, за 1 минуту (ну пусть за 2) вы много не сэкономите.

С другой стороны перегибать палку с увеличением задержки тоже не надо. Чем больше значение — тем меньше экономия.

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

Уведомления о смене тарифа через PushBullet

Писалось, как и все остальное, в первую очередь для себя. Но, честно честно говоря, так и не пригодилось, потому как генерирует слишком много спама. Но может кому понравится ;)

Включается это дело все там же, в конфиге:

pb_enabled: true
pb_api: v13WA7HYfwj99gUz8rwvK2m7eLN1uheVxZujAgP8gn2su
pb_devices: [
ujAgP8gn2sasFDsgsWhxs,
ujAgP8gn2mkasnuH2Gtga
]

Как видите, здесь все довольно просто. pb_enabled принимает значения true/false. Назначение очевидно.
pb_api — ваш ключ со страницы www.pushbullet.com/account
pb_devices — список идентификаторов устройств, на которые рассылаются уведомления. Взять можно тут: api.pushbullet.com/v2/devices. При входе вас попросят авторизоваться: просто введите ключ api в поле логина. Полное описание метода тут: docs.pushbullet.com/v2/devices/

Ну что же. Дело сделано — сообщение описание доставлено. Теперь можно и код показать рассказать, как все это дело готовить.

Установка

  • Первым делом идем и устанавливаем на свой роутер OpenWrt. Ну или что-нибудь подобное с SSH, авторизацией по ключам и утилитой ifconfig.
  • После этого нужно найти что-то вроде Raspberry Pi (именно на нем у меня все и крутится). То, на чем можно запустить третий питон и где есть ssh-клиент с авторизацией по ключам. Если ничего такого нет, то вполне сможет сгодиться и полноценный компьютер с Linux/виртуалка с Linux.
  • Настраиваем авторизацию по ключу для подключения к роутеру. Убеждаемся что команда вида (с вашими данными, конечно же) «ssh 192.168.1.1 -p 22 -l root ifconfig» работает без дополнительных вопросов/требования пароля
  • Ставим Python 3 и pip. Изначально все писалось и тестировалось на 3.2.3. На совместимость с более ранними версиями тестов не было
  • Скачиваем исходный код отсюда
  • Распаковываем скачанный архив в любую папку на target-девайсе. Я, например, сделал это в /root/yota.
  • Делаем cd в папку, в которую распаковали архив
  • Выполняем pip3 install -r requirements.txt. Обратите внимание, что pip может иметь другое имя (pip-3.2 например)
  • Редактируем config.yaml. Описания большинства его параметров есть вверху. Остальные, как мне кажется, в представлении не нуждаются. Ну разве что кроме interface. По умолчанию там указан eth0.2. Обычно в openwrt именно так называется интерфейс через который идет обмен траффиком с внешней сетью. Также стоит отметить опцию use_ssh. Если выставить ее в false, то совершенно логично, мониториться будет локальный интерфейс. Ну это на случай если у кого-нибудь в качестве роутера используется полноценный компьютер
  • После этого запускайте main.py. Можно как напрямую (предварительно дав ему права на исполнения), так и передав интерпретатору
  • Если все работает, то устанавливайте supervisor (обычно есть в стандартных репозиториях)
  • Переместите файл yota.conf в папку /etc/supervisor/conf.d/ попутно подправив в нем пути к main.py и, если хотите, к файлу, в который будет писаться лог
  • Перезагрузите supervisor командой supervisorctl reload. Демон должен запуститься автоматически. Посмотрите напоследок логи. В них должно быть что-то вроде:

    2014-07-06 14:16:01,805 [INFO] yota | Initializing
    2014-07-06 14:16:11,497 [INFO] yota.web | CURRENT TARIFF: 512
    2014-07-06 14:16:11,712 [INFO] yota | Initialised
    2014-07-06 14:16:11,718 [INFO] yota | Starting
    2014-07-06 14:16:16,016 [DEBUG] yota.current_speed_provider | Current speed: RX: 5 TX: 9. Summary: 14, Time: 3
    2014-07-06 14:16:17,262 [INFO] yota.speed_control | Downgrade started: 2014-07-06 14:19:16.022307
    2014-07-06 14:16:18,315 [DEBUG] yota.current_speed_provider | Current speed: RX: 7 TX: 19. Summary: 26, Time: 2
    2014-07-06 14:16:20,575 [DEBUG] yota.current_speed_provider | Current speed: RX: 12 TX: 20. Summary: 32, Time: 2

Вот, собственно, и все. Надеюсь, у Yota есть совесть и они не будут «вставлять палки в колеса», как это обычно любят делать в России. В конце концов, людей, способных развернуть у себя такое — не так много. А если учесть, что мало кто из них пользуется Yota, то так и вообще мелочь в масштабах компании.

Традиционно, как и все выходцы песочницы, прошу не очень сильно кидаться помидорами. Ну или посвежее выбрать что-ли :)

Автор: rdvlip

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js