Як налаштувати єСвітло засобами MikroTik
Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті
Усім привіт, мене звати Олександр, я Python Developer. Переважній більшості мешканців України через війноньку доводиться миритися зі стабілізаційними відключеннями світла. Айтішникам треба шукати джерело струму деінде, тіки б позакривати наявні таски, щоб мати змогу скирдувати євро-доляри, а потім — донатити на ЗСУ.
Мені, як й автору топіка, що став моїм натхненням, дуже кортить знати, коли вдома з’являється електрика, щоб якнайшвидше чимчикувати додому, до ліжечка.
Але запропонована автором схема для мене виявилася не робочою, бо відсутність інтернету не завжди є ознакою відсутності світла. Часто буває, що світло в хаті є, але якийсь бісів проміжний вузол знеструмлений, через що доводиться переходити на 4G від {{зарезервовано_під_рекламу_стільникового_оператора}}.
То ж нумо ладнати свого ровера!
У цьому матеріалі:
- ноутбук у якості сенсора наявності електрики для цілей автоматизації;
- скриптова мова MikroTik;
- розбиття коду на модулі
- зберігання даних між викликами скрипта
- надсилання повідомлень у Telegram-канал за допомогою /fetch
- проблема MikroTik з юнікодом
- весь код тут
Перший сценарій налаштування
Я є давнім прихильником роутерів MikroTik (щире орігато мому сенсею, за те що підсадив мене на них). В мене він підключений до ДБЖ на 12V / 7Ah, то ж вимикається останнім зі всієї хатньої техніки й вмикається, відповідно, першим.
Йдея така — най мікротік відслідковує наявність живлення та, якщо щось змінилося, звітує про це, відправляючи повідомлення телеґрам-ботом до спеціально створеного каналу.
Хіба що... як він буде знати, що електрики немає? Бо моє джерело безперебійного живлення не має ніяких мізків, то ж звітувати про статус електромережі жодним чином не вміє. На щастя, поряд з роутером лежить старенький ноутбук для низки потреб, на кшталт телефонної станції з Asterisk-ом для автовідповідача, чи боту з ось цього торішнього топіку.
Звісно, ноут знає, коли він живиться від мережі, а коли від батареї. Треба навчити його ділитися цією інформацією. Там Убунта, тож з цим не було складнощів:
apt install nginx-light
ln -s /sys/class/power_supply/ADP1/online /var/www/html/ac
Зверніть увагу, що замість ADP1 можуть бути інші літери, залежить від пристрою. На моєму робочому ноутбуці, наприклад, там є просто AC.
Тепер за посиланням http://xxx.xxx.xxx.xxx/ac
отримуємо «1» коли електрика є, та «0», коли її немає.
Взагалі-то, можна б було бота завести й на самому ноутбуці замість роутера, але заряду його батареї вистачає значно на менший термін, ніж роутерівського ДБЖ й найголовніше — він не вміє автоматично вмикатися при відновленні електроенергії.
Отже, залишилось трохи — запрограмувати роутер.
В мікротиків є своя скриптова мова (RouterOS Script). Не bash звичайно, але теж досить потужна.
Наш код повинен мати змогу:
- отримувати інформацію від ноутбука про стан електромережі;
- зберігати цей стан для подальшого порівняння;
- надіслати повідомлення у Telegram при зміні стану:
- 0 → 1 — електрика з’явилась
- 1 → 0 — електрика зникла
Відразу скажу — ми розіб’ємо наш код на модулі. Задля зручності та краси. Бо ось, наприклад, відправка повідомлень буде використовуватися у декількох місцях, тож най такі речі будуть окремо.
Імена в скриптів-модулів я роблю з початковим знаком підкреслення (_), щоб наочно їх відрізняти від виконуваних скриптів.
Вбудованої підтримки модулів у мікротикової скриптової мови нема, але це можна легко обійти кількома способами. Я роблю це за допомогою того, що є найсуміснішим з усіма версіями RouterOS. Але про це трохи далі, коли ми будемо «імпортувати» наші модулі.
Спочатку навчимося отримувати інформацію про стан електромережі з ноутбука. Для цього створюємо ось такий модуль «_ac»:
# dont-require-permissions=yes
:local url "http://xxx.xxx.xxx.xxx/ac"
:local acCurr
:do {
:set acCurr [/tool fetch url="$url" output=user as-value]
} on-error={
:return -1
}
:if ($acCurr->"status" = "finished") do={
:if ($acCurr->"data" = "1\n") do={
:return 1
} else={
:return 0
}
} else={
:return -1
}
У першому коментарі я згадую, з якими параметрами має бути створений цей скрипт. Модулі в нас не виконуються напряму ніколи, тож права їм ніякі не потрібні, що й вказано у коментарі.
Тут код отримує дані з ноутбука і якщо там «1\n» (приділить увагу кінцевому символу нової строки), то повернути 1, інакше, повернути 0. У разі помилок, повернути −1.
Ось ми отримали, що хотіли, тепер для порівняння зі статусом з попереднього запиту, треба якось його зберегти. Зберігатимемо його у глобальній змінній, але обов’язково продублюємо у комент до модуля, щоб мати змогу його відновити, бо при перезавантаженні мікротік забуває усі глобальні змінні. При першому виклику відновлюємо значення глобальної змінної з коменту, якщо воно порожнє.
Створимо модуль «_ac_prev»:
# dont-require-permissions=yes
# comment=1
:global AC
:local key "_ac_prev"
:local value "$state"
:if ([:typeof "$AC"] = "nothing") do={
:set AC [/system script get "$key" comment]
}
:if ([:typeof "$value"] = "nothing") do={
:return $AC
} else={
/system script set "$key" comment "$value"
:set AC "$value"
}
Важливо при створенні, щоб уже був коментар. Поставимо туди «1», як ознаку, що світло зараз присутнє.
У цьому модулі, передане значення state зберігається у коментарі самого скрипта. Якщо state невказаний, то код повертає збережене значення.
Перейдемо до модуля відправки повідомлень до телеґрам-каналу. Заздалегідь створюємо бота та публічний канал. Додаємо бота адміном.
Примусити бота відправляти повідомлення можна простим GET-запитом. Ось таким модулем будемо робити відправку — «_telegram»:
# dont-require-permissions=yes
:local apiUrl "https://api.telegram.org"
:local token "NNN:XXXXX"
:local chatId "NNNN"
:local text "$message"
/tool fetch url="$apiUrl/bot$token/sendMessage?chat_id=$chatId&text=$text" keep-result=no
З token усе зрозуміло, його видає @BotFather. То от як отримати chatId? Треба після додавання боту до каналу, зробити запит:
curl https://api.telegram.org/bot{token}/getUpdates
Там можна знайти chat_id каналу (зазвичай від’ємне число для каналів та груп).
З модулями покінчили, тепер переходимо до скриптів. Ось, скрипт для відправки повідомлення про відсутність світла — «ac-offline»:
:local acPrev [:parse [/system script get _ac_prev source]]
:local telegram [:parse [/system script get _telegram source]]
:log info message="AC offline"
local message "\F0\9F\8C\9A\20\D0\A1\D0\B2\D1\96\D1\82\D0\BB\D0\B0\20\D0\BD\D0\B5\D0\BC\D0\B0\E2\80\A6"
$telegram message=$message
$acPrev state=0
У перших двох рядках ми «імпортуємо» наші модулі _ac_prev та _telegram. Сприймайте це так, як ніби пітоністи б писали:
from . import _ac_prev as acPrev
from . import _telegram as telegram
Уважне око побачило тут UTF8-закодовану строку. У мікротиків є проблеми з підтримкою юнікоду, тож строка для відправки у Telegram кодується ось таким дивоглядним чином. Також перед викликом коду з модуля _telegram потрібно використовувати проміжну змінну, бо якщо відправити туди юнікод напряму, то відбувається додаткове перетворення строки, яке її нівечить.
Наприкінці зберігаємо «0» як попередній стан мережі.
Код скрипту «ac-online» вигляда відповідним чином:
:local acPrev [:parse [/system script get _ac_prev source]]
:local telegram [:parse [/system script get _telegram source]]
:log info message="AC online"
:local message "\F0\9F\92\A1\20\D0\A1\D0\B2\D1\96\D1\82\D0\BB\D0\BE\20\D1\94\21"
$telegram message=$message
$acPrev state=1
Давайте тепер зв’яжемо усе це у єдиний скрипт — «ac-check»:
# policy=read,write,policy,test
:local ac [:parse [/system script get _ac source]]
:local acPrev [:parse [/system script get _ac_prev source]]
:local acState [$ac]
:local acPrevState [$acPrev]
:if ($acState = 1 && $acPrevState = 0) do={
/system script run ac-online
} else={
:if ($acState = 0 && $acPrevState = 1) do={
/system script run ac-offline
}
}
Тут все просто. Якщо стан електромережі змінюється з 0 на 1, то викликаємо ac-online. Якщо з 1 на 0, то ac-offline. Нічого не робимо, якщо стан не змінюється або неможливо виявити поточний стан (модуль _ac повертає −1).
Тепер запускаємо ac-check у плановику, наприклад, раз на 20 секунд. Готово! Можна сміливо гуляти містом, а бот повідомить нас, коли вдома з’явиться світло.
Другий сценарій налаштування
Розгляньмо ще випадок, коли вся електрика зникла на триваліший термін, ніж вистачає запасу акумуляторів, та все взагалі вирубилось. Після її повернення мій ноутбук не прокидається, але прокидається роутер. Він отримує −1 замість стану мережі, але ж в нього струм є, то нехай він звітує про це.
Буде в нас такий скрипт для спешл кейсу — «ac-restore»:
# policy=read,write,policy,test
:local ac [:parse [/system script get _ac source]]
:local acPrev [:parse [/system script get _ac_prev source]]
:local acState [$ac]
:local acPrevState [$acPrev]
:if ($acState = -1 && $acPrevState = 0) do={
/system script run ac-online
}
Його запускатимемо лише при старті роутера. Якщо на момент його запуску ноутбук недоступний, а попередній стан був 0, то скрипт викликає ac-online. Якщо ноутбук буде доступний, то відпрацює ac-check за розкладом.
От який розумничок в нас вийшов!
Але підемо далі! Нехай він нас ще попереджає про можливі відключення за графіком. Нехай за півгодини до планового стабілізаційного відключення бот нас попередить про це. В мене графіки якийсь період сяк-так стабільні (± 1 годину), тож нагадування стане в пригоді.
Скрипт нагадування — «ac-shutdown» — такий:
# policy=read,test
:local telegram [:parse [/system script get _telegram source]]
:local message ( \
"\E2\8F\B1\EF\B8\8F\20\D0\97\D0\B0\20\33\30\20\D1\85\D0\B2\D0\B8\D0\BB\D0" \
. "\B8\D0\BD\20\D0\BC\D0\BE\D0\B6\D0\BB\D0\B8\D0\B2\D1\96\20\D1\81\D1\82" \
. "\D0\B0\D0\B1\D1\96\D0\BB\D1\96\D0\B7\D0\B0\D1\86\D1\96\D0\B9\D0\BD\D1\96" \
. "\20\D0\B2\D1\96\D0\B4\D0\BA\D0\BB\D1\8E\D1\87\D0\B5\D0\BD\D0\BD\D1\8F" \
)
$telegram message=$message
Просто відсилання повідомлення. Зверніть увагу, як можна перенести довгу строку.
Розклад його виклику, як я й казав, за 30 хвилин до відключень за графіком. Я написав декілька утиліт, щоб спростити роботу з розгортання бота. Там є й такий, що перетворює графік з текстового файлу на команди для мікротика, щоб багато не довелось тицяти руками. Можна, гадаю, взагалі щось типу Ansible прикрутити, для масового розгортання.
Плановик врешті виглядає десь так:
А так скрипти:
Цього бота можна вдосконалювати та, наприклад, відв’язати його від ноутбуку. Натомість отримувати дані про стан мережі іншим зручним способом, якщо мій вам не підходить. Треба лише замінити єдиний модуль — _ac.
А так це виглядає з боку користувача:
Ось репозиторій з кодом.
Якось так... Не наступіть у темряві на улюбленця.
15 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів