Разработчикам
Отправка событий с устройства.
Агент subrose — небольшая программа на самом устройстве. Любая программа на устройстве может сообщить ему о событии: об авторизации, аномалии, показании датчика. Агент подписывает сообщение ключом устройства и отправляет на ноду — независимый сервер, который держит связь с устройством. События читаются через HTTP API ноды или в веб-консоли Subrose, а для периодического сбора есть cron-скрипты.
Подпись защищает событие от подделки, но не от чтения: паролям и токенам в событиях не место, шифрование — на подходе.
01 · Механизмы
Два способа отправить событие.
Локальное событие (local push)
Программа на устройстве пишет JSON в локальный канал агента (AF_UNIX-сокет на Linux/macOS/OpenWrt; Windows — на подходе) или вызывает subrose emit из командной строки. Работает, только если владелец включил события в контракте устройства — записи устройства в TON, блокчейне, где хранятся владелец, обслуживающая нода, параметры и события.
Скрипты-сборщики (collector scripts)
Агент запускает *.sh-скрипты из директории collectors по cron-расписанию, которое владелец задаёт в контракте устройства. Stdout скрипта становится данными события. Работает, только если владелец включил флаг stat.
02 · Требования
Что владелец включает заранее.
Для локальных событий (механизм 1) владелец включает флаг
events = true в контракте устройства командой
SetEvents. Для скриптов-сборщиков (механизм 2) —
флаг stat = true через SetStat
и расписание через SetCronSchedule.
Пока флаг выключен, агент принимает сообщения в локальный канал, но
не пересылает их на ноду. Новые флаги устройство получает при
следующей регистрации или сразу — запросом POST /update
к ноде.
03 · Механизм 1
Локальное событие: программа пишет в канал агента.
Агент слушает локальный канал. Любая программа, запущенная от имени того же пользователя (или root), открывает соединение, пишет одну JSON-строку и закрывает его. Агент добавляет подпись ключом устройства и отправляет событие на ноду.
Путь сокета по умолчанию — <stateDir>/subrose.sock,
где <stateDir> — рабочая директория агента
(на Linux и роутерах обычно /var/run/subrose);
сменить путь можно ключом конфига events.socketPath.
Windows-канал \\.\pipe\subrose — на подходе.
Формат сообщения — одна строка JSON с переводом строки в конце:
{"name":"motion_detected","data":{"camera":"garage","confidence":0.93}}
04 · Рецепты
Как вызвать из разных окружений.
Shell — subrose emit
Самый простой способ. Работает везде, где установлен пакет subrose.
Флаг --data принимает JSON-объект; без него поле data
остаётся пустым.
# Простое событие без данных
subrose emit --name door_open
# Событие с данными
subrose emit --name wifi_connected --data '{"ssid":"HomeNet","rssi":-55}'
# В скрипте init.d / cron (например, при смене DNS)
/opt/subrose/bin/subrose emit --name dns_changed --data "{\"server\":\"$DNS\"}"
Shell — прямая запись в сокет (socat / nc)
Если subrose emit недоступен, событие пишут в сокет
напрямую. Подходит для скриптов init.d на Keenetic/OpenWrt, где нет
пути к бинарю.
# socat (широко доступен в OpenWrt/opkg)
echo '{"name":"reboot","data":{}}' | socat - UNIX-CONNECT:/var/run/subrose/subrose.sock
# nc с поддержкой unix-сокетов (BusyBox nc не поддерживает -U; используй socat)
# На Linux с полноценным nc:
echo '{"name":"reboot","data":{}}' | nc -U /var/run/subrose/subrose.sock
Python 3
Встроенный модуль socket — дополнительных зависимостей нет.
Работает на Linux, macOS и Raspberry Pi.
import socket, json
SOCK_PATH = "/var/run/subrose/subrose.sock" # или из конфига
def emit(name: str, data: dict = None):
payload = json.dumps({"name": name, "data": data or {}}) + "\n"
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect(SOCK_PATH)
s.sendall(payload.encode())
# Пример использования
emit("temperature", {"celsius": 42.5, "sensor": "cpu"})
emit("disk_full", {"path": "/data", "used_pct": 97})
C (POSIX)
Минимальный фрагмент для встраивания в системный демон или init-скрипт на OpenWrt / Keenetic. Зависимостей нет, только POSIX-сокеты.
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define SOCK_PATH "/var/run/subrose/subrose.sock"
int subrose_emit(const char *name, const char *data_json) {
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct sockaddr_un addr = {0};
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
close(fd); return -1;
}
char buf[4096];
int n = snprintf(buf, sizeof(buf),
"{\"name\":\"%s\",\"data\":%s}\n", name, data_json ? data_json : "{}");
write(fd, buf, n);
close(fd);
return 0;
}
/* Пример: */
int main(void) {
subrose_emit("sensor_alert", "{\"type\":\"overheat\",\"temp\":85}");
return 0;
}
Windows — named pipe (на подходе)
Рецепт заработает вместе с Windows-сборкой агента — она на подходе
(см. «Пакеты»). Агент будет слушать
именованный канал \\.\pipe\subrose: удобнее всего
subrose emit, либо CreateFile +
WriteFile в C/C++.
# PowerShell
$pipe = [System.IO.File]::Open('\\.\pipe\subrose',
[System.IO.FileMode]::Open, [System.IO.FileAccess]::Write,
[System.IO.FileShare]::None)
$bytes = [System.Text.Encoding]::UTF8.GetBytes(
'{"name":"vpn_up","data":{"peer":"10.0.0.1"}}' + "`n")
$pipe.Write($bytes, 0, $bytes.Length)
$pipe.Close()
05 · Механизм 2
Сбор данных по расписанию: скрипты-сборщики.
Агент периодически сверяется с расписанием (cronSchedule)
и запускает все исполняемые *.sh-файлы из директории
collectors. Результат каждого скрипта уходит на ноду отдельным
событием.
Расписание — стандартные пять полей cron:
минута час день-месяца месяц день-недели.
Владелец задаёт его командой SetCronSchedule в контракте
устройства; агент получает его при регистрации.
06 · Пример
Структура директории collectors и пример скрипта.
# Структура по умолчанию:
# <stateDir>/collectors/
# cpu_temp.sh
# disk_usage.sh
# wan_status.sh
# Пример: cpu_temp.sh (должен быть chmod +x)
#!/bin/sh
TEMP=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0)
# Агент ожидает валидный JSON-объект на stdout.
# Если stdout — не JSON, агент оборачивает: {"output":"..."}
printf '{"celsius":%d}' "$((TEMP / 1000))"
Имя события = имя файла скрипта (например, cpu_temp.sh).
Пример расписания для запуска каждые 5 минут: */5 * * * *.
Каждые 2 часа по будням: 0 */2 * * 1-5.
07 · Чтение событий
Чтение — через HTTP API ноды.
Накопленные события читаются через HTTP API ноды. Нода хранит до 50 последних событий на устройство в кольцевом буфере.
GET /device/events?address=0:<device-contract-address>
# Пример ответа:
{
"address": "0:abc...def",
"events": [
{ "name": "cpu_temp.sh", "data": {"celsius": 67}, "at": 1751000001 },
{ "name": "wifi_connected", "data": {"ssid": "HomeNet"}, "at": 1751000120 }
]
}
Поле at — Unix-время записи события на ноде (секунды).
Список идёт от старых к новым — свежее в конце. Этот эндпоинт — не
то же самое, что индексатор TON
/events/{abi}/{address}: тот показывает события
контракта устройства в TON.
08 · Протокол
Как устроена аутентификация события.
Агент подписывает каждое событие закрытым ключом устройства (device private key) перед отправкой на ноду. Нода проверяет подпись открытым ключом устройства (device public key), сохранённым при регистрации, в окне ±60 секунд. Событие без корректной подписи или с устаревшей меткой времени нода отклоняет.
Формула подписи:
sig = hex( ed25519_sign( devicePrivateKey,
sha256( address + ts + name ) ) )
Так в буфере ноды оказываются только события, доказанно пришедшие с зарегистрированного устройства: достаточно записать JSON в локальный канал, подпись добавит агент.
Подпись подтверждает подлинность события — его содержимое нода видит открытым.
09 · Вопросы
Перед тем как отправлять события.
GET /device/events или в веб-консоли Subrose — событие появляется там через несколько секунд после отправки.subrose emit или скриптов-сборщиков — остаются в буфере ноды (до 50 последних на устройство) и читаются через API или консоль, в TON их нет.SetCronSchedule, а stdout скрипта становится данными события.Начать
Первое событие за несколько минут.
Установка агента, включение событий в контракте устройства из
веб-консоли Subrose и команда
subrose emit --name hello --data '{}' — событие появится
через несколько секунд.