Пристрій сам стежить за батареєю, мережею, погодою і може писати в Telegram або змінювати режим інвертора.
Правила — короткий скрипт (схожий на спрощений JavaScript). Файл на пристрої: /automation.js, до 6144 байт (кирилиця — 2 байти на символ; лічильник під редактором).
Перевірка йде кожні ~1.5 с, починаючи з 20-ї секунди після старту.
edge/at теж не змінюється).▶ running — правила виконуються;
⛔ … — скрипт зупинений, поруч причина і номер рядка (той самий текст, що і в журналі пристрою, код 16).
Зберегти можна і з помилкою (правки не пропадуть), але правила при цьому не виконуються, доки помилку не виправлено.
Збереження скидає змінні та стан edge/every/held/at.
fn setup() {
}
fn tick() {
if (edge(pid(100) < 50)) {
tg("Мережа пропала, батарея [pid110]%");
}
}
pid(100) — напруга мережі. [pid110] у тексті заміниться на заряд батареї. Так можна підставляти будь-який PID.
Це один і той самий файл. Міняєте в одній вкладці — бачите в іншій після збереження / перемикання.
Картка = одне просте правило: «коли … тоді зробити …». У списку видно назву і вмикач. Клік по назві відкриває одну картку.
У картці можна:
// @vis Мережа пропала
if (edge(pid(100) < 50)) { tg("Мережа пропала"); }
// @endvis
Нова картка з Visual сама ставить ці рядки. Складний код (else, вкладені if, свої формули) пишіть у Script без @vis — тоді картки його не чіпають і не ламають.
Під картками — «Показники на головній» (expose). Шаблони внизу — готові типові сценарії.
Тут живуть змінні, свої функції, if / else, нічний тариф із двома гілками. Visual цей код не затирає.
// Коментар до кінця рядка. Пристрій його ігнорує.
let min_soc = 25; // глобальна змінна (пам'ять між циклами)
fn setup() { // один раз після збереження або ребута
expose(800, "Поріг", "%");
set(800, min_soc);
}
fn tick() { // кожні ~1.5 секунди
set(800, min_soc);
if (pid(110) < min_soc && every(600)) {
tg("Батарея [pid110]%");
}
}
fn setup() — старт: показати значення на головній, початкові set.fn tick() — робоче серце. Усі «якщо батарея…» пишіть сюди.let — тільки зверху, не всередині функції. Ім’я до 11 латинських символів: min_soc ок, night_min_soc — Check скаже name>11.;.if і else завжди фігурні дужки { } (у else if дужки має сам if). Без них Check скаже помилку.// (і повні рядки, і в кінці рядка) пристрій ігнорує; номери рядків у помилках збігаються з редактором.Циклів for / while немає навмисно: скрипт завжди закінчується і не може «зависнути».
Найчастіша помилка новачка: написати if (pid(110) < 30) { tg("..."); } — і Telegram сипле кожні 1.5 с. Оберіть тип спрацювання:
| Хочу | Пишіть | Простими словами |
|---|---|---|
| Один раз, коли сталося | if (edge(УМОВА)) { ... } |
Спрацює в момент переходу «ні → так». Після ребута вже-так не стріляє знову. |
| Нагадувати, поки умова тримається | if (УМОВА && every(600)) { ... } |
Раз на 600 с (10 хв), поки умова true. every ставте справа. Перший виклик лише заводить таймер (ще false) — перше спрацювання через N секунд. |
| Чекати, поки протримається N секунд | if (held(УМОВА, 300)) { ... } |
Стає true і лишається true кожен цикл, поки умова жива. Для одноразової дії: edge(held(…, 300)). Скидання — щойно умова стала false. |
| Рівно о 23:00, раз на добу | if (at("23:00")) { ... } |
Один раз на календарну добу в цю хвилину. Після ребута в ту саму хвилину може спрацювати ще раз. |
| Весь час, поки true | if (УМОВА) { ... } |
Кожен цикл. Для Telegram / запису в інвертор майже завжди зайве. |
if (edge(pid(100) < 50)) { tg("Мережа пропала"); }
Максимум — кілька «коли» в одному tick:
if (edge(pid(100) < 50)) { tg("Мережа пропала, SOC [pid110]%"); }
if (edge(pid(100) > 50)) { tg("Мережа [pid100]V"); }
if (pid(110) < 25 && every(600)) { tg("Низький SOC [pid110]%"); }
if (pid(509) > 40 && every(1800)) { tg("Спека [pid509]°C"); }
Порівняння як у школі: > < >= <= == !=. Логіка: && (і), || (або), ! (не). Дужки ( ) — щоб не плутати порядок.
Можна мішати PID між собою: pid(106) - pid(117) > 1500 (надлишок сонця).
if без edge/every виконується кожні ~1.5 с. Telegram у такій гілці засипле чат (і все одно ліміт — 1 повідомлення за цикл). Нижче — лише синтаксис гілок; у реальних правилах додавайте edge або every.
if (pid(110) < 20) {
set(800, 1);
} else {
set(800, 0);
}
set(800, …) на expose-змінну головної безпечний кожен цикл (це RAM, не команда інвертору). Для pid132 так робити не варто.
Мова розуміє else if. Гілки перевіряються зверху вниз, спрацює перша правдива.
if (pid(110) < 15) {
set(800, 1);
} else if (pid(110) < 30) {
set(800, 2);
} else if (pid(110) > 90) {
set(800, 3);
} else {
set(800, 0);
}
if (at("23:00")) {
if (pid(513) >= 3000 && pid(110) >= 25) {
set(132, 2);
} else {
set(132, 1);
set(131, 2);
tg("Нічний заряд, завтра [pid513]");
}
}
at зручніше спочатку «чи зараз 23:00», а вже всередині — погода / SOC.
Не пишіть at("23:00") && pid(513) < 1000 навпаки: якщо прогноз ще «не той», спрацювання на сьогодні вже витрачене.
умова ? якщо_так : якщо_ні
let flag = 0; // зверху файлу, не всередині fn
fn tick() {
flag = pid(509) > 40 ? 1 : 0;
if (edge(flag == 1)) { tg("Спека [pid509]°C"); }
}
У фігурних дужках можна кілька рядків — виконаються підряд.
| Виклик | Що робить |
|---|---|
set(132, 2) | Записати параметр (режим інвертора, GPIO, Tasmota, web-кнопка). Для списків — номер пункту, не ампери: дивіться «Список PID». Якщо параметр уже має це значення — запис пропускається (кнопки спрацьовують завжди). Після невдалого запису в інвертор решта set цього циклу пропускаються, щоб не зупиняти читання даних. |
tg("текст [pid110]%") | Повідомлення в Telegram. Одне на цикл tick. Якщо підписка закінчилась — не відправляється. |
log("...") | Рядок у журналі пристрою (код 12). До 3 за цикл, текст до 64 символів. |
http("http://192.168.0.4/cm?cmnd=Power+1") | GET у LAN, таймаут ~1.5 с, відповідь не читається. Один за цикл. Пробіл у URL → +. [pid110] в URL підставляється як у tg. Запит на IP самого пристрою ігнорується. |
if (at("07:00")) { set(132, 2); }
Максимум — кілька дій разом:
if (pid(513) < 1000 && at("23:00")) {
set(132, 1);
set(138, 4);
tg("Нічний заряд, завтра [pid513] Wh/m², SOC [pid110]%");
log("night charge");
}
set на PID інвертора (пріоритети, струми) — реальна зміна налаштувань. Спочатку Check, потім збережіть. Для проби зручний pid звуку (часто 123) або змінна на головній (800–807).
Годинник пристрою (NTP). now() — число ГГХХ: 7:30 → 730, 23:00 → 2300.
| Функція | Приклад | Коли true |
|---|---|---|
between("07:00", "21:00") | день | З 07:00 по 21:00 включно |
between("22:00", "06:00") | ніч | Через північ: 22:00…00:00…06:00 |
at("23:00") | момент | Один раз на добу в цю хвилину (після ребута в ту саму хвилину — знову) |
hm("07:30") | → 730 | Щоб писати now() >= hm("07:00") && now() < hm("21:00") |
dow() | 0…6 | 0 = неділя, 6 = субота |
if ((dow() == 0 || dow() == 6) && at("10:00")) {
tg("Вихідний, SOC [pid110]%");
}
Не розбирайте pid611 у скрипті — для умов є now / at / between. Поки годинник не синхронізовано (одразу після ребута без інтернету), now() = 9999, а between і at — false, тож нічні правила помилково не спрацюють.
До 16 штук. Живуть між циклами tick, скидаються при ребуті й при збереженні скрипта. На флеш самі не пишуться.
let peak = 0;
fn tick() {
peak = max(peak, pid(106));
if (at("23:50")) { peak = 0; }
}
Номер PID 800–807 (або вільний). Не потрапляє в графіки дня. Те саме число може зайняти датчик DS18B20 — тоді візьміть наступний вільний. Значення expose можна змінити з головної, сторінки інвертора або Telegram: це запис у RAM, без Modbus. Наступний set у скрипті перезапише його.
fn setup() {
expose(800, "Пік PV", "W");
}
fn tick() {
set(800, peak);
}
У Visual те саме — блок під картками.
Разом до 8 функцій, рахуючи setup і tick (тобто до 6 своїх). Без параметрів, ім’я до 11 символів, вкладеність викликів до 4 (Check: depth). return — результат.
fn excess() {
return pid(106) - pid(117);
}
fn tick() {
if (edge(held(excess() > 1500 && pid(110) > 80, 300))) {
set(1000, 1);
}
}
return; всередині tick — решта правил цього циклу не виконається.
| Функція | Навіщо |
|---|---|
pid(n) | Поточне значення. Порожньо → -1. |
set(n, v) | Запис параметра. |
tg("...") log("...") http("...") | Telegram / журнал / HTTP GET. |
now() hm("HH:MM") | Час як ГГХХ. |
between("A","B") | Проміжок; якщо A > B — через північ. |
at("HH:MM") | Один раз на календарну добу в цю хвилину. Після ребута в ту саму хвилину може спрацювати ще раз. |
dow() | День тижня 0…6. |
edge(x) every(sec) held(x, sec) | Фронт / період / утримання. Стан прив’язаний до місця виклику в тексті. |
changed(x) | 1, коли число змінилось (поріг 0.001). Перший цикл після збереження — 0, щоб не стріляти на старті. |
cooldown(sec) | 1 одразу, далі 0, поки не мине N секунд. На відміну від every, перший tick не «мовчить». |
hyst(val, on, off) | Гістерезис. Якщо on ≥ off: увімкнути при val ≥ on, вимкнути при val ≤ off, між ними — попередній стан. |
sunrise() sunset() | Схід/захід як ГГХХ з погоди. Немає прогнозу → 9999 (умова не спрацює о 00:00). |
avg(pid, sec) | Плавне середнє PID (менше хибних спрацювань). |
min(a,b) max(a,b) abs(x) | Математика. |
expose(pid,"ім’я","од.") | Показник на головній. До 8. |
Також: + - * / %, порівняння, && || !, ?:. == з допуском 0.001 (25 і 25.0005 вважаються рівними).
Мережа пропала / з’явилась
if (edge(pid(100) < 50)) {
tg("Мережа пропала! Батарея [pid110]% ([pid109]V)");
}
if (edge(pid(100) > 50)) {
tg("Мережа [pid100]V");
}
Два окремі if, не else if: інакше другий edge не оновить стан, поки спрацював перший, і «мережа з’явилась» можна пропустити.
Ніч на батареї, якщо SOC високий
if (edge(between("22:00", "06:00") && pid(110) > 60)) {
set(132, 2);
tg("Ніч, батарея [pid110]%");
}
Надлишок PV 5 хв → розетка Tasmota (pid1000)
if (edge(held(pid(106) - pid(117) > 1500 && pid(110) > 80, 300))) {
set(1000, 1);
}
if (edge(pid(110) < 40 || pid(106) < 500)) {
set(1000, 0);
}
held сам по собі далі true кожен цикл — без edge команда set молотила б реле кожні 1.5 с.
GPIO: світло о 18:00 / 22:00 (режим піна задається в налаштуваннях параметра)
if (at("18:00")) { set(704, 1); }
if (at("22:00")) { set(704, 0); }
Складніше: нічний заряд за прогнозом + ранок SBU
Пишіть у Script без @vis. Пороги й індекси pid131/132/138 залежать від вашого інвертора — дивіться «Список PID».
let sun_ok = 3000;
let min_soc = 25;
let grid_a = 4;
fn tick() {
if (at("23:00")) {
if (pid(513) >= sun_ok && pid(110) >= min_soc) {
set(131, 3);
set(132, 2);
} else {
set(132, 1);
set(131, 2);
set(138, grid_a);
tg("Нічний заряд. Завтра [pid513], SOC [pid110]%");
}
}
if (at("07:00")) {
set(132, 2);
set(131, 1);
}
}
pid513 — прогноз сонця на завтра (Wh/m²). pid506 — «чи ясно зараз», для завтра не підходить.
Низький SOC без спаму
if (pid(110) < 25 && cooldown(1800)) {
tg("Батарея [pid110]%");
}
Гістерезис SOC (Script, бо є else)
if (hyst(pid(110), 25, 35)) { set(132, 1); }
else { set(132, 2); }
Тут on < off: увімкнути, коли SOC ≤ 25, вимкнути, коли SOC ≥ 35. Між 25 і 35 лишається попередній стан.
Світло від світанку до заходу
if (between(sunrise(), sunset())) { set(704, 1); }
Поки погода не завантажена, sunrise() і sunset() дають 9999 і умова хибна.
tg і коментарях — 2 байти на символ; лічильник під редактором). 16 × let, 8 × fn (разом із setup/tick), 8 × expose, 8 × avg, 32 «слоти» для edge/every/held/at/changed/cooldown/hyst._, до 11 символів.tg/log/http/at/…tg в одному циклі — піде лише перший. Додатково не більше 12 tg на годину; зайві тихо відкидаються.now, лічильник tg) і журнал рядків Automation. Попереднє збереження — кнопка «Відновити попередній» (файл /automation.bak).warn: bare if+tg/set не блокує збереження: голий if з tg або записом інвертора слатиме команду кожні ~1.5 с. set на 800–807 не попереджається.edge/at — окреме місце в тексті. Не копіюйте «той самий» виклик двічі, думаючи що це один лічильник.| Слово | Що не так |
|---|---|
{ } ( ) ; , = : | Очікувався цей символ у вказаному рядку (незакрита дужка, забута ;). |
undef | Невідома змінна: let для неї немає (або описка в імені). |
unknown fn, need ( | Невідома функція / функція без дужок (). |
str | Тут потрібен текст у лапках (at("23:00"), expose(800, "Ім’я", "од.")). |
name>11 | Ім’я змінної/функції довше за 11 символів. |
stmt, top, prim, bad char | Несподіваний текст: на верхньому рівні дозволено лише let і fn; усередині — if, присвоєння, виклик, return. |
nested let/fn, no loops | let/fn усередині функції; for/while не підтримуються. |
lets, fns, slots, avg, depth | Перевищено ліміт: 16 змінних, 8 функцій, 24 edge/every/held/at, 8 avg, 4 рівні вкладених викликів. |
too long, too big, nomem, load failed | Скрипт понад 6144 байт / не вистачило пам’яті / файл не прочитано. |
busy, retry | Пристрій саме виконує tick (наприклад, чекає відповідь інвертора) — натисніть ще раз. |
stopped: 3 crashes | Пристрій тричі підряд аварійно перезавантажився — автоматизацію вимкнено як запобіжник. Перевірте скрипт і натисніть Зберегти (або перезавантажте пристрій із меню Налаштування). |
Помилки під час виконання (наприклад, undef у правилі) показуються там само, у статусі над кнопками, і потрапляють у журнал пристрою (сторінка Інвертор, код 16) один раз — до наступного збереження. Правила вище помилкового рядка продовжують працювати.
if, беріть edge або every.held / avg.pid(n) може бути -1 (ще не прочитано).⛔ з причиною означає, що скрипт не виконується взагалі.edge «не спрацювало» одразу після збереження → умова вже була true в момент збереження; edge чекає переходу «ні → так». every(N) вперше спрацює через N секунд.@vis/@endvis навколо нього, залиште лише в Script.Повний список параметрів — кнопка Список PID на сторінці автоматизації. GPIO 7xx, Tasmota 1000+, BMS 9xx — як у вашій таблиці.
Якщо є /automation_PARAMS.json, а /automation.js ще немає — пристрій сам конвертує правила (без інтернету). JSON лишається копією. Старі правила стають картками з @vis.
Прошивка 4.24+. Check → Зберегти. Складне — у Script без карток; просте — у Visual з @vis.