К содержимому
BlueTraktor

Как плагин общается с системой

Реальные операции Plugin API 1.2, точные пути USEPI, события и границы доступа. Что поддерживается, а чего в SDK пока нет.

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

Плагин запускается менеджером, затем PluginSession устанавливает служебное соединение и получает config_json, grants и согласованную версию API. PluginClient отправляет разрешённые запросы хосту. Адреса сервера сайта, пароль пользователя или доступ к базе для этого не нужны.

Сам процесс не получает полномочия того администратора, который нажал «Запустить». Его полномочия определяются пакетом и grants конкретного экземпляра. Форма настроек позволяет пользователю выбрать источник, но выбранный путь ещё должен быть разрешён.

  1. Объявите необходимые capabilities в package/plugin.toml.
  2. Опишите поля настроек в JSON Schema и элементы формы в UI schema.
  3. После PluginSession::connect проверьте конфигурацию и раскрытые grants, затем вызовите ready().
  4. Читайте данные через PluginClient, одновременно слушая wait_for_shutdown(). Для постоянных изменений используйте подписку, а не цикл частого опроса.

Доступные операции

usepi.read и usepi.subscribe независимы: право разового чтения не разрешает подписку и наоборот. Наличие метода в SDK не заменяет разрешение экземпляра, лицензию или поддержку API целевой установкой.

ЗадачаМетод SDKРазрешение и результат
Один текущий снимокresolve_usepi(server_id, path)usepi.read для выбранного пути; возвращает байты JSON, которые нужно разобрать и проверить
Изменения одного листаsubscribe_usepi(server_id, path), next(), cancel()Точный usepi.subscribe grant; начальный снимок и текущие изменения, не история всех измерений
Отправка результатаpublish_mqtt(server_id, topic, payload, retain)mqtt.publish для выбранного сервера и топика; принятие публикации QoS 0, не подтверждение доставки
Готовность и остановкаready(), wait_for_shutdown()Жизненный цикл процесса; готовность не проверяет бизнес-результат
Вызов согласованного расширенияnext_invocation(), respond_json()Отдельный одобренный contribution; это входящий вызов расширения, не общий доступ к разделам системы

Можно ли получить список шаблонов

Нет: в публичном Plugin API 1.2 нет операции списка шаблонов, универсальных шаблонов, устройств, пользователей или дашбордов. Также нет CRUD-операций для создания, изменения и удаления этих записей. resolve_usepi читает данные выбранного источника, а не каталог сохранённых шаблонов.

HTTP-маршруты пользовательского интерфейса не являются Plugin Host API. Не передавайте плагину токен администратора и не пытайтесь обходить sandbox запросами к внутренним страницам. Для такого сценария потребуется отдельное расширение публичного контракта с ограничением доступа к записям и пагинацией; сейчас оно не предоставляется.

Если расчёту нужны параметры оборудования, передайте их через типизированную конфигурацию либо подключите согласованные поля USEPI. Это не способ выгрузить весь каталог и не обещание доступа ко всем моделям.

СценарийСостояние
Читать согласованное значение и считать результатПоддерживается
Реагировать на изменения этого значенияПоддерживается через подписку
Вывести результат в MQTT и привязать к виджетуПоддерживается при настроенном источнике и правах
Получить список шаблонов или создать устройствоНет публичного API
Подписаться напрямую на MQTTНет метода mqtt.subscribe в этом SDK
Добавить произвольный React/JS или системное менюНе разрешено обычному плагину

Как записать путь USEPI

server_id — внутренний ID зарегистрированного подключения, а не его название, hostname или адрес сайта. path — массив отдельных сегментов канонического пути листа. Имя поля значения обычно является последним сегментом; не теряйте его при переносе ссылки из интерфейса.

Представление ссылки в редакторе и аргументы SDK различаются. Пример ниже использует вымышленный ID demo-source; на своей установке выберите реальный источник через настройки экземпляра. Для подписки не добавляйте ведущий root, адрес брокера, обёртку @[...], wildcard или несколько сегментов в одной строке.

Scope подписки не поддерживает *, +, # и слеш внутри сегмента. Неподдерживаемый путь нужно отклонить, а не сокращать до родителя: сокращение может открыть лишние данные.

text
Ссылка в редакторе:
@["demo-source","pump","pressure","value"]

Аргументы SDK:
server_id = "demo-source"
path = ["pump", "pressure", "value"]

Точный grant подписки:
usepi.subscribe:demo-source/pump/pressure/value

Снимок и проверка типа

Фрагмент ниже выполняется после подключения и ready() внутри вашей асинхронной функции. resolve_usepi требует отдельный grant usepi.read:demo-source/pump/pressure/value. SDK возвращает JSON, а не готовый f64: объект, null и числовая строка требуют явной обработки в логике плагина.

Не превращайте отсутствующее значение в ноль и не извлекайте произвольное поле объекта догадкой. Опишите допустимый формат источника и единиц в инструкции вашего плагина. Разбор десятичной запятой или единиц внутри текста нужно задавать отдельным явным правилом.

rust
let bytes = client.resolve_usepi(
    "demo-source",
    vec!["pump".into(), "pressure".into(), "value".into()],
).await?;
let input: serde_json::Value = serde_json::from_slice(&bytes)?;
let number = input.as_f64().or_else(|| {
    input.as_str()?.trim().parse::<f64>().ok()
}).filter(|value| value.is_finite());
// None means invalid/missing numeric input, not a zero measurement.

События вместо постоянного опроса

subscribe_usepi открывает поток для одного точного листа. Первый Snapshot содержит found: false для отсутствующего листа и found: true для существующего значения, включая JSON null. Следующие Update относятся к текущему состоянию; это не архив каждой промежуточной мутации.

next() ожидает событие, скрывает служебные idle-ответы и проверяет последовательность. Revision относится к дереву источника: пропуски номеров нормальны. Нельзя считать разницу revision числом пропущенных измерений именно этого листа.

Reset, Lagged и Cancelled завершают старый поток. После ошибки или потери связи откройте новую подписку с ограниченной задержкой, получите новый снимок и не трактуйте его как гарантированно новое измерение. cancel().await подтверждает отмену; Drop выполняет только best-effort очистку.

ОграничениеТекущий предел
Подписки8 на экземпляр, 128 на установку
Буфер одной подписки32 события, до 256 КиБ с учётом служебных данных
Одно значение192 КиБ JSON на уровне Host API; плагин может задать меньший лимит
Неактивная подпискаLease 30 секунд без NEXT
История и повторное воспроизведениеНе предоставляются

Где появится результат

publish_mqtt отправляет байты в разрешённый топик зарегистрированного сервера. Пример публикует JSON с числом. retain=false не сохраняет retained-сообщение, QoS 0 не даёт гарантии доставки. Успех не создаёт шаблон, устройство, формулу или виджет автоматически.

  1. Настройте отдельный безопасный топик результата, который не используется для команд оборудованию.
  2. Разрешите точный mqtt.publish:demo-source/demo/pressure-normalized для экземпляра.
  3. Проверьте результат на тестовом получателе; убедитесь, что нужный сервер принимает этот топик в дерево данных.
  4. В редакторе модели или виджета выберите появившийся лист результата. Проверьте его значение и единицу измерения.
  5. Не публикуйте результат обратно в исходный топик без специально продуманной защиты от обратной связи.

Таймаут или разрыв после публикации не доказывает, что публикации не было. Немедленный повтор может дублировать эффект. Для управляющих действий нужны собственный протокол подтверждений и защита от повторов; этот пример предназначен только для телеметрии.

rust
let payload = serde_json::to_vec(&serde_json::json!({ "value": 4.2 }))?;
client.publish_mqtt(
    "demo-source", "demo/pressure-normalized", payload, false,
).await?;

Минимальный набор разрешений

Предпочитайте config-bound grants API 1.2: package объявляет привязку к полям конфигурации, а экземпляр подтверждает раскрытый точный путь. Один BTP можно использовать с разными источниками без встраивания рабочих адресов в код.

В текущем контракте config-bound форма предназначена для usepi.subscribe и mqtt.publish. Не придумывайте usepi.read:config, списочные scopes или методы по аналогии. Для разового чтения используйте поддержанный статический scope и явно проверяйте необходимые права.

toml
[[capabilities]]
name = "usepi.subscribe:config:/sourceServerId:/sourcePath"
reason = "Receive only the selected measurement"
required = true

[[capabilities]]
name = "mqtt.publish:config:/destinationServerId:/destinationTopic"
reason = "Publish the processed measurement"
required = true