↩ Назад

English

Автоматизація інвертора

Пристрій сам стежить за батареєю, мережею, погодою і може писати в Telegram або змінювати режим інвертора. Правила — короткий скрипт (схожий на спрощений JavaScript). Файл на пристрої: /automation.js, до 6144 байт (кирилиця — 2 байти на символ; лічильник під редактором).

Перевірка йде кожні ~1.5 с, починаючи з 20-ї секунди після старту.

1. З чого почати

  1. Відкрийте сторінку Автоматизація.
  2. Вкладка Script — повний текст. Visual — прості картки.
  3. Натисніть Check — пристрій перевірить синтаксис без Telegram, HTTP і записів в інвертор (стан edge/at теж не змінюється).
  4. Якщо OK — Зберегти.
Чи працює скрипт зараз? Рядок статусу над кнопками: ▶ running — правила виконуються; ⛔ … — скрипт зупинений, поруч причина і номер рядка (той самий текст, що і в журналі пристрою, код 16). Зберегти можна і з помилкою (правки не пропадуть), але правила при цьому не виконуються, доки помилку не виправлено. Збереження скидає змінні та стан edge/every/held/at.
Найменший робочий скрипт. Скопіюйте, Check, Зберегти. Якщо мережа пропаде — прийде повідомлення.
fn setup() {
}

fn tick() {
  if (edge(pid(100) < 50)) {
    tg("Мережа пропала, батарея [pid110]%");
  }
}

pid(100) — напруга мережі. [pid110] у тексті заміниться на заряд батареї. Так можна підставляти будь-який PID.

2. Дві вкладки: Visual і Script

Це один і той самий файл. Міняєте в одній вкладці — бачите в іншій після збереження / перемикання.

Visual — прості картки

Картка = одне просте правило: «коли … тоді зробити …». У списку видно назву і вмикач. Клік по назві відкриває одну картку.

У картці можна:

Visual показує лише блоки, обгорнуті так:
  // @vis Мережа пропала
  if (edge(pid(100) < 50)) { tg("Мережа пропала"); }
  // @endvis
Нова картка з Visual сама ставить ці рядки. Складний код (else, вкладені if, свої формули) пишіть у Script без @vis — тоді картки його не чіпають і не ламають.

Під картками — «Показники на головній» (expose). Шаблони внизу — готові типові сценарії.

Script — повна мова

Тут живуть змінні, свої функції, if / else, нічний тариф із двома гілками. Visual цей код не затирає.

3. Скелет скрипта

// Коментар до кінця рядка. Пристрій його ігнорує.

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]%");
  }
}

Циклів for / while немає навмисно: скрипт завжди закінчується і не може «зависнути».

4. Коли спрацьовує правило

Найчастіша помилка новачка: написати 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"); }

5. Умови: if, else, кілька рівнів

Порівняння як у школі: > < >= <= == !=. Логіка: && (і), || (або), ! (не). Дужки ( ) — щоб не плутати порядок.

Можна мішати 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)

Мова розуміє 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 всередині if

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 навпаки: якщо прогноз ще «не той», спрацювання на сьогодні вже витрачене.

Короткий запис без if

умова ? якщо_так : якщо_ні

let flag = 0;          // зверху файлу, не всередині fn
fn tick() {
  flag = pid(509) > 40 ? 1 : 0;
  if (edge(flag == 1)) { tg("Спека [pid509]°C"); }
}

6. Дії

У фігурних дужках можна кілька рядків — виконаються підряд.

ВикликЩо робить
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).

7. Час доби

Годинник пристрою (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…60 = неділя, 6 = субота
if ((dow() == 0 || dow() == 6) && at("10:00")) {
  tg("Вихідний, SOC [pid110]%");
}

Не розбирайте pid611 у скрипті — для умов є now / at / between. Поки годинник не синхронізовано (одразу після ребута без інтернету), now() = 9999, а between і at — false, тож нічні правила помилково не спрацюють.

8. Змінні, expose, свої функції

Змінні

До 16 штук. Живуть між циклами tick, скидаються при ребуті й при збереженні скрипта. На флеш самі не пишуться.

let peak = 0;

fn tick() {
  peak = max(peak, pid(106));
  if (at("23:50")) { peak = 0; }
}

Показник на головній — expose

Номер 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 — решта правил цього циклу не виконається.

9. Довідник функцій

ФункціяНавіщо
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 вважаються рівними).

10. Готові рецепти

Мережа пропала / з’явилась

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 і умова хибна.

11. Обмеження і типові помилки

Слова помилок Check / статусу

СловоЩо не так
{ } ( ) ; , = :Очікувався цей символ у вказаному рядку (незакрита дужка, забута ;).
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 loopslet/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) один раз — до наступного збереження. Правила вище помилкового рядка продовжують працювати.

Часті помилки

Повний список параметрів — кнопка Список PID на сторінці автоматизації. GPIO 7xx, Tasmota 1000+, BMS 9xx — як у вашій таблиці.

12. Міграція зі старого JSON

Якщо є /automation_PARAMS.json, а /automation.js ще немає — пристрій сам конвертує правила (без інтернету). JSON лишається копією. Старі правила стають картками з @vis.

Відкат на прошивку ≤ 4.23 читає лише JSON на момент конвертації. Правки вже в скрипті назад не поїдуть. Спочатку ставте beta OTA, переконайтесь, потім стабільну.

Прошивка 4.24+. Check → Зберегти. Складне — у Script без карток; просте — у Visual з @vis.