Для розробників

Локальний API плати СтрумОК

Повний перелік HTTP-методів, які віддає сама станція у вашій домашній мережі — з параметрами, прикладами відповідей і готовими curl-командами під кожен.

Чому тут немає кнопки «Виконати»

Цей сайт працює по HTTPS, а плата в локальній мережі — по звичайному HTTP. Браузер блокує такий запит просто зі сторінки ще до мережі (mixed content) — стандартна політика безпеки, однакова в Chrome, Firefox і Safari, і сайт її обійти не може. Тому запит виконує не сторінка, а ви самі — через curl у терміналі.

Як користуватись

  1. 1Впишіть IP станції у полі вгорі (наприклад 192.168.8.54) — воно запамʼятається в цьому браузері.
  2. 2Знайдіть потрібний метод нижче, за потреби змініть приклади параметрів і скопіюйте готову curl-команду.
  3. 3Виконайте команду в терміналі (потрібен встановлений curl) і вставте відповідь назад у поле під командою — вона акуратно відформатується.

Вхід, ключі API і Home Assistant

Вхід — за бажанням

За замовчуванням плата не питає пароля. Власник може ввімкнути вхід у веб-панелі плати або в застосунку («Захист станції»). Тоді всі зміни й дії (налаштування, команди, перезапуск, оновлення, файли панелі, скани) виконуються лише після входу (POST /api/auth/login) або з ключем API, інакше плата відповідає 401 {"error": "auth"}.

Читання без входу (ropen)

Навіть з увімкненим входом читання стану (/api, /api/history, /api/errors, /api/cfg) лишається відкритим, поки ввімкнено «читання без входу» — так за замовчуванням. Вимкнете його — читання теж вимагатиме входу чи ключа.

Ключі API

Для програм без людини — довгі ключі: заголовок Authorization: Bearer <ключ>. Ключ app видає плата для застосунку СтрумОК, ключі ha:<назва> власник створює в панелі (після входу) для Home Assistant.

Як підключити Home Assistant

Встановіть інтеграцію СтрумОК (HACS або custom_components/strumok), вкажіть IP плати. Поки читання без входу відкрите, більше нічого не треба. Якщо власник закрив читання — створіть у панелі плати ключ ha:<назва> і впишіть його в поле «Ключ API» інтеграції. MQTT-місток і REST-клієнт HA вхід не зачіпає: там клієнт — сама плата.

Забули пароль

Довге натискання кнопки BOOT на платі вимикає вхід — так само, як для повторного налаштування Wi-Fi.

Показано 62 методи

Стан і діагностика

10
GET/

Головна сторінка панелі

Віддає HTML веб-панелі. Панель — це набір файлів на SD-картці (webpanel.cpp:handlePage → serveAsset("index.html")); якщо картки немає або набір ще не залито, плата показує вбудовану в прошивку recovery-сторінку (стиснуту gzip, з флешу), яка дозволяє залити набір панелі й прошивку.

Ніколи не блокується LightGate (правило дозволяє все поза /api/ і /provision/). Якщо набору на SD нема — це не помилка, а штатний recovery-режим.

curl-команда

curl -s "http://IP_ПЛАТИ/" | jq .

Приклад відповіді

text/html (саме розмітка панелі або recovery-сторінка). Не JSON.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api

Повний зліпок стану плати

Головний статусний ендпоінт (handleApi → fillStatus, main.cpp:7808). Формує один великий JSON-документ з усіма підсистемами плати: живі метрики інвертора/батареї (live), стан шини (bus), SD-картки (sd), EVSE/пошук зарядки (evse), Wi-Fi (wifi), Home Assistant (ha), памʼять (heap, ram), перезавантаження/аварії (rst, safe), OTA (upd), провізіонінг (prov), UI-набір (ui) тощо. Це канал ЛОКАЛЬНОГО РЕЖИМУ застосунків (LOCAL-MONITORING-CONTRACT.md): телефон опитує його кожні 3-5 с, коли немає інтернету, читаючи саме блок live.m.

Ключі live.m канонічні й ідентичні хмарним (декодуються profiles::decodeIn); порожній live (без m) означає, що плата не впізнала карту регістрів. Поле evse.scanm заповнюється лише ПІСЛЯ виклику /api/evsescan. evse.kw — у ВАТАХ (не кВт, попри назву); evse.cur — заданий струм, А; evse.car — чи є авто на кабелі керованого слота; evse.session_kwh — енергія поточної сесії, кВт·год (лише Eveus; для інших зарядок поля немає); evse.min_a/evse.max — межі струму, які накладає плата. НІКОЛИ не блокується LightGate — це єдиний /api/* шлях, явно виключений з перевірки (webpanel.cpp:46, uri != "/api").

curl-команда

curl -s "http://IP_ПЛАТИ/api" | jq .

Приклад відповіді

Приклад (скорочено, справжня відповідь набагато більша):
{
  "dev": "SK-E89F6DB694F0",
  "fw": "0.15.53",
  "up": 183920,
  "heap": 42996,
  "heapmin": 8180,
  "live": {
    "m": {
      "pv_power_w": 1240,
      "pv_voltage_v": 358.2,
      "batt_soc_pct": 67,
      "batt_power_w": -320,
      "grid_power_w": 0,
      "load_power_w": 920
    },
    "map": "deye-hybrid",
    "src": "local",
    "age": 4
  },
  "bus": { "...": "стан опитування Modbus/RS-485" },
  "sd": { "...": "наявність картки, вільне місце" },
  "evse": { "on": 1, "cur": 16, "kw": 3680, "car": true, "session_kwh": 4.25, "min_a": 7, "max": 32, "scanm": {"strat":"par","ms":1840,"cand":3,"resp":1}, "cands": [ {"ip":"192.168.1.42","code":200,"conf":true,"mac":"AA:BB:CC:DD:EE:FF"} ] },
  "wifi": { "ip": "192.168.1.30" },
  "safe": 0,
  "upd": { "...": "стан перевірки/заливки OTA" },
  "prov": { "...": "стан провізіонінгу" }
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/errors

Журнал внутрішніх помилок

Віддає кільцевий журнал подій/помилок плати (errlog::toJson, errlog.cpp:215) — від найновішого до найстарішого запису. Джерело для питання «чому плата перезавантажилась/зависла/що сталось».

t — unix-час UTC запису, up — час роботи плати в секундах (uptime) на момент запису, src — підсистема-джерело, msg — текст, rep — зʼявляється лише коли той самий запис повторився понад 1 раз. НЕ блокується LightGate (у білому списку).

curl-команда

curl -s "http://IP_ПЛАТИ/api/errors" | jq .

Приклад відповіді

{
  "errors": [
    {"t": 1758534221, "up": 183900, "src": "sys", "msg": "/api/safe on=1 від 192.168.1.15", "rep": 3},
    {"t": 1758534100, "up": 183700, "src": "ota", "msg": "пряма заливка образу: порожній образ"}
  ],
  "n": 2
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/errclear

Очистити журнал помилок

Синхронно очищає кільцевий журнал помилок (errlog::clear()).

Блокується LightGate під час активного BLE-банк-сеансу (503).

curl-команда

curl -s "http://IP_ПЛАТИ/api/errclear" | jq .

Приклад відповіді

{"ok": true, "msg": "журнал помилок очищено"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/regs

Останній опитаний блок регістрів

Віддає останній масив сирих значень регістрів Modbus, які плата опитала автоматично (webRegs, main.cpp:7771), разом з розкладкою вікон опитування і, якщо мапу впізнано, підписами метрик — щоб панель могла показати не стовпчик чисел, а таблицю з назвами полів.

age — вік даних у секундах (-1, якщо ще не опитано жодного разу). НЕ блокується LightGate (у білому списку).

curl-команда

curl -s "http://IP_ПЛАТИ/api/regs" | jq .

Приклад відповіді

{
  "mask": 31,
  "age": 2,
  "slave": 1,
  "w": [ {"fc": 3, "addr": 108, "n": 12, "off": 0} ],
  "v": [230, 5012, 0, 671],
  "map": "deye-hybrid",
  "metrics": [ {"k": "pv_power_w", "fc": 3, "a": 109} ]
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/history

Історія метрик для графіків

Потокова (не буферизована в памʼяті) віддача історичних точок метрик із SD-картки для побудови графіків (webHistory, main.cpp:4479). Розшифровується активною картою регістрів; точки, записані під іншою розкладкою опитування (сервер змінив профіль), лишаються без метрик — розрив на графіку чесніший за вигадані значення.

Кожен рядок rows — кортеж [t, batt_soc_pct, pv_power_w, load_power_w, grid_power_w, batt_power_w]. budget — доступний бюджет запису в байтах, число за межами 32 біт, тому передається рядком. Chunked-відповідь (Content-Length невідомий наперед). НЕ блокується LightGate (у білому списку).

Параметри

frominteger (unix сек)необовʼязковий

Нижня межа часу; 0 або відсутність — без нижньої межі.

tointeger (unix сек)необовʼязковий

Верхня межа часу.

ninteger (1..1440)необовʼязковий

Стеля кількості точок після проріджування (типово 300).

curl-команда

curl -s "http://IP_ПЛАТИ/api/history?from=1758400000&to=1758534221&n=300" | jq .

Приклад відповіді

{
  "oldest": 1758000000,
  "newest": 1758534221,
  "bytes": 5242880,
  "segments": 12,
  "budget": "34359738368",
  "profile": "server",
  "map": "deye-hybrid",
  "map_src": "local",
  "rows": [ [1758533900, 67, 1240, 920, 0, -320], [1758533930, 67, 1180, 900, 0, -280] ]
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/coredump

Дамп паніки без USB

Віддає сирий бінарний дамп ядра (coredump) останньої паніки прошивки — розділ пишеться ESP-IDF автоматично при кожному крешу. Декодується на ПК інструментом esp-coredump разом із ELF тієї ж збірки. З параметром erase=1 замість віддачі стирає збережений дамп (щоб наступна паніка не змішалась зі старою).

Блокується LightGate. Стирання (erase=1) незворотне.

Параметри

erase"1"необовʼязковий

Якщо дорівнює "1" — стерти дамп замість читання.

curl-команда

curl -s "http://IP_ПЛАТИ/api/coredump?erase=1" -o coredump.bin

Приклад відповіді

Без erase: application/octet-stream (сирі байти coredump-розділу), 404 {"error":"coredump немає"} якщо дампу немає. З erase=1: {"ok": true, "msg": "coredump стерто"} або {"ok": true, "msg": "coredump: стерти не вдалось"}.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/selftest/bleflash

Самоперевірка BLE + запис у флеш

Заводська/діагностична проба одночасного навантаження BLE-сканера й запису у флеш-розділ spiffs (не використовується прошивкою для даних) — перевіряє відому апаратну пастку: на S3 без PSRAM одночасні BLE-пули й запис у флеш можуть спричинити паніку. Живе завершення з відповіддю означає «пройшло»; аварія лишить по собі coredump.

Блокує веб-сервер на весь час проби (s секунд). Стирає й переписує розділ spiffs — безпечно (прошивка цей розділ не використовує), але це реальна фізична операція запису флешу. 404 {"error":"немає розділу spiffs"}, якщо розділу немає в partition table.

Параметри

sinteger (10..240)необовʼязковий

Тривалість проби в секундах; сервер зайнятий увесь цей час.

curl-команда

curl -s "http://IP_ПЛАТИ/api/selftest/bleflash?s=60" | jq .

Приклад відповіді

{
  "secs": 60,
  "flash_written_b": 3801088,
  "flash_erased_b": 3801088,
  "flash_errs": 0,
  "ble_scans": 11,
  "ble_scans_ok": 11,
  "ble_devices_seen": 34,
  "scan_task_joined": true,
  "heap_min": 39120,
  "ble_psram": false
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/sdformat

Форматувати SD-картку

Форматує файлову систему SD-картки плати наново (webpanel.cpp:1065, обробник onSdFormat).

⚠ ДЕСТРУКТИВНО: знищує все на SD-картці — історію метрик, файли веб-панелі, збережений coredump. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/sdformat" | jq .

Приклад відповіді

{"ok": true, "msg": "..."}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/sdwipe

Стерти SD-картку

Низькорівнево стирає вміст SD-картки (webpanel.cpp:1069, обробник onSdWipe) — точна відмінність від sdformat (повне стирання секторів проти простого переформатування) не задокументована в коді як окремий контракт, лише назва хука.

⚠ ДЕСТРУКТИВНО й повільніше за sdformat. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/sdwipe" | jq .

Приклад відповіді

{"ok": true, "msg": "..."}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

Оновлення прошивки (OTA)

4
GET/api/update

Перевірити оновлення на сервері

Ставить у чергу запит fwupdate::requestCheck() — на наступному тику головного циклу плата спитає сервер, яка версія прошивки доступна, і запише результат у внутрішній стан оновлювача, видимий далі через блок upd у /api.

Асинхронно — сама перевірка виконується пізніше в головному циклі, а не в цьому HTTP-запиті. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/update" | jq .

Приклад відповіді

{"ok": true, "msg": "перевірка оновлення поставлена в чергу"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/install

Встановити знайдене оновлення

Ставить у чергу запит fwupdate::requestInstall() — плата скачає з сервера й встановить у неактивний OTA-слот раніше знайдену (через /api/update) версію прошивки, а потім перезавантажиться в неї.

Заливка триває хвилини; обробник лише зводить прапорець, а не чекає завершення. Блокується LightGate. Може завершитись перезавантаженням плати в нову прошивку.

curl-команда

curl -s "http://IP_ПЛАТИ/api/install" | jq .

Приклад відповіді

{"ok": true, "msg": "заливка поставлена в чергу"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

POST/api/otapush

Пряма заливка образу файлом

Пряма (без TLS і без сервера) заливка .bin-образу прошивки по HTTP у локальній мережі через multipart-форму (handleOtaUpload, webpanel.cpp:836). Існує тому, що заливка з сервера ходить по TLS і потребує ~40 КБ суцільної купи — на завантаженій платі цієї памʼяті часом бракує. Пряма заливка працює навіть тоді, коли плата не може достукатись до strumok.app.

Незворотна операція за успіху: плата одразу перезавантажується в новий образ. Обірваний або битий файл НЕ шкодить поточній прошивці. Блокується LightGate.

Поле multipart-форми з бінарним образом прошивки.

curl-команда

curl -s -X POST "http://IP_ПЛАТИ/api/otapush" -F "fw=@/шлях/до/прошивки.bin"

Приклад відповіді

Успіх: 200 {"ok": true, "msg": "образ прийнято — перезавантаження в нову прошивку"}. Невдача: 400 {"ok": false, "msg": "заливка не вдалась: <причина>"}.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/restart

Перезавантажити плату

Ставить у чергу звичайне перезавантаження (reqRestart через onRestartPlain). Навмисно НЕ знімає аварійний режим (safe mode) — проста кнопка «перезавантажити» не повинна мовчки виводити плату з режиму, у який її ввела людина.

Відповідь надсилається ДО фактичного перезавантаження. Блокується LightGate. На відміну від /api/otapush і /api/safe, свідомо НЕ чіпає аварійний режим.

curl-команда

curl -s "http://IP_ПЛАТИ/api/restart" | jq .

Приклад відповіді

{"ok": true, "msg": "перезавантаження…"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

RS-485 / Modbus

9
GET/api/scan

Автоскан шини (пошук інвертора/батареї)

Ставить прапорець reqScan → головний цикл переводить плату в стан Scan: повний скан адресного простору Modbus для автоматичного пошуку endpoint (інвертор/батарея) — швидкості, адреси, протоколу.

Асинхронно; сам скан виконується в головному циклі й може тривати десятки секунд — прогрес видно в блоці bus у /api. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/scan" | jq .

Приклад відповіді

{"ok": true, "msg": "скан шини поставлено в чергу"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/dump

Дамп регістрів знайденого endpoint

Ставить прапорець reqDump (лише якщо endpoint вже знайдено) → головний цикл переводить плату в стан Dump: послідовний дамп регістрів опитаного пристрою для аналізу/пошуку карти метрик.

Асинхронно. Якщо endpoint ще не знайдено — команда мовчки ігнорується. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/dump" | jq .

Приклад відповіді

{"ok": true, "msg": "дамп регістрів поставлено в чергу"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/read

Ручне читання регістрів Modbus

Синхронне читання блоку регістрів Modbus (webRead, main.cpp:8448) по знайденому endpoint напряму, в обхід автоопитування — для ручної перевірки конкретної адреси.

Відмовляє, якщо йде OTA-заливка або триває автоскан шини — щоб не зіпсувати обидва кадри. Блокується LightGate.

Параметри

fcinteger (3|4)необовʼязковий

Функція Modbus: 3 = Holding Registers, 4 = Input Registers. Будь-яке інше значення трактується як 3.

addrinteger (0..65535)обовʼязковий

Початкова адреса регістра.

ninteger (1..32)обовʼязковий

Кількість регістрів; клампиться до MODBUS_MAX_BLOCK=32.

curl-команда

curl -s "http://IP_ПЛАТИ/api/read?fc=3&addr=108&n=12" | jq .

Приклад відповіді

{"result": "Ok", "v": [230, 5012, 0, 671]} — або {"result": "Exception", "exc": "IllegalDataAddress"}, або {"result": "плата заливає прошивку"} / {"result": "шина зайнята скануванням"} / {"result": "endpoint ще не знайдено"}.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/busstop

Зупинити автоскан шини

Ставить reqBusStop=true і переводить плату в ручний режим шини (s_busManual=true) — автоматичне опитування зупиняється до явного /api/busauto.

Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/busstop" | jq .

Приклад відповіді

{"ok": true, "msg": "автоскан шини зупинено — ручний режим"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/busauto

Повернути автоскан шини

Знімає ручний режим (s_busManual=false) і явно повертає автомат опитування в роботу (resumeBusState()) — просто зняти прапорець недостатньо, бо зі стану Idle плата сама не виходить.

Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/busauto" | jq .

Приклад відповіді

{"ok": true, "msg": "автоскан шини увімкнено"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/bustest

Повний прогін тестів шини

Ставить reqBusTest=true → doBusTest(): перебирає всі 8 стандартних швидкостей (1200..115200) на пасивному слуханні, Pylon get-analog (адреси 0..3) і Modbus FC3 liveness (slave 1..3) — інструмент «на чому взагалі хоч щось відповідає» для пошуку невідомого протоколу батареї/інвертора без перезаливань прошивки.

Асинхронно, займає десятки секунд, перериваний через /api/busstop — результат не віддається цим ендпоінтом напряму, дивитись через веб-консоль/лог. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/bustest" | jq .

Приклад відповіді

{"ok": true, "msg": "повний прогін тестів шини поставлено в чергу"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/busprobe

Ручна точкова проба шини

Синхронна одиночна проба конкретного baud/режиму/адреси (webBusProbe, main.cpp:8573) — багаторежимний інструмент пошуку фізичної несправності або протоколу: listen (пасивне слухання), pylon (запит Pylontech get-analog), raw (сирий hex-кадр), loop (внутрішній UART loopback), txpin (чи ворушить ESP свій пін передачі), txecho (реальне ехо в лінії), loopdiag (loopback із різнобітним патерном), pinlvl (пасивний замір рівня RX-піна).

Відмовляє під час OTA або зайнятої шини. Ставить плату в ручний режим ЛИШЕ на час проби. Блокується LightGate.

Параметри

baudintegerнеобовʼязковий

Швидкість шини, типово 9600.

modestring (listen|pylon|raw|loop|txpin|txecho|loopdiag|pinlvl)необовʼязковий

Режим проби; типово listen.

addrintegerнеобовʼязковий

Адреса пристрою (для режиму pylon), типово 2.

hexstring (hex)необовʼязковий

Сирий кадр у hex — потрібен лише в режимі raw.

msinteger (50..2000)необовʼязковий

Таймаут очікування відповіді, мс.

businteger (1|2|3)необовʼязковий

Номер шини: 1 — RS-485 №1, 2 — RS-485 №2, 3 — розʼєм RS-232.

invboolean (0|1)необовʼязковий

Програмна інверсія лінії A/B.

rxpinteger (GPIO)необовʼязковий

Явний RX-пін.

txpinteger (GPIO)необовʼязковий

Явний TX-пін.

curl-команда

curl -s "http://IP_ПЛАТИ/api/busprobe?baud=9600&mode=listen&addr=2&hex=010300000001&ms=700&bus=1&inv=0&rxp=26&txp=25" | jq .

Приклад відповіді

Спільна частина: {"bus":1,"rx":26,"tx":25,"inv":false,"baud":9600,"mode":"listen","got":8,"hex":"0103A20001...","ascii":"..."}. Кожен режим додає свої поля, напр. txpin: {"tx_pin":25,"samples":640,"low":210,"high":430,"pad_readable":true,"verdict":"пін передачі ВОРУШИТЬСЯ — ESP драйвить лінію"}.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/busdiag

Автоматична діагностика ліній шини

Синхронна (~2.5 с) комплексна діагностика обох RS-485-шин і розʼєму RS-232 (webBusDiag, main.cpp:8972): трипозиційна проба підтяжки RX-піна, відсоток спокою в MARK, пасивне слухання на кількох швидкостях і винесений вердикт (LiveData / LikelyInvert / IdleMark / інше).

rs232 приймає значення absent, sd_busy, data, idle. Нічого не передає в шину сама (крім патернів у busprobe) — лише міряє спокій і пасивно слухає. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/busdiag" | jq .

Приклад відповіді

{
  "buses": [
    {"name": "Шина №1 (інвертор)", "rx": 26, "tx": 25, "idle_mark_pct": 100, "bytes": 0, "frames": 0, "verdict": "IdleMark"},
    {"name": "Шина №2 (батарея)", "rx": 21, "tx": 22, "idle_mark_pct": 98, "bytes": 24, "frames": 3, "verdict": "LiveData"}
  ],
  "rs232": "absent",
  "report": "== Шина №1 (інвертор), RX G26 / TX G25 ==\n...",
  "anydata": true,
  "invert": false,
  "live": true
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/pylon

Живі метрики банку Pylontech

Синхронне читання й розбір двох кадрів протоколу Pylontech (0x42 — аналогові значення, 0x44 — тривоги) зі знайденої батареї (webPylonRead, main.cpp:9158). Потребує, щоб endpoint уже був знайдений і мав протокол Pylon.

На помилці розбору: {"ok": false, "error": "не вдалося прочитати/розібрати кадр 0x42", "got": 0}. Поля alarm_cells/alarm_temps зʼявляються лише коли alarms>0. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/pylon" | jq .

Приклад відповіді

{
  "ok": true,
  "baud": 19200,
  "addr": 2,
  "bus": 2,
  "voltage": 52.4,
  "current": -3.12,
  "soc": 87.5,
  "remain_ah": 87.6,
  "total_ah": 100.0,
  "cell_min": 3298,
  "cell_max": 3312,
  "cell_spread": 14,
  "cells": [3298, 3305, 3312],
  "temps": [24.3, 24.8],
  "alarms": 0
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

CAN-шина

3
GET/api/can

Живі метрики з CAN (Pylontech-CAN)

Слухає TWAI-контролер ~1.3 с (один цикл широкомовлення батареї) і розбирає знайдені кадри Pylontech-CAN. Якщо на робочому бітрейті тиша — робить повний перебір бітрейтів 500/250/125 кбіт/с (canbus::sweep, webCanRead, main.cpp:9413).

На повній тиші: {"ok": false, "error": "тиша на CAN на всіх бітрейтах..."} або, при bus-off: {"bus_off": true, ...} (плата сама пробує підняти контролер назад). Якщо трафік не Pylontech: {"ok": false, "error": "на шині Є трафік, але це НЕ Pylontech-CAN ID..."}. Блокується LightGate. Не потребує /api/scan — незалежна периферія, працює паралельно з опитуванням Modbus.

curl-команда

curl -s "http://IP_ПЛАТИ/api/can" | jq .

Приклад відповіді

{
  "frames": 5,
  "raw_frames": 12,
  "kbps": 500,
  "tec": 0,
  "rec": 0,
  "bus_err": 0,
  "ok": true,
  "soc": 87,
  "soh": 100,
  "voltage": 52.4,
  "current": -3.1,
  "temp": 24.3,
  "charge_v_max": 54.0,
  "modules": 2,
  "maker": "Pylontech"
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/candiag

Пошук CAN-піна перебором

Знімає TWAI-драйвер, перебирає кандидатів-піни і читає їх напряму як GPIO, шукаючи перемикання рівня (ознака CAN-трафіку). Найактивніший кандидат додатково доводиться реальним підйомом TWAI-контролера (canbus::probeRxPin).

Якщо жоден пін не дав підтвердженого CAN — found_pin/kbps/frames відсутні, у report — пояснення. Відмовляє під час OTA. Блокується LightGate. Повертає {"error": "у паспорті заліза цієї плати CAN немає"}, якщо плата не має CAN за паспортом заліза.

curl-команда

curl -s "http://IP_ПЛАТИ/api/candiag" | jq .

Приклад відповіді

{
  "found_pin": 35,
  "kbps": 500,
  "frames": 3,
  "report": "G35: АКТИВНІСТЬ (24 перем., 51% HIGH) → тут CAN-сигнал!\n...\n→ РЕАЛЬНИЙ CAN на G35 @500к."
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/canprobe

Зонд CAN на довільній парі пінів

Піднімає TWAI у режимі NORMAL на заданій парі rx/tx і слухає — для пошуку реальних CTX/CRX трансивера, коли вони не на штатних пінах (webCanProbe, main.cpp:9389).

Побічний ефект: TWAI тимчасово захоплює GPIO-матрицю, що може зачепити піни RS-485 шини №2 — тому обробник примусово скидає шину після зонда. Блокується LightGate.

Параметри

rxinteger (GPIO)необовʼязковий

Пін прийому CAN; типово поточний штатний.

txinteger (GPIO)необовʼязковий

Пін передачі CAN; типово поточний штатний.

curl-команда

curl -s "http://IP_ПЛАТИ/api/canprobe?rx=35&tx=32" | jq .

Приклад відповіді

{"rx": 35, "tx": 32, "frames": 4, "kbps": 500, "ok": true}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

BLE-банки (BMS)

6
GET/api/batadd

Керування джерелами BLE-банків і параметрами радіо

Багатофункціональний ендпоінт (webBleBatSet, main.cpp:2545): або додає/очищає джерело BLE-банку (тип seplos/tianpwr + MAC + slot), або, якщо передано інший параметр, налаштовує рантайм-поведінку BLE-стека (темп опитування, режим утримання стека, вікно/інтервал підключення, пакетні сеанси, пріоритет співіснування радіо Wi-Fi/BT, чи RS-485-семплер опитує сам).

Службові параметри (poll/keep/batch/scanwin/connitvl/coex/sampler) НЕ пишуться в NVS — живуть лише до перезавантаження. Блокується LightGate (окрім поточного власного BLE-сеансу — цей ендпоінт саме і керує ним).

Параметри

tstring (seplos|tianpwr|clear)необовʼязковий

Тип BMS для додавання джерела, або clear — очистити всі додані джерела.

macstring (MAC)необовʼязковий

MAC-адреса BLE-банку (обовʼязковий разом із t=seplos|tianpwr).

slotintegerнеобовʼязковий

Номер слоту джерела.

pollinteger (мс)необовʼязковий

Темп опитування живого банку; 0 повертає типовий; мінімум клампиться до 2000 мс.

keepinteger (0|1|2)необовʼязковий

Режим утримання BLE-стека/клієнта між сеансами.

batchinteger (0|1)необовʼязковий

Пакет кількох сеансів за один підйом стека.

scanwinintegerнеобовʼязковий

Вікно сканування перед підключенням.

connitvlintegerнеобовʼязковий

Інтервал BLE-зʼєднання.

coexinteger (0|1|2)необовʼязковий

Пріоритет співіснування радіо: 0 = баланс, 1 = Wi-Fi, 2 = BT. Діє до перезавантаження.

samplerinteger (0|1)необовʼязковий

RAM-перемикач: чи задача опитування RS-485 керує сама собою; скидається на перезавантаженні.

curl-команда

curl -s "http://IP_ПЛАТИ/api/batadd?t=seplos&mac=AA%3ABB%3ACC%3ADD%3AEE%3AFF&slot=0&poll=3000&keep=1&batch=1&scanwin=0&connitvl=0&coex=0&sampler=1" | jq .

Приклад відповіді

Залежить від гілки: додавання джерела — {"ok": true, "count": 1}; очищення — {"ok": true, "count": 0}; coex — {"ok": true, "coex": 1}; connitvl — {"ok": true, "connitvl": 40}; помилки — {"error": "t: seplos|tianpwr|clear"} / {"error": "mac?"} / {"error": "mac не розбирається"} / {"error": "повно"}.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/blescan

Скан чужих BLE-пристроїв поруч

Блокуючий скан ефіру (кілька секунд) для пошуку BMS-акумуляторів по Bluetooth (handleBleScan, webpanel.cpp:519). available розрізняє «BLE-стек не піднявся» від «нічого не знайдено»; якщо стек холодний — ендпоінт чекає повторними сканами, а не здається одразу.

mac завжди у ВЕРХНЬОМУ регістрі; name — null, коли імені нема; service/guess пропускаються повністю, якщо порожні. at — ISO-8601 UTC, зʼявляється лише коли годинник синхронізовано. При невдалому підйомі стека: available:false + error:"стек BLE не піднявся вчасно". Блокується LightGate під час чужого активного BLE-банк-сеансу.

Параметри

sinteger (1..15)необовʼязковий

Тривалість скану в секундах; типово 6.

curl-команда

curl -s "http://IP_ПЛАТИ/api/blescan?s=6" | jq .

Приклад відповіді

{
  "ok": true,
  "available": true,
  "at": "2026-09-22T10:15:03Z",
  "report": "...текстовий звіт для веб-панелі...",
  "devices": [
    {"mac": "AA:BB:CC:DD:EE:FF", "name": "SP05S", "rssi": -62, "adv": 14, "adv_ms": 320, "service": "ffe0", "guess": "seplos"},
    {"mac": "11:22:33:44:55:66", "name": null, "rssi": -80}
  ]
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/bleprobe

Проба одного BLE-пристрою (дамп GATT)

Підключається до вказаного MAC і знімає дамп його GATT-сервісів/характеристик — інструмент розвідки невідомого BLE-пристрою (bleprov::probeDevice).

Блокується LightGate. Блокуючий виклик (конект + читання GATT).

Параметри

macstring (MAC)обовʼязковий

MAC-адреса пристрою для проби.

curl-команда

curl -s "http://IP_ПЛАТИ/api/bleprobe?mac=AA%3ABB%3ACC%3ADD%3AEE%3AFF" | jq .

Приклад відповіді

{"report": "текстовий дамп GATT-сервісів/характеристик (значення не формалізовані як окремі JSON-поля)"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/blejbd

Читання JBD-BMS по BLE

Підключається до вказаного MAC і читає реальні дані батареї за протоколом JBD BMS (bleprov::readJbd).

Блокується LightGate. Блокуючий виклик.

Параметри

macstring (MAC)обовʼязковий

MAC-адреса JBD-BMS.

curl-команда

curl -s "http://IP_ПЛАТИ/api/blejbd?mac=AA%3ABB%3ACC%3ADD%3AEE%3AFF" | jq .

Приклад відповіді

{"report": "текстовий звіт із прочитаними значеннями JBD-BMS (напруга/струм/SOC/комірки — структура рядка не формалізована)"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/bleseplos

Читання Seplos V2 BMS по BLE

Підключається до вказаного MAC і читає дані батареї за протоколом Seplos V2 (bleprov::readSeplos).

Блокується LightGate. Блокуючий виклик.

Параметри

macstring (MAC)обовʼязковий

MAC-адреса Seplos-банку.

curl-команда

curl -s "http://IP_ПЛАТИ/api/bleseplos?mac=AA%3ABB%3ACC%3ADD%3AEE%3AFF" | jq .

Приклад відповіді

{"report": "текстовий звіт Seplos V2 (значення не формалізовані як окремі JSON-поля)"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/bletp

Читання TianPower BMS по BLE

Підключається до вказаного MAC і читає дані батареї за протоколом TianPower (bleprov::readTianpwr).

Блокується LightGate. Блокуючий виклик.

Параметри

macstring (MAC)обовʼязковий

MAC-адреса TianPower-банку.

curl-команда

curl -s "http://IP_ПЛАТИ/api/bletp?mac=AA%3ABB%3ACC%3ADD%3AEE%3AFF" | jq .

Приклад відповіді

{"report": "текстовий звіт TianPower (значення не формалізовані як окремі JSON-поля)"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

Tesla (Fleet API через BLE)

6
GET/api/tesla

Статус модуля Tesla BLE

Віддає поточний стан інтеграції з автомобілем Tesla по BLE (handleTeslaStatus → teslable::statusJson, tesla_ble.cpp:889): чи налаштовано VIN, чи спарований ключ, підстан процесу спарування/сеансу, і, якщо дані свіжі, останні прочитані показники заряду.

vin маскується посередині зірочками. phase — один із idle/scan/connect/discover/enroll_send/enroll_wait_nfc/session/read/teardown. Доступний лише в збірках зі STRUMOK_TESLA (умовна компіляція). Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/tesla" | jq .

Приклад відповіді

{
  "configured": true,
  "enabled": true,
  "paired": true,
  "has_key": true,
  "need_enroll": false,
  "phase": "idle",
  "enroll_active": false,
  "vin": "7SA**********405",
  "fresh": true,
  "soc": 74,
  "charging": true,
  "charge_a": 16,
  "v": 230,
  "eta_min": 95,
  "state": 2
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/tesla/scan

Пошук авто Tesla поруч (BLE discovery)

Ставить прапорець на discovery-скан BLE-реклами поблизу (пошук пристроїв Tesla за хеш-патерном) і одразу віддає накопичений на цей момент список знайдених кандидатів, відсортований за рівнем сигналу.

Сам скан виконується асинхронно в головному циклі (у момент відповіді scan.busy зазвичай ще true, а found — результат попереднього проходу). Доступний лише зі STRUMOK_TESLA. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/tesla/scan" | jq .

Приклад відповіді

{
  "ok": true,
  "msg": "скан Tesla поставлено в чергу",
  "scan": {
    "busy": true,
    "at_ms": 183920123,
    "found": [ {"hash": "S1a2b3c4C", "rssi": -58}, {"hash": "S9f8e7d6C", "rssi": -74} ]
  }
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/tesla/config

Задати VIN автомобіля вручну

Записує VIN автомобіля (17 символів) і вмикає/вимикає модуль Tesla (handleTeslaConfig, webpanel.cpp:678). Альтернатива автовизначенню через /api/tesla/derive.

Доступний лише зі STRUMOK_TESLA. Блокується LightGate.

Параметри

vinstring (рівно 17 символів)обовʼязковий

Повний VIN автомобіля.

enabled"0"|"1"необовʼязковий

Увімкнути (типово) чи вимкнути модуль; лише "0" вимикає.

curl-команда

curl -s "http://IP_ПЛАТИ/api/tesla/config?vin=5YJ3E1EA1JF000001&enabled=1" | jq .

Приклад відповіді

Успіх: {"ok": true, "msg": "VIN збережено, модуль увімкнено"} (або "...вимкнено"). Невалідний VIN: 400 {"ok": false, "msg": "VIN має бути рівно 17 символів"}.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/tesla/derive

Автовизначення VIN сервером

Ставить прапорець запиту на серверне визначення VIN автомобіля (перебором через Fleet API за адресою власника) замість ручного вводу.

Асинхронно; результат (derive: ok/not_found/busy/error) читається пізніше з /api/tesla. Доступний лише зі STRUMOK_TESLA. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/tesla/derive" | jq .

Приклад відповіді

{"ok": true, "msg": "визначення VIN сервером поставлено в чергу"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/tesla/enroll

Запустити спарування ключа з авто

Ставить прапорець на процедуру спарування (enroll) BLE-ключа плати з автомобілем. Потребує фізичної дії власника — прикласти NFC-картку авто до дверей у момент спарування.

Асинхронно, прогрес читається через phase/enroll_active у /api/tesla. Доступний лише зі STRUMOK_TESLA. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/tesla/enroll" | jq .

Приклад відповіді

{"ok": true, "msg": "спарування заплановано — прикладіть NFC-картку власника до авто"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/tesla/pubkey

Публічний ключ плати для спарування

Віддає публічний ключ (secp256r1, незжатий формат) плати в hex-вигляді — потрібен для реєстрації ключа на боці автомобіля/сервера Tesla.

Доступний лише зі STRUMOK_TESLA. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/tesla/pubkey" | jq .

Приклад відповіді

{"pubkey": "04a1b2c3..."} (130 hex-символів = 65 байтів: префікс 0x04 + X(32) + Y(32)) — або {"error": "ключ недоступний"}, якщо ключ ще не згенеровано.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

Пошук зарядної станції авто (EVSE)

1
GET/api/evsescan

Пошук зарядки обраною стратегією

Запускає пошук домашньої зарядної станції (EVSE, наприклад Eveus) у локальній мережі обраною стратегією, перевикористовуючи консольну команду evse::handleCommand("evse scan <strat>"). Виконується в контексті головного циклу — безпечно, як і Serial-команда.

Асинхронно; результат (кандидати, замір часу/памʼяті проходу) читається пізніше з /api → evse.cands і evse.scanm. Блокується LightGate.

Параметри

stratstring (seq|par|arp|mdns)необовʼязковий

Стратегія пошуку: seq — послідовний перебір адрес підмережі, par — паралельний, arp — через ARP-таблицю, mdns — через mDNS. Порожнє значення — без зміни поточної стратегії.

curl-команда

curl -s "http://IP_ПЛАТИ/api/evsescan?strat=par" | jq .

Приклад відповіді

{"ok": true, "msg": "пошук зарядки [par] поставлено в чергу"} (без strat: "пошук зарядки поставлено в чергу").

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

Wi-Fi / радіо-режими / аварійний режим

6
POST/api/safe

Увімкнути/вимкнути аварійний режим

Вмикає (on=1) або вимикає (on=0) аварійний («безпечний») режим плати — мінімум підсистем, зупинка автопілота зарядки перед тишею. Обидва напрямки завершуються перезавантаженням, бо сенс режиму саме в тому, що НЕ піднімається після скиду. Вихід (on=0) додатково скидає лічильник аварій.

ЛИШЕ POST — навмисно, щоб пасивний перегляд посилання (оновлення вкладки, префетч браузера) не вмикав режим випадково. IP викликача пишеться в журнал помилок. Незворотний побічний ефект: примусове перезавантаження плати. Блокується LightGate.

Параметри

on"0"|"1"обовʼязковий

1 — увійти в аварійний режим, 0 — вийти й скинути лічильник аварій.

curl-команда

curl -s -X POST "http://IP_ПЛАТИ/api/safe?on=1" | jq .

Приклад відповіді

{"ok": true, "msg": "аварійний режим: перезавантажуюсь із мінімумом підсистем"} (або "звичайний режим: перезавантажуюсь"). 405 {"error": "only POST"} на GET.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/ble

Увімкнути/вимкнути BLE

Ручний перемикач BLE-радіо. Вимкнення звільняє помітну частину купи (BLE тримає й фрагментує памʼять) — оператор так стабілізує плату, коли падає TLS або тисне буфер.

Скидається на перезавантаженні (не персистентне). Блокується LightGate.

Параметри

on"0"|"1"обовʼязковий

1 — увімкнути BLE, 0 — вимкнути.

curl-команда

curl -s "http://IP_ПЛАТИ/api/ble?on=0" | jq .

Приклад відповіді

{"ok": true, "msg": "BLE увімкнено"} (або "BLE вимкнено — RAM звільнено").

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/ap

Увімкнути/вимкнути власну Wi-Fi-точку

Ручний перемикач SoftAP плати (провізіонінг-точки).

Скидається на перезавантаженні. Блокується LightGate.

Параметри

on"0"|"1"обовʼязковий

1 — увімкнути власну Wi-Fi-точку, 0 — вимкнути.

curl-команда

curl -s "http://IP_ПЛАТИ/api/ap?on=0" | jq .

Приклад відповіді

{"ok": true, "msg": "власну Wi-Fi-точку увімкнено"} (або "...вимкнено").

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/wifips

Модемний сон Wi-Fi / вимоги BT

Два незалежні перемикачі в одному ендпоінті: якщо переданий параметр bt — керує тим, чи Bluetooth-контролер вимагає модемного сну Wi-Fi (експериментальний режим); інакше параметр on керує самим модемним сном Wi-Fi. Хто перемкнув — пишеться в журнал помилок, бо режим радіо впливає на пінг і Modbus.

IP викликача логується разом із новим значенням. Блокується LightGate.

Параметри

on"0"|"1"необовʼязковий

1 — увімкнути модемний сон Wi-Fi, 0 — вимкнути. Ігнорується, якщо переданий bt.

bt"0"|"1"необовʼязковий

0 — контролер BT працює без вимоги модемного сну Wi-Fi (експеримент); будь-яке інше значення — BT знову вимагає сну.

curl-команда

curl -s "http://IP_ПЛАТИ/api/wifips?on=1&bt=0" | jq .

Приклад відповіді

З bt: {"ok": true, "msg": "BT знову вимагає модемного сну Wi-Fi"} (або "BT без модемного сну Wi-Fi (експеримент)"). Без bt: {"ok": true, "msg": "Wi-Fi: модемний сон увімкнено"} (або "...вимкнено").

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/mqtt

Стан MQTT-містка «коробка в Home Assistant»

Стан MQTT-клієнта плати, що публікує її в Home Assistant через MQTT Discovery (ha_mqtt.cpp::panel). Пароль брокера не віддається ніколи — лише has_pass.

err: "" | dns | refused | auth | timeout | tls. rev — ревізія конфіга, заданого із застосунку через сервер; 0 — конфіг задано лише з панелі. ent — скільки сутностей оголошено в HA.

curl-команда

curl -s "http://IP_ПЛАТИ/api/mqtt" | jq .

Приклад відповіді

{
  "configured": true, "enabled": true, "connected": true,
  "host": "192.168.1.10", "port": 1883, "tls": false,
  "has_user": true, "user": "strumok", "has_pass": true,
  "control": false, "fails": 0, "err": "", "ent": 16, "rev": 3,
  "dev": "SK-E89F6DB694F0",
  "limits": { "host": 63, "user": 31, "pass": 63 }
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

POST/api/mqtt/config

Налаштувати MQTT-місток

Зберігає налаштування брокера в NVS і перепідключається. Лише POST із JSON-тілом: пароль у GET-параметрах осідав би в історії браузера й журналах проксі, тож GET відповідає 405.

Команди з HA йдуть у ту саму точку, що й команди з сервера (evse::applyServerCommand, source "ha"), тож межі струму й ручна витримка автопілота ті самі. Налаштування, задане із застосунку (блок mqtt від сервера з новою ревізією), перезаписує панельне.

Параметри

hoststringобовʼязковий

Адреса брокера (≤ 63 символи), без схеми й порту.

portintegerобовʼязковий

1–65535, типово 1883.

userstringобовʼязковий

Логін (≤ 31). Ключ відсутній — лишити збережений; "" — стерти.

passstringобовʼязковий

Пароль (≤ 63). Ключ відсутній — лишити збережений; "" — стерти.

tlsbooleanобовʼязковий

true — шифроване зʼєднання з брокером.

enabledbooleanобовʼязковий

false — вимкнути місток. Типово true.

controlbooleanобовʼязковий

true — оголосити в HA керування: switch charging_enabled і number charging_current (команди strumok/<dev>/set/…). Типово false.

curl-команда

curl -s -X POST "http://IP_ПЛАТИ/api/mqtt/config" -H "Content-Type: application/json" -d '{"host":"192.168.1.10","port":1883,"user":"strumok","pass":"••••","tls":false,"enabled":true,"control":false}' | jq .

Приклад відповіді

{ "ok": true, ...той самий стан, що й GET /api/mqtt }  ·  400 {"ok":false,"err":"need_host"|"bad_port"|"bad_json"|"too_long","field":"host","max":63}  ·  405 {"ok":false,"err":"only_post"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

Файли веб-панелі (SD)

4
GET/api/ui/list

Перелік файлів набору панелі

Віддає перелік усіх файлів набору веб-панелі, які зараз лежать на SD-картці (/sd/ui), з розміром кожного. Панель — це набір статичних файлів на картці, а не моноліт у прошивці.

НЕ блокується LightGate (у білому списку). sd — чи присутня картка взагалі.

curl-команда

curl -s "http://IP_ПЛАТИ/api/ui/list" | jq .

Приклад відповіді

{
  "files": [ {"p": "index.html", "n": 4821}, {"p": "vendor/coreui.css.gz", "n": 58210} ],
  "bytes": 63031,
  "n": 2,
  "sd": true
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

POST/api/ui/put

Залити файл у набір панелі

Заливає один файл у набір веб-панелі на SD-картку через multipart body; цільовий шлях передається окремим query-параметром path (не з імені файлу в multipart) — саме так працює інструмент деплою device/tools/push_ui.py.

Перезаписує файл, якщо він уже існує за тим самим шляхом. Блокується LightGate.

Параметри

pathstringобовʼязковий

Шлях цільового файлу в наборі панелі (відносний, без початкового слеша).

curl-команда

curl -s -X POST "http://IP_ПЛАТИ/api/ui/put?path=vendor%2Fcoreui.css.gz" -F "file=@/шлях/до/файлу"

Приклад відповіді

{"ok": true, "path": "vendor/coreui.css.gz", "n": 58210} — або {"ok": false, "path": "...", "n": 0, "err": "не відкрити файл на SD"} тощо.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/ui/rm

Видалити файл набору панелі

Видаляє один файл із набору веб-панелі на SD-картці за шляхом (sdlog::uiRemove).

Незворотна операція. Блокується LightGate.

Параметри

pathstringобовʼязковий

Шлях файлу для видалення.

curl-команда

curl -s "http://IP_ПЛАТИ/api/ui/rm?path=vendor%2Fold-chart.js.gz" | jq .

Приклад відповіді

{"ok": true} (або {"ok": false}, якщо файл не знайдено/не видалився).

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/ui/clear

Прибрати весь набір панелі

Видаляє ВСІ файли набору веб-панелі з SD-картки (sdlog::uiClear) — після цього плата показує вбудовану recovery-сторінку з флешу, доки набір не залиють знову.

Незворотна й руйнівна операція: панель стає недоступною, доки хтось не заллє набір повторно через /api/ui/put або device/tools/push_ui.py. Блокується LightGate.

curl-команда

curl -s "http://IP_ПЛАТИ/api/ui/clear" | jq .

Приклад відповіді

{"ok": true, "removed": 47}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

Провізіонінг (запасний шлях по Wi-Fi SoftAP)

4
GET/provision/info

Ідентифікація непідключеної плати

HTTP-двійник BLE-характеристики INFO (docs/PROVISIONING.md §3.1/§4). Доступний лише коли плата підняла власну SoftAP-точку (запасний шлях — застосунок не знайшов плату по BLE за 20 с, або людина натиснула «Не знаходить»). Віддає ідентичність плати й одноразовий nonce для підпису claim-запиту.

nonce новий на кожен запит/конект; hmac = HMAC-SHA256(claim_secret, uid+"|"+nonce). state: unprovisioned|provisioned. НЕ блокується LightGate (у білому списку).

curl-команда

curl -s "http://IP_ПЛАТИ/provision/info" | jq .

Приклад відповіді

{"uid": "SK-E89F6DB694F0", "model": "strumok-v1", "fw": "0.15.53", "nonce": "9f2c1a...", "hmac": "4b81...", "state": "unprovisioned"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/provision/scan

Скан домашніх Wi-Fi-мереж (з боку плати)

Запускає (або продовжує) скан Wi-Fi-мереж, які бачить САМА ПЛАТА (не телефон) — принципово: телефон і плата в підвалі за стінами бачать різні мережі. Скан триває до 6 с, обробник HTTP не має права чекати довше 1 с у головному циклі, тому перший запит повертає 202 з порожнім масивом, а застосунок опитує далі, доки не отримає 200 з результатом.

Застосунок ЗОБОВʼЯЗАНИЙ розрізняти 202 і 200 — прийняти 202+[] за фінальну відповідь означає показати користувачу «мереж не знайдено» там, де насправді ще не дошукали. Блокується LightGate (крім info/status).

curl-команда

curl -s "http://IP_ПЛАТИ/provision/scan" | jq .

Приклад відповіді

202 (ще триває): []. 200 (готово): [{"ssid": "HomeNet", "rssi": -52, "secure": true}, {"ssid": "Neighbor_5G", "rssi": -80, "secure": true}]

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/provision/status

Прогрес підключення плати до Wi-Fi

HTTP-двійник BLE-характеристики STATUS. Показує поточну стадію спроби підключення після CONFIG.

stage: idle|wifi_connecting|wifi_failed|server_connecting|server_failed|done. error при wifi_failed: bad_password|not_found|timeout; при server_failed: unauthorized|unreachable. Після done плата тримає BLE ще 10 с і вимикає радіо. НЕ блокується LightGate (у білому списку).

curl-команда

curl -s "http://IP_ПЛАТИ/provision/status" | jq .

Приклад відповіді

{"stage": "wifi_connecting", "error": null, "ip": null} — на успіху: {"stage": "done", "error": null, "ip": "192.168.1.30"}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

POST/provision/config

Передати облікові дані Wi-Fi

HTTP-двійник BLE-характеристики CONFIG. Приймає SSID/пароль (і опційно адресу сервера для стенду) і запускає спробу підключення. Дані пишуться в NVS ЛИШЕ після успішного підключення — щоб невірний пароль не перетворив плату на «цеглину», яка більше не рекламується.

Тіло — сирий JSON, не multipart. Класифікація вхідних даних синхронна, сама спроба підключення — асинхронна, в наступному тику. Блокується LightGate.

Параметри

ssidstringобовʼязковий

Назва Wi-Fi-мережі.

pskstringнеобовʼязковий

Пароль мережі.

apistring (URL)необовʼязковий

Адреса сервера; типово https://strumok.app, потрібен лише для стендових середовищ.

curl-команда

curl -s -X POST "http://IP_ПЛАТИ/provision/config" -H "Content-Type: application/json" -d '{"ssid":"HomeNet","psk":"supersecretpass","api":"https://strumok.app"}'

Приклад відповіді

202 (прийнято) з поточним STATUS: {"stage": "wifi_connecting", "error": null, "ip": null}. Помилки: 400 {"error": "empty body"} / {"error": "empty config"}, 405 {"error": "only POST"}, 409 {"error": "window closed"} (вікно провізіонінгу закрите).

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

Налаштування станції (розділи)

5
GET/api/cfg

Усі розділи налаштувань

Налаштування живуть на самій платі й працюють без інтернету. Кожен розділ — окремий документ зі своєю версією: evse_auto (автоматика зарядки), evse_units (розетки), charger (привʼязка зарядки), battery (джерела даних АКБ), ha (Home Assistant), mqtt (місток до брокера), wifi, tariff (тарифні зони), device (назва й часовий пояс), auth (захист входу). Поля в data — ті самі, що в запитах застосунку до сервера. Коли інтернет є, плата сама синхронізує розділи з сервером: перемагає остання зміна, а без годинника (ts = 0) — зміна біля коробки.

Секрети (пароль зарядки, токен HA, пароль Wi-Fi, пароль MQTT, хеш входу) ніколи не повертаються — замість них поле has_<імʼя>: true|false. dirty = true — локальна зміна, яку сервер ще не підтвердив. by — хто змінив: local:panel, local:app, local:ha, cloud:app, cloud:admin, factory. clock.known = false — плата після перезапуску без інтернету ще не знає часу; панель і застосунок присилають його самі (/api/time).

Параметри

Authorizationstringнеобовʼязковий

Потрібен, лише якщо власник увімкнув вхід і закрив читання без входу: Bearer <ключ API або токен сеансу>.

curl-команда

curl -s "http://IP_ПЛАТИ/api/cfg" | jq .

Приклад відповіді

{
  "clock": {"known": true, "utc": 1790000000, "src": "ntp"},
  "auth": {"enabled": false, "ropen": true},
  "sections": {
    "evse_auto": {"ver": 12, "ts": 1790000000, "by": "local:panel", "dirty": false, "data": {"enabled": true, "mode": "solar"}},
    "charger": {"ver": 3, "ts": 1789990000, "by": "cloud:app", "dirty": false, "data": {"host": "192.168.1.50", "login": "admin", "has_password": true, "slot": 0}}
  }
}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/cfg/{name}

Один розділ

Той самий документ, що в /api/cfg, лише для одного розділу. Зручно перед зміною: з відповіді беруть ver і передають його як base_ver у PUT.

Невідоме імʼя розділу — 404.

Параметри

namestringобовʼязковий

Імʼя розділу: evse_auto, evse_units, charger, battery, ha, mqtt, wifi, tariff, device, auth.

Authorizationstringнеобовʼязковий

Як у GET /api/cfg.

curl-команда

curl -s "http://IP_ПЛАТИ/api/cfg/evse_auto" | jq .

Приклад відповіді

{"section": {"ver": 12, "ts": 1790000000, "by": "local:panel", "dirty": false, "data": {"enabled": true, "mode": "solar", "min_current_a": 7, "max_current_a": 16}}}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

PUT/api/cfg/{name}

Змінити розділ

Записує розділ на плату й одразу застосовує. Тіло — {"base_ver": <ver, від якого робили зміну>, "data": {…}}. Межі значень ті самі, що перевіряє сервер, коли ту саму зміну робить застосунок. Нова версія = попередня + 1, ts = поточний час плати (0, якщо годинник невідомий), dirty = true, доки сервер не підтвердить.

Зміна Wi-Fi (розділ wifi) вважається успішною, коли плата асоціювалась із точкою й отримала адресу по DHCP — інтернет для цього не потрібен. Не вдалося за 3 спроби — плата повертається до старої мережі; результат видно в GET /api/cfg полем wifi_try. Регістри інвертора (режим, ліміти, SOC-пороги) — не розділ налаштувань: вони мають окремий канал.

Параметри

namestringобовʼязковий

Імʼя розділу.

base_verintegerобовʼязковий

Версія розділу, яку ви бачили (ver із GET).

dataobject (JSON)обовʼязковий

Новий вміст розділу. Секрет, якого немає в data, лишається старим; порожній рядок "" — стерти секрет.

Authorizationstringнеобовʼязковий

Обовʼязковий, коли власник увімкнув вхід: Bearer <ключ API або токен сеансу>.

curl-команда

curl -s -X PUT "http://IP_ПЛАТИ/api/cfg/device" -H "Content-Type: application/json" -d '{"base_ver":4,"data":{"name":"Гараж","tz":"Europe/Kyiv"}}' | jq .

Приклад відповіді

200 {"section": {"ver": 5, "ts": 1790000100, "by": "local:app", "dirty": true, "data": {"name": "Гараж", "tz": "Europe/Kyiv"}}}
409 {"error": "changed", "section": {…свіжий розділ…}} — хтось змінив розділ раніше; покажіть свіже й повторіть з новим base_ver.
422 {"error": "invalid", "fields": {"max_current_a": "…текст українською…"}} — значення поза межами.
401 {"error": "auth"} — вхід увімкнено, а ключа чи сеансу немає.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

POST/api/evse/cmd

Команда зарядці

Ручне керування зарядкою локально, без сервера: струм, увімкнути/вимкнути, разова зарядка, скидання вироку про фазу. Це команди, а не налаштування — версій вони не мають. Семантика та сама, що в застосунку через сервер.

Команду виконує сама плата; якщо є інтернет, застосунок побачить результат так само, як після команди через сервер.

Параметри

current_ainteger (А)необовʼязковий

Струм зарядки в амперах у межах моделі зарядки.

enabledbooleanнеобовʼязковий

Дозволити (true) чи зупинити (false) зарядку.

one_chargebooleanнеобовʼязковий

Разова зарядка до кінця сесії.

phase_resetbooleanнеобовʼязковий

Скинути вирок «зарядка не на фазі інвертора» — плата поміряє фазу заново.

Authorizationstringнеобовʼязковий

Обовʼязковий, коли власник увімкнув вхід.

curl-команда

curl -s -X POST "http://IP_ПЛАТИ/api/evse/cmd" -H "Content-Type: application/json" -d '{"current_a":16,"enabled":true}' | jq .

Приклад відповіді

200 {"ok": true}
409 — зарядка зараз не приймає команду (не підключена чи зайнята).
422 {"error": "invalid", "fields": {…}} — значення поза межами.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

POST/api/time

Підказати платі час

Після перезапуску без інтернету плата не знає поточного часу, а розклади зарядки й тарифні зони від нього залежать. Панель і застосунок присилають час самі, щойно бачать clock.known = false. Приймається, лише якщо годинник плати не від NTP.

Щойно зʼявиться NTP, плата бере час звідти. Останній відомий час плата зберігає раз на 10 хв.

Параметри

utcinteger (unix сек)обовʼязковий

Поточний час UTC.

curl-команда

curl -s -X POST "http://IP_ПЛАТИ/api/time" -H "Content-Type: application/json" -d '{"utc":1790000000}' | jq .

Приклад відповіді

{"ok": true, "clock": {"known": true, "src": "browser"}}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

Вхід і ключі API

4
GET/api/auth

Стан входу

Відкритий завжди, навіть коли вхід увімкнено й читання закрито. Показує, чи увімкнено вхід, чи відкрите читання без входу і чи діє сеанс цього запиту, а також uid плати — за ним застосунок упізнає свою станцію в домашній мережі без ключа.

Поле dev — з прошивки 0.16.18 (S3) / 0.15.161 (ram2).

curl-команда

curl -s "http://IP_ПЛАТИ/api/auth" | jq .

Приклад відповіді

200 {"dev": "SK-7C0C5F49FFF0", "enabled": false, "ropen": true, "session": false}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

POST/api/auth/login

Увійти

Відкриває сеанс на платі. Токен можна надсилати заголовком Authorization: Bearer <token>; браузер отримує його також кукою sk_s (HttpOnly). Сеанс живе 12 годин від останнього запиту; одночасно — до 4 сеансів.

Вхід вимкнено за замовчуванням. Увімкнути його можна з веб-панелі плати або в застосунку («Захист станції»). Забули пароль — довге натискання кнопки BOOT на платі вимикає вхід.

Параметри

userstringобовʼязковий

Логін, заданий власником.

passstringобовʼязковий

Пароль.

curl-команда

curl -s -X POST "http://IP_ПЛАТИ/api/auth/login" -H "Content-Type: application/json" -d '{"user":"admin"}' | jq .

Приклад відповіді

200 {"token": "…", "exp": 43200} — exp: скільки секунд сеанс діє без запитів (12 год).
401 {"error": "auth"} — хибний логін чи пароль.
429 — після 5 хибних спроб поспіль вхід блокується на 60 с.

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

POST/api/auth/logout

Вийти

Закриває поточний сеанс.

Параметри

Authorizationstringнеобовʼязковий

Bearer <токен сеансу> (або кука sk_s у браузері).

curl-команда

curl -s -X POST "http://IP_ПЛАТИ/api/auth/logout" | jq .

Приклад відповіді

{"ok": true}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

GET/api/auth/keys

Ключі API

Довгі ключі для програм, які ходять до плати без людини: ключ app видає сама плата для застосунку СтрумОК, ключі ha:<назва> створює власник для Home Assistant. Ключ передається заголовком Authorization: Bearer <ключ>. POST на цю ж адресу створює ключ, DELETE — відкликає.

Сам ключ показується один раз — у відповіді на створення. Ключ app плата один раз передає серверу, і застосунок власника отримує його зі своєї картки станції — щоб працювати локально й без інтернету.

Параметри

Authorizationstringобовʼязковий

Bearer <токен сеансу>: керувати ключами можна лише після входу.

curl-команда

curl -s "http://IP_ПЛАТИ/api/auth/keys" | jq .

Приклад відповіді

{"keys": [{"name": "app"}, {"name": "ha:дім"}]}

Відформатувати вивід команди

Не запит — лише форматування: скопіюйте, що надрукував термінал після curl-команди вище, і вставте сюди.

Жоден із запитів не проходить через сервер СтрумОК: адреса плати, параметри й відповіді лишаються лише у вашому браузері. Це той самий локальний API, яким користуються мобільний застосунок і заводські інструменти. Вхід за логіном і паролем — за бажанням власника (за замовчуванням вимкнений), тож довіряйте плату лише своїй домашній мережі.