Як налаштувати єСвітло засобами 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.

А так це виглядає з боку користувача:

Ось репозиторій з кодом.

Якось так... Не наступіть у темряві на улюбленця.

👍ПодобаєтьсяСподобалось9
До обраногоВ обраному1
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

у мене 750Gr3, він має вольтметр.
Якщо така опція є, ділюся

:global LastM;
:global state;
:global v220;
:global Vnow;
:set Vnow [/system health get voltage];
:if ($Vnow >= 138) do={:set v220 true} else={:set v220 false};
:if ($v220= true) do={:set state “OK”} else={:set state “OFF”};
:if ($v220= false && $Vnow<= 138) do={:set state “LOW”};
:if ($LastM!= $state && $state= “OK”) do={/system script run Send220OK};
:if ($LastM!= $state && $state= “OFF”) do={/system script run Send220OFF};
:if ($LastM!= $state && $state= “LOW”) do={/system script run Send12LOW};

якщо він на гарантованому живленні, то я би рекомендував дивитись напругу на Мікротє, коли 13.8В це від мережі, 13 і менше акум і 11 критична напруга, мовляв зараз пропаде
/system health print

В мене /system health print видає порожню строку.

Дякую, мікротік це добре, підглянув декілька трюків. Але все одно контактор + sonoff, який уміє відправити зміну стану по web-api, а відтак відправити повідомлення в телеграм працює чудово і налаштувань на 20 хвилин.

Якщо є бажання, можете додати свій канал на нашу мапу — esvitlo.org.ua

Дякую за статтю. Також зробив через стандартну NetWatch тулзу. Підключив старенький роутер як індикатор світла

Якщо є бажання, можете додати свій канал на нашу мапу — esvitlo.org.ua

Мікротік і стандартна тулза NetWatch — 1 рядок коду

UPD: замість ноута, як індикатора наявності електрики, встромив USB-to-Ethernet-адаптер прямісінько у USB-зарядку 5V і підключив іншим кінцем до порту мікротіка.

Звісно, на пінги там ніхто не відповіда, але лінк присутній. А коли світло зникає, зникає і лінк. Вийшло надійніше, ніж з ноутом.

Модуль _ac спростився до:

# dont-require-permissions=yes

:local interface "etherX"

:if ([/interface get "$interface" running] = yes) do={
  :return 1
} else={
  :return 0
}

І можна викинути ac-restore

Якщо не MicroTik, то прошити роутер під OpenWRT — і скрипт можна писати на shell, python чи lua.

плюс 50 грифіндору за єблю з скріптами мікротіка )
але чому не можна з роутера послати WOL на лептоп і зробити все на лептопі?
я допускаю що може нема опції запускати автоматично, але можна ж просто послати ping з роутера регулярно
може я щось пропустив, крута стаття в будь-якому разі )

Для прийняття пакетів WOL систему треба коректно вимкнути або ввести в стан гібернації
Інакше мережева карта просто не знає що треба чекати magic packet

Ну взагалі в біосах це опція, але підручний лептоп не вибирають, і не сервер все таки. Може й не варіант

В лептопах це ніяк через bios зазвичай не керується окрім On/Off (якщо взагалі є опції acpi)

На стаціонарному ПК залежить від материнки. Може бути вибір керуватиметься WOL засобами ОС чи bios/uefi. Але і в цьому випадку маса нюансів.

Цей конкретний ноут не вміє WoL. Через ethtool якісь режими перемикаються, але жоден не працює.

Підписатись на коментарі