| layout | article |
|---|---|
| title | Plugin API |
| date | 2025-11-19 12:00:00 +0300 |
| categories | bary install |
| description | Полное руководство по созданию плагинов для BARY с продвинутыми возможностями |
Plugin API позволяет разработчикам создавать собственные плагины (драйверы) для интеграции новых устройств с системой BARY. Плагины работают как отдельные процессы и взаимодействуют с основным приложением через IPC (Inter-Process Communication).
Возможности:
- Поддержка любых IoT устройств и протоколов
- Изоляция драйверов в отдельных процессах
- Автоматическая установка зависимостей
- Логирование и мониторинг
- Публикация событий в реальном времени
- Mixins — модульное расширение функциональности
- Templates — конфигурационные файлы устройств
- Динамические capabilities — генерация интерфейса в runtime
- Settings_ex — динамические настройки с фильтрацией
my-plugin/
├── package.json # Зависимости и метаданные
├── tsconfig.json # Конфигурация TypeScript
├── webpack.config.js # Сборка плагина
├── compile.sh # Скрипт компиляции
├── debug.sh # Скрипт отладки
├── nodemon.json # Конфигурация dev-режима
├── src/
│ ├── mixins/ # Пользовательские миксины (опционально)
│ │ ├── params.ts
│ │ └── ipc.ts
│ ├── my-plugin.ts # Основной файл плагина
│ └── my-plugin.json # Метаданные плагина для BARY
├── core/ # Базовые классы (наследуются)
│ ├── base-module.ts
│ └── base-driver-module.ts
├── enums/
│ └── EventTypes.ts # Типы событий
├── lib/ # Вспомогательные библиотеки
│ ├── better-queue/ # Система очередей
│ ├── foibles/ # Система миксинов
│ ├── require-ex.ts # Автоматическая установка зависимостей
│ └── shared.functions.ts
├── templates/ # Конфигурационные файлы устройств (опционально)
│ └── device-config.json
└── dist/ # Скомпилированные файлы
{
"name": "bary-plugin-mydevice",
"version": "1.0.0",
"main": "src/my-plugin.ts",
"scripts": {
"build": "webpack --display_modules",
"debug": "nodemon --config nodemon.json"
},
"dependencies": {
"async-mutex": "^0.5.0",
"moment": "^2.27.0",
"node-ipc": "^9.1.1",
"winston": "^3.17.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/node": "^14.14.34",
"ts-loader": "^8.0.14",
"ts-node": "^10.3.0",
"typescript": "^4.1.3",
"webpack": "^4.46.0",
"webpack-cli": "^3.2.3"
}
}Метаданные и конфигурация плагина для BARY:
{
"name": "My Device Plugin",
"type": 1,
"icon": "hub",
"class_name": "my-plugin",
"module": "my-plugin",
"manufacturer": "Company Name",
"cloud": false,
"support_autoupdate": false,
"dynamic_capabilities": true,
"mac_address": false,
"capabilities": [],
"sort_index": 4,
"sub_devices": [
{
"class_name": "my-plugin.subdevice",
"name": "My SubDevice",
"dynamic_capabilities": true,
"mac_address": false,
"support_autoupdate": true,
"autoupdate_interval": 500,
"selectable": true,
"connect_config": true,
"type": 39,
"sort_index": 1,
"settings": [
{
"key": "identifier",
"name": "Device ID",
"type": "text",
"required": true
}
],
"commands": [
{
"command": "reset"
}
]
}
],
"settings": [
{
"key": "port",
"name": "Port",
"type": "text",
"required": true,
"defaultValue": "/dev/ttyUSB0"
},
{
"key": "baud_rate",
"name": "Baud rate",
"type": "select",
"items": [
{"id": 9600, "title": "9600"},
{"id": 19200, "title": "19200"},
{"id": 115200, "title": "115200"}
],
"defaultValue": 9600
},
{
"key": "enable_logging",
"name": "Enable logging",
"type": "checkbox"
}
],
"commands": [
{
"command": "scan_devices"
},
{
"command": "reset"
}
],
"display": [
{
"functions": [
{
"title": "Scan Mode",
"type": "scan_mode",
"view": "buttons",
"model": "device.:ident.status.scan_mode",
"buttons": [
{"value": "auto", "title": "Automatic"},
{"value": "manual", "title": "Manual"}
],
"data": {
"ident": ":ident",
"command": "scan_mode",
"value": ":scan_mode"
}
}
]
}
],
"dependencies": {
"my-device-sdk": "^2.0.0"
}
}Основные поля:
name— отображаемое имя плагинаtype— уникальный тип устройства (число)icon— иконка плагинаclass_name— техническое имя классаmodule— имя модуляmanufacturer— производитель (опционально)cloud— поддержка облакаsupport_autoupdate— автообновление статусаdynamic_capabilities— capabilities генерируются в runtimemac_address— требуется MAC-адресdisable_display_sort— отключить сортировку отображенияcapabilities— статические возможности устройстваsub_devices— конфигурация дочерних устройствsettings— статические настройки плагинаcommands— команды плагинаdisplay— кастомизация UI (опционально)dependencies— NPM-зависимости (устанавливаются автоматически)
Базовый класс для всех плагинов, обеспечивает IPC коммуникацию с BARY.
Основные свойства:
config— конфигурация приложенияipc— IPC клиент для связи с BARYevents— массив зарегистрированных событийrequestId— счетчик запросовexternal_driver— флаг внешнего драйвера
Основные методы:
// Отправка запроса с ожиданием ответаrequest(eventName: string,params: object): Promise<any>// Отправка запроса без ожидания ответаrequestEx(eventName: string,params: object): void// Логированиеlog(message: any): voiderror(message: any): void// Динамическая загрузка npm модулейrequire(ident: string,require: boolean): Promise<any>Расширяет baseModule дополнительными методами для работы с драйверами устройств.
Дополнительные свойства:
device_id— ID устройстваparams— параметры устройстваconfig— конфигурация устройстваlogger— Winston logger для записи логовident— идентификатор устройстваappDevices— список устройств приложенияstatusCache— кэш статусов для оптимизацииpluginTemplate— метаданные плагина из JSON
Жизненный цикл:
// 1. Инициализация плагинаinitDeviceEx(resolve,reject): void// 2. Подключение к устройствуconnectEx(resolve,reject): void// 3. Обработка командcommandEx(command,value,params,options,resolve,reject,status): void// 4. Получение списка дочерних устройствgetSubDevicesEx(resolve,reject,zones): void// 5. Обработка событий от подписанных устройств (опционально)onSubscribeDevice(params: any): voidДополнительные методы:
// Создание/обновление дочернего устройстваcheckSubDevice(model,key,name,params,zone_id): Promise<any>// Публикация событийpublish(eventType, ...params): voidpublishStatus(eventType,status): void// Формирование имени события для статусаeventTypeStatus(className,identifier?,key?): string// Подписка на события другого устройстваsubscribeDevice(ident,eventType): void// УведомленияsendNotify(message,options?): voidsendNotifyEx(body): voidsendPushNotification(message,email,title): void// Работа с устройствамиgetDevices(params?): Promise<Device[]>deviceCommand(ident,command,data,value): Promise<any>deviceEvent(ident,event,data): Promise<any>// Работа с БДgetTable(table,options): Promise<any[]>createTable(table,options): Promise<any>updateTable(table,options,where): Promise<any>getConfig(): Promise<any>getDeviceCustomData(ident,custom_data_id): Promise<any>// Загрузка templatesloadTemplate(ident,name,options?): any// ОчередиstartQueue(ident): voidcountQueue(ident): voiddoneQueue(ident,resolve,reject,inc?,error?): voidBARY использует библиотеку foibles для создания миксинов — модульного способа расширения функциональности классов.
// src/mixins/params.tsconst{Mixin}=require('../../lib/foibles');exportconstDEVICE_ID_INC=10000;exportconstParams=Mixin(parent=>classParamsextendsparent{capabilities=[];settings_ex=[];getheater_list(){returnthis.params?.heater_list||[];}getheater_list_devices(){returnthis.heater_list ? this.appDevices.filter(item=>this.heater_list.find(item1=>item1===item.ident)) : [];}getDeviceParam(params,device,key,defaultValue){returnparams[`${device.ident}_${key}`]!==undefined ? params[`${device.ident}_${key}`] : defaultValue;}updateCapabilities(){this.capabilities=[];this.heater_list_devices.forEach(device=>{this.capabilities.push({ident: 'power',index: DEVICE_ID_INC+device.id,display_name: `${device.name} (${device.zone_name})`,options: {info: `info_${DEVICE_ID_INC+device.id}`,ignore_changes: true}});});}});import{baseDriverModule}from'../core/base-driver-module';import{Params,DEVICE_ID_INC}from'./mixins/params';import{IPC}from'./mixins/ipc';// Расширение класса несколькими миксинамиclassMyPluginextendsbaseDriverModule.with(Params,IPC){connectEx(resolve,reject){this.getDevices({currentStatus: true}).then(devices=>{this.appDevices=devices;// Использование методов из миксина Paramsthis.updateCapabilities();// Доступ к геттерам миксинаthis.heater_list_devices.forEach(device=>{console.log(device.name);});// Публикация capabilitiesthis.publish(this.eventTypeStatus(this.pluginTemplate.class_name,this.id),{capabilities: this.capabilities,settings_ex: this.settings_ex});resolve({});});}}constapp=newMyPlugin();Плагины могут хранить конфигурационные файлы в папке /templates/:
my-plugin/
└── templates/
├── device1.json
├── device2.json
└── conditioner.json
{
"manufacturer": "Philio Technology Corp",
"manufacturerId": "0x013c",
"label": "PAR01",
"description": "Conditioner",
"devices": [
{
"productType": "0x0106",
"productId": "0x8290"
}
],
"firmwareVersion": {
"min": "0.0",
"max": "255.255"
},
"paramInformation": [
{
"#": "25",
"label": "Learn mode",
"valueSize": 2,
"minValue": 0,
"maxValue": 2048,
"defaultValue": 1
},
{
"#": "27",
"label": "Temperature",
"valueSize": 2,
"minValue": 16,
"maxValue": 30,
"defaultValue": 22
}
]
}// В методе плагинаconsttemplate=this.loadTemplate('my-plugin','conditioner.json');console.log(template.manufacturer);// "Philio Technology Corp"// С опциями (если template — это функция)consttemplate=this.loadTemplate('my-plugin','device-template',{deviceId: 123});import{baseDriverModule}from'../core/base-driver-module';import{EventTypes}from'../enums/EventTypes';classMyPluginextendsbaseDriverModule{/** * Инициализация плагина */initDeviceEx(resolve,reject){super.initDeviceEx(()=>{this.app.log('Плагин инициализирован');// Создание имени события для статусаthis.eventName=this.eventTypeStatus(this.pluginTemplate.class_name,this.id);resolve({});},reject);}/** * Подключение к устройству */connectEx(resolve,reject){this.app.log('Подключение к устройству...');// Получение списка устройствthis.getDevices({currentStatus: true}).then(devices=>{this.appDevices=devices;// Подписка на события устройствthis.appDevices.forEach(device=>{this.subscribeDevice(device.ident,'changed_power');});// Публикация динамических capabilitiesconstcapabilities=[];capabilities.push({ident: 'power',index: 1,display_name: 'Включить/выключить',options: {hideTitle: false}});this.publish(this.eventName,{capabilities});});resolve({});}/** * Обработка команд */commandEx(command,value,params,options,resolve,reject,status){this.app.log('Команда:',command,'Значение:',value);// Проверка прав администратора (если нужно)if(command==='admin_action'&&options&&!options.is_admin){returnreject({message: 'Access Denied!'});}switch(command){case'on':
// Включить устройствоresolve({state: 'on'});break;case'off':
// Выключить устройствоresolve({state: 'off'});break;case'status':
// Получить статусresolve({connected: true,state: 'on'});break;default:
reject({message: 'Неизвестная команда'});}}/** * Обработка событий от подписанных устройств */onSubscribeDevice(params: any){constdevice=this.appDevices.find(item=>item.ident===params.ident);if(device){device.currentStatus=params.currentStatus;// Реакция на изменение статусаthis.handleDeviceUpdate(device);}}}// Создание экземпляра плагинаconstapp=newMyPlugin();app.logging=true;Capabilities можно генерировать динамически в методе connectEx() или при изменении конфигурации.
connectEx(resolve,reject){constcapabilities=[];// Простой переключательcapabilities.push({ident: 'power',index: 1,display_name: 'Управление отоплением',options: {hideTitle: false,hideSeparator: false,hideBottomPadding: false,ignore_changes: false,read_only: false}});// Текстовое поле с измерениемcapabilities.push({ident: 'text',index: 2,display_name: 'Температура',options: {measure: '°C',read_only: true}});// Ползунок (range)capabilities.push({ident: 'target_temperature',index: 3,display_name: 'Желаемая температура',options: {minValue: 5,maxValue: 30,stepValue: 0.5,hideTitle: true}});// Capability с информационным полемcapabilities.push({ident: 'power',index: 10,display_name: 'Насос 1',options: {info: 'info_10',// Связано с status.info_10ignore_changes: true}});// Заголовокcapabilities.push({ident: 'title',index: 100,options: {title_only: 'Климат-контроль'},hide_background: true,hide_bottom_padding: true,hide_separator: true});// Capability для добавления/удаления устройствcapabilities.push({ident: 'power',index: 1,display_name: 'Add Z-Wave device',options: {link_devices: true}});capabilities.push({ident: 'power',index: 2,display_name: 'Remove Z-Wave device',options: {unlink_devices: true}});// Публикацияthis.publish(this.eventTypeStatus(this.pluginTemplate.class_name,this.id),{capabilities});resolve({});}hideTitle— скрыть заголовокhideSeparator— скрыть разделительhideBottomPadding— скрыть отступ снизуignore_changes— игнорировать измененияread_only— только для чтенияinfo— связь с информационным полем (например,info_10связано сstatus.info_10)measure— единица измерения (например,°C,%,W)minValue,maxValue,stepValue— для ползунков (range)value_on,value_off— значения для переключателейtitle_only— только заголовок без фона (для ident: 'title')link_devices,unlink_devices— для добавления/удаления устройств
Settings_ex позволяют создавать настройки с фильтрацией устройств по командам и типам.
connectEx(resolve,reject){constsettings_ex=[];// Выбор устройства с фильтрацией по командамsettings_ex.push({key: 'temperature_sensor',name: 'Датчик температуры',type: 'select',items: [],driver_support: [// Фильтр по поддерживаемым командам'supportTemperature','supportTemperatureEx'],multi: false// Одиночный выбор});// Множественный выбор устройствsettings_ex.push({key: 'heater_list',name: 'Список обогревателей',type: 'select',items: [],driver_support: ['supportPower','supportPowerEx'],driver_types: [5],// Фильтр по типу драйвераmulti: true// Множественный выбор});// Выбор комнатыsettings_ex.push({key: 'zone_id',name: 'Комната',type: 'select',items: [],zone_support: ['rooms']// Показывать только комнаты});// Условная видимостьsettings_ex.push({key: 'advanced_options',name: 'Дополнительные опции',type: 'text',visibleField: 'enable_advanced',// Показывать только еслиvisibleFieldValue: true// enable_advanced = true});// Текстовое поле с значением по умолчаниюsettings_ex.push({key: 'interval',name: 'Интервал обновления (мин)',type: 'text',defaultValue: 5});// Заголовок (группировка настроек)settings_ex.push({key: 'group_heating',name: 'Настройки отопления',type: 'title'});// Динамическое создание настроек для каждого устройстваthis.heater_list_devices.forEach(device=>{settings_ex.push({key: `${device.ident}_kp`,name: `${device.name} - Пропорциональная составляющая`,type: 'text',defaultValue: 0.5});});// Публикация вместе с capabilitiesthis.publish(this.eventTypeStatus(this.pluginTemplate.class_name,this.id),{capabilities, settings_ex});resolve({});}key— ключ настройкиname— отображаемое имяtype— тип поля (text,select,checkbox,title)items— элементы дляselectdefaultValue— значение по умолчаниюrequired— обязательное полеdriver_support— фильтрация устройств по поддерживаемым командамdriver_types— фильтрация устройств по типуmulti— множественный выбор (дляselect)zone_support— фильтрация зон (['rooms']— только комнаты)visibleField— поле, от которого зависит видимостьvisibleFieldValue— значение поля для отображенияdescription— описание настройкиgroup— группа настроек (например,settingsDevice)
checkSubDevice(model: string,// Модель устройства (например, 'zigbee2mqtt.light')key: string,// Уникальный ключname: string,// Название устройстваparams: object,// Параметры устройстваzone_id: number // ID зоны (null для автоматического определения)): Promise<any>Параметры устройства:
{icon: 'light',// Иконка устройстваidentifier: 'unique_id',// Уникальный идентификаторcapabilities: [// Возможности устройства{index: 0,property: 'state',ident: 'state',display_name: 'Состояние',access: 'rw',// r=read, w=write, rw=read+writehomekit: true,// Поддержка HomeKityandex: true,// Поддержка Яндекс Алисаsber: true,// Поддержка Сберoptions: {value_on: true,value_off: false,minValue: 0,maxValue: 100,stepValue: 1}}],settings_ex: [// Динамические настройки устройства{key: 'parameter_1',name: 'Parameter 1',type: 'text',defaultValue: 10}],parent_identifier: 123// ID родительского устройства}Доступные иконки:
- Освещение:
light,chandelier,rgb_lamp,rgb_strip,rgb_led,table_lamp,spotlight,sconce,facade_light - Развлечения:
tv,gamepad,music_note,speaker - Безопасность:
camera,security,intercom,doorlock,night,smoke,gas_leak,leak - Дом:
home,gates,gate1,gate2,gate3,gate4,socket,infrared,faucet,valve,pump,hub - Шторы/жалюзи:
view_column,louvers,marquise - Климат:
floor_heater,heater,heater1,convector,humidifier,ac_unit,fan,dryer,cooling,anti-icing - Датчики:
co2,voc,p2,multisensor,temp_sensor,humidity_sensor,door_sensor,counter,motion - Бытовая техника:
kettle,exhaust_hood,fridge,washing_machine,microwave,dish_washer,oven,stove,vacuum_cleaner,pool,squirt
Типы плагинов (driver_types):
BARY поддерживает 52 типа плагинов для различных устройств и сервисов:
- Шлюзы и сервисы: Шлюзы (1), Облачные сервисы (2), Провайдеры услуг (19), Система (39), Внешние API для камеры (43), Музыкальные сервисы (45)
- Медиа устройства: Игровые приставки (7), Ресиверы (8), Телевизоры (9), ТВ-приставки (18), Умные колонки (37), Интерактивные панели (50)
- Умный дом базовый: Умные розетки (3), Выключатели (12), Релейные модули (14), Эмуляторы IR/RF (16), Умные лампочки (22), Освещение (25), RGB Контроллеры (40), Диммеры (44)
- Климат-контроль: Термостаты (13), Кондиционеры (17), Системы вентиляции (33), Умные вентиляторы (41), Климат-контроль (47), Увлажнители (6)
- Датчики: Датчики температуры (11), Датчики влажности (23), Датчики освещенности (24), Датчики протечки (26), Датчики движения (27), Датчики напряжения (28), Датчики УФ (29), Датчики открытия окон и дверей (30), Комбинированные датчики (31), Детекторы дыма (36), Мониторы качества воздуха (21)
- Безопасность: Камеры (5), Дверные замки (32), Домофоны (42), Распознавание ГРЗ (48), СКУД (49)
- Бытовая техника: Пылесосы (4), Приводы штор (38), Уход за растениями (20)
- Инженерные системы: Счетчики электроэнергии (15), Водосчетчики (35), Умные контроллеры (34), Модули ввода-вывода (46), Модули управления устройствами (51)
- Голосовое управление: Голосовые ассистенты (52), Погода (10)
getDevices(params?: object): Promise<Device[]>Пример:
constdevices=awaitthis.getDevices({currentStatus: true});devices.forEach(device=>{this.app.log('Устройство:',device.name,'Статус:',device.currentStatus);});subscribeDevice(ident: string,eventType: string): voidПример:
// Подписка на изменение питанияthis.subscribeDevice('device-001','changed_power');// Обработка в onSubscribeDevice()onSubscribeDevice(params: any){constdevice=this.appDevices.find(item=>item.ident===params.ident);if(device){device.currentStatus=params.currentStatus;this.app.log('Статус изменился:',device.name,params.currentStatus);}}deviceCommand(ident: string,// Идентификатор устройстваcommand: string,// Командаdata: object,// Данныеvalue: any // Значение): Promise<any>Пример:
awaitthis.deviceCommand('light_bedroom','on',{},true);eventTypeStatus(className: string,identifier?: string,key?: string): stringПример:
consteventName=this.eventTypeStatus('my-plugin','device-001','temperature');// Результат: "status->my-plugin->device-001->temperature"// Для плагинаconsteventName=this.eventTypeStatus(this.pluginTemplate.class_name,this.id);// Результат: "status->my-plugin->driver-123"publishStatus(eventType: EventTypes,status: object): voidПример:
// Отправка данных с датчикаthis.publishStatus(EventTypes.UpdateTemperature,{temperature_living_room: 22.5,humidity_living_room: 45,connected: true});Оптимизация публикации:
Метод publishStatus автоматически:
- Публикует только измененные данные
- Отправляет числовые данные при изменении >10% или раз в 15 секунд
- Всегда отправляет события датчиков движения, открытия и т.д.
publish(eventType: EventTypes|string, ...params: any[]): voidПример:
this.publish(EventTypes.ChangedMotion,{parent_identifier: this.device_id,motion_sensor_1: true});// Публикация capabilities и settings_exthis.publish(this.eventTypeStatus(this.pluginTemplate.class_name,this.id),{capabilities, settings_ex,displays: [...]});sendNotify(message: string,options?: object): voidПример:
this.sendNotify('Устройство подключено успешно!');sendNotifyEx(body: object): voidПример:
// Обновление устройстваthis.sendNotifyEx({system: true,type: 'device-update',ident: this.ident});// Обновление настроекthis.sendNotifyEx({system: true,type: 'settings-update',ident: this.ident});sendPushNotification(message: string,email: string,title: string): voidПример:
this.sendPushNotification('Обнаружено движение в гостиной','user@example.com','Датчик движения');getConfig(): Promise<any>// Получение записейgetTable(table: string,options: object): Promise<any[]>// Создание записиcreateTable(table: string,options: object): Promise<any>// Обновление записейupdateTable(table: string,options: object,where: object): Promise<any>Пример:
// Получение всех устройств типа 'light'constlights=awaitthis.getTable('devices',{where: {type: 'light'}});// Создание записиawaitthis.createTable('custom_data',{device_id: 123,key: 'last_seen',value: newDate().toISOString()});// Обновлениеawaitthis.updateTable('devices',{name: 'Updated Name'},{where: {id: 123}});cloudRequest(params: object): Promise<any>// Создание очереди запросовconstQueue=require('../lib/better-queue/queue');this.requestQueue=newQueue((task,callback)=>{this.deviceCommand(task.ident,task.command,{},task.value).then(()=>{callback(null,{});}).catch(error=>{callback(error);});},{concurrent: 1,// Последовательная обработкаmaxRetries: 3});// Добавление задачи в очередьthis.requestQueue.push({ident: 'device-001',command: 'set_power',value: true},(error,result)=>{if(error){console.error('Error:',error);}else{console.log('Success:',result);}});// Встроенные методы для простых очередейthis.startQueue('commands');this.countQueue('commands');this.doneQueue('commands',resolve,reject);DeviceCreate— создание устройстваDeviceUpdate— обновление устройстваDeviceDelete— удаление устройстваDeviceConnect— подключение устройстваDeviceDiscover— обнаружение нового устройства
UpdateTemperature— обновление температурыUpdateThermostatTemp— обновление температуры термостатаUpdatePower— обновление мощностиUpdateAlert— предупреждение
ChangedMotion— изменение датчика движенияChangedMagnet— изменение геркона (открытие/закрытие)ChangedMagnetChange— изменение геркона с передачей измененийChangedPower— изменение состояния питанияChangedPowerChange— изменение состояния питания с передачей измененийChangedPowerLoad— изменение нагрузкиChangedAction— пользовательское действиеChangedLeakChange— обнаружение протечкиChangedInputChange— изменение входаChangedCounterChange— изменение счетчикаChangedChannel— изменение каналаChangedChannel0— изменение канала 0ChangedChannel1— изменение канала 1
MqttMessage— сообщение MQTT
NewStats— новая статистикаNewStatsEx— расширенная статистика
WiFiConnect— подключение Wi-FiWiFiConnected— Wi-Fi подключенWiFiNetworkFound— сеть Wi-Fi найдена
ApplicationReady— приложение готовоApplicationCreated— приложение созданоApplicationGetDevices— получение устройств приложенияApplicationDeviceCommand— команда устройствуApplicationAddDeviceQueue— добавление устройства в очередьApplicationAddScanQueue— добавление сканирования в очередьApplicationDriverReady— драйвер готовNewEvent— новое событие
DatabaseReady— БД готоваDatabaseConnected— БД подключенаDatabaseGetAllItems— получение всех элементовDatabaseUpdateItem— обновление элементаDatabaseUpdateDeviceParams— обновление параметров устройстваDatabaseCreateItem— создание элементаDatabaseQuery— запрос к БДDatabaseUpdate— обновление БДDatabaseQueueQuery— запрос в очереди БД
CheckSubDevice— проверка дочернего устройстваPublish— публикацияNotify— уведомлениеUpdateSensor— обновление датчикаRemoveSensor— удаление датчикаUpdateSensorEx— расширенное обновление датчика
import{baseDriverModule}from'../core/base-driver-module';import{EventTypes}from'../enums/EventTypes';importaxiosfrom'axios';classHttpDevicePluginextendsbaseDriverModule{privatebaseUrl: string;initDeviceEx(resolve,reject){super.initDeviceEx(()=>{// Получаем URL из параметровthis.baseUrl=this.params.url||'http://192.168.1.100';this.app.log('HTTP устройство:',this.baseUrl);resolve({});},reject);}connectEx(resolve,reject){// Проверяем доступность устройстваaxios.get(`${this.baseUrl}/status`).then(response=>{this.app.log('Устройство онлайн');this.startPolling();resolve({});}).catch(error=>{reject({message: 'Устройство недоступно'});});}startPolling(){setInterval(()=>{axios.get(`${this.baseUrl}/sensors`).then(response=>{this.publishStatus(EventTypes.UpdateTemperature,{temperature_sensor: response.data.temperature,humidity_sensor: response.data.humidity,connected: true});}).catch(error=>{this.app.error('Ошибка получения данных:',error.message);});},10000);// Каждые 10 секунд}commandEx(command,value,params,options,resolve,reject,status){switch(command){case'on':
axios.post(`${this.baseUrl}/control`,{power: 'on'}).then(()=>resolve({state: 'on'})).catch(error=>reject({message: error.message}));break;case'off':
axios.post(`${this.baseUrl}/control`,{power: 'off'}).then(()=>resolve({state: 'off'})).catch(error=>reject({message: error.message}));break;case'status':
axios.get(`${this.baseUrl}/status`).then(response=>resolve(response.data)).catch(error=>reject({message: error.message}));break;default:
reject({message: 'Неизвестная команда'});}}}constapp=newHttpDevicePlugin();app.logging=true;import{baseDriverModule}from'../core/base-driver-module';import{EventTypes}from'../enums/EventTypes';import{Params}from'./mixins/params';classDashboardPluginextendsbaseDriverModule.with(Params){appDevices=[];initDeviceEx(resolve,reject){super.initDeviceEx(()=>{this.app.log('Dashboard plugin initialized');this.eventName=this.eventTypeStatus(this.pluginTemplate.class_name,this.id);resolve({});},reject);}connectEx(resolve,reject){this.getDevices({currentStatus: true}).then(devices=>{this.appDevices=devices;// Подписка на события устройствthis.appDevices.forEach(device=>{this.subscribeDevice(device.ident,'changed_power');});// Динамическое создание capabilitiesthis.updateCapabilities();this.updateSettingsEx();// Публикация capabilities и settings_exthis.publish(this.eventName,{capabilities: this.capabilities,settings_ex: this.settings_ex});resolve({});});}updateCapabilities(){this.capabilities=[];constdeviceCount=this.params.device_count||0;// Создание capabilities для каждого устройстваfor(leti=0;i<deviceCount;i++){constdeviceIdent=this.params[`device_ident_${i}`];constdeviceType=this.params[`device_type_${i}`];if(deviceIdent){this.capabilities.push({ident: 'power',index: i+1,display_name: `Device ${i+1}`,options: {hideTitle: false,info: `info_${i}`,ignore_changes: false}});}}}updateSettingsEx(){this.settings_ex=[];constdeviceCount=this.params.device_count||4;// Заголовокthis.settings_ex.push({key: 'devices_group',name: 'Devices Configuration',type: 'title'});// Создание настроек для каждого устройстваfor(leti=0;i<deviceCount;i++){// Тип устройстваthis.settings_ex.push({key: `device_type_${i}`,name: `Device ${i+1} Type`,type: 'select',items: [{id: 'switch',title: 'Switch'},{id: 'sensor',title: 'Sensor'},{id: 'light',title: 'Light'}],defaultValue: 'switch'});// Выбор устройства с фильтрациейthis.settings_ex.push({key: `device_ident_${i}`,name: `Device ${i+1}`,type: 'select',items: [],driver_support: ['supportPower'],multi: false});}}onSubscribeDevice(params: any){// Обработка событий от подписанных устройствconstdevice=this.appDevices.find(item=>item.ident===params.ident);if(device){device.currentStatus=params.currentStatus;this.app.log('Device status updated:',device.name,params.currentStatus);// Публикация обновленного статусаthis.publishStatus(EventTypes.UpdatePower,{[`status_${device.ident}`]: params.currentStatus.power});}}commandEx(command,value,params,options,resolve,reject,status){// Проверка прав администратораif(command==='reset'&&options&&!options.is_admin){returnreject({message: 'Access Denied!'});}switch(command){case'update_config':
// Обновление конфигурацииthis.updateCapabilities();this.updateSettingsEx();this.publish(this.eventName,{capabilities: this.capabilities,settings_ex: this.settings_ex});// Системное уведомление об обновлении настроекthis.sendNotifyEx({system: true,type: 'settings-update',ident: this.ident});resolve({success: true});break;case'status':
resolve({connected: true,devices_count: this.capabilities.length});break;default:
reject({message: 'Неизвестная команда'});}}}constapp=newDashboardPlugin();app.logging=true;import{baseDriverModule}from'../core/base-driver-module';import{EventTypes}from'../enums/EventTypes';classGatewayPluginextendsbaseDriverModule{privatedevices: Map<string,any>=newMap();initDeviceEx(resolve,reject){super.initDeviceEx(()=>{this.app.log('Шлюз инициализирован');resolve({});},reject);}connectEx(resolve,reject){// Сканирование устройствthis.scanDevices().then(()=>{resolve({});});}asyncscanDevices(){// Обнаружение устройств (например, через Zigbee, Z-Wave и т.д.)constfoundDevices=[{id: '0x123',type: 'light',name: 'Свет в гостиной'},{id: '0x456',type: 'sensor',name: 'Датчик температуры'}];for(constdeviceoffoundDevices){awaitthis.registerDevice(device);}}asyncregisterDevice(device: any){letmodel,icon,capabilities;if(device.type==='light'){model='gateway.light';icon='light';capabilities=[{property: 'state',ident: 'state',display_name: 'Включено',access: 'rw',homekit: true,yandex: true,options: {value_on: true,value_off: false}},{property: 'brightness',ident: 'brightness',display_name: 'Яркость',access: 'rw',homekit: true,options: {minValue: 0,maxValue: 100,stepValue: 1}}];}elseif(device.type==='sensor'){model='gateway.temperature';icon='temp_sensor';capabilities=[{property: 'temperature',ident: 'temperature',display_name: 'Температура',access: 'r',scale: '°C'}];}constresult=awaitthis.checkSubDevice(model,device.id,device.name,{icon,identifier: device.id, capabilities},null);this.devices.set(device.id,device);this.app.log('Зарегистрировано устройство:',device.name);}commandEx(command,value,params,options,resolve,reject,status){constdeviceId=options.ident;// Отправка команды конкретному дочернему устройствуif(this.devices.has(deviceId)){// Здесь отправка команды через протокол шлюзаthis.app.log('Команда для устройства',deviceId,':',command,value);resolve({success: true});}else{reject({message: 'Устройство не найдено'});}}getSubDevicesEx(resolve,reject,zones){// Возврат списка всех дочерних устройствconstdevices=Array.from(this.devices.values()).map(device=>({class_name: 'GatewayDevice',identifier: device.id,name: device.name,params: {icon: device.type==='light' ? 'light' : 'temp_sensor',identifier: device.id}}));resolve(devices);}}constapp=newGatewayPlugin();app.logging=true;Для ручного тестирования плагина:
constapp=newMyPlugin();app.logging=true;// Инициализация с тестовыми параметрамиapp.initDevice({params: {url: 'http://192.168.1.100',port: 80}}).then(()=>{// Подключениеapp.connect({id: 1}).then(()=>{// Тестовая командаapp.command({command: 'status',id: 1}).then(result=>{console.log('Результат:',result);});});});// Разные уровни логированияthis.app.log('Информация');// INFOthis.app.error('Ошибка');// ERRORthis.app.debug('Отладка');// DEBUGthis.app.info('Информация');// INFO// Логирование с автоматическим таймстампом и использованием памяти// Формат: HH:mm:ss [memory] сообщениеЛоги сохраняются в ${params.log_path}/${module}-${id}.log с ротацией:
- Максимальный размер файла: 10 МБ
- Количество файлов: 5
# Запуск с логированием
node dist/my-plugin.js 123 logging=true
# Удаленное подключение к BARY
node dist/my-plugin.js 123 host=192.168.1.100 port=8000commandEx(command,value,params,options,resolve,reject,status){try{// Ваш кодresolve(result);}catch(error){this.app.error('Ошибка выполнения команды:',error.message);reject({message: error.message,ignore: false});}}connectEx(resolve,reject){consttryConnect=(attempts=0)=>{this.connectToDevice().then(()=>resolve({})).catch(error=>{if(attempts<3){this.app.log(`Попытка переподключения ${attempts+1}/3`);setTimeout(()=>tryConnect(attempts+1),5000);}else{reject({message: 'Не удалось подключиться'});}});};tryConnect();}// Используйте publishStatus для автоматической оптимизацииthis.publishStatus(EventTypes.UpdateTemperature,{temperature: 22.5,// Отправится только при изменении >10%humidity: 45// или раз в 15 секунд});// Для важных событий используйте publish напрямуюthis.publish(EventTypes.ChangedMotion,{motion: true// Отправится сразу});// Разделяйте функциональность на миксины// mixins/params.ts — работа с параметрами// mixins/ipc.ts — дополнительная IPC логика// mixins/servo.ts — управление сервоприводамиclassMyPluginextendsbaseDriverModule.with(Params,IPC,Servo){// Чистый и модульный код}В my-plugin.json указывайте только специфичные для вашего плагина зависимости:
{
"dependencies": {
"my-device-sdk": "^2.0.0",
"zwave-js": "^9.2.2"
}
}BARY автоматически установит их при первом запуске плагина через RequireEx.
// Создайте очередь для команд устройствуconstQueue=require('../lib/better-queue/queue');this.requestQueue=newQueue((task,callback)=>{this.deviceCommand(task.ident,task.command,{},task.value).then(()=>callback(null,{})).catch(error=>callback(error));},{concurrent: 1,maxRetries: 3});// Используйте очередь вместо прямых вызововthis.requestQueue.push({ident: device.ident,command: 'set_power',value: true});# Установка зависимостей
npm install
# Сборка
npm run build
# Отладка с автоперезагрузкой
npm run debug# Компиляция и упаковка в ZIP
./compile.sh 1.0.0
# С копированием на сервер
./compile.sh 1.0.0 user@server:/path/to/pluginsРезультат — ZIP-архив в папке release/ с структурой:
plugins/
├── my-plugin.js
└── my-plugin.json
- Официальный сайт:https://bary.io
- Email:support@bary.io
- Telegram:@bary_support
- Примеры плагинов:https://github.com/bary-io/plugins
Версия документации: 2.0 (2025-11-19)
Дополнения: Mixins, Templates, Динамические capabilities, Settings_ex, Better-queue, sendNotifyEx, eventTypeStatus, subscribeDevice, onSubscribeDevice