Plugin API
August 30, 2021Plugin API
Введение
Plugin API позволяет разработчикам создавать собственные плагины (драйверы) для интеграции новых устройств с системой BARY. Плагины работают как отдельные процессы и взаимодействуют с основным приложением через IPC (Inter-Process Communication).
Возможности:
- Поддержка любых IoT устройств и протоколов
- Изоляция драйверов в отдельных процессах
- Автоматическая установка зависимостей
- Логирование и мониторинг
- Публикация событий в реальном времени
Установка и структура проекта
Базовая структура плагина
my-plugin/
├── package.json # Зависимости и метаданные
├── tsconfig.json # Конфигурация TypeScript
├── webpack.config.js # Сборка плагина
├── plugin.json # Метаданные плагина для BARY
├── core/ # Базовые классы (наследуются)
│ ├── base-module.ts
│ └── base-driver-module.ts
├── src/
│ └── my-plugin.ts # Основной файл плагина
└── dist/ # Скомпилированные файлы
package.json
{
"name": "bary-plugin-mydevice",
"version": "1.0.0",
"main": "src/my-plugin.ts",
"dependencies": {
"async-mutex": "^0.5.0",
"moment": "^2.27.0",
"node-ipc": "^9.1.1",
"winston": "^3.17.0"
},
"devDependencies": {
"@types/node": "^14.14.34",
"ts-loader": "^8.0.14",
"typescript": "^4.1.3",
"webpack": "^4.46.0"
}
}
plugin.json
{
"module": "my-plugin",
"name": "My Device Plugin",
"version": "1.0.0",
"description": "Поддержка устройств MyDevice",
"dependencies": {
"custom-library": "^2.0.0"
}
}
Базовые классы
baseModule
Базовый класс для всех плагинов, обеспечивает IPC коммуникацию с BARY.
Основные свойства:
config— конфигурация приложенияipc— IPC клиент для связи с BARYevents— массив зарегистрированных событий
Основные методы:
// Отправка запроса с ожиданием ответа
request(eventName: string, params: object): Promise<any>
// Отправка запроса без ожидания ответа
requestEx(eventName: string, params: object): void
// Логирование
log(message: any): void
error(message: any): void
// Динамическая загрузка npm модулей
require(ident: string, require: boolean): Promise<any>
baseDriverModule
Расширяет baseModule дополнительными методами для работы с драйверами устройств.
Дополнительные свойства:
device_id— ID устройстваparams— параметры устройстваconfig— конфигурация устройстваlogger— Winston logger для записи логов
Жизненный цикл:
// 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
Создание плагина
Минимальный пример
import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';
class MyPlugin extends baseDriverModule {
/**
* Инициализация плагина
*/
initDeviceEx(resolve, reject) {
super.initDeviceEx(() => {
this.app.log('Плагин инициализирован');
resolve({});
}, reject);
}
/**
* Подключение к устройству
*/
connectEx(resolve, reject) {
this.app.log('Подключение к устройству...');
// Здесь код подключения к устройству
resolve({});
}
/**
* Обработка команд
*/
commandEx(command, value, params, options, resolve, reject, status) {
this.app.log('Команда:', command, 'Значение:', value);
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: 'Неизвестная команда'});
}
}
}
// Создание экземпляра плагина
const app = new MyPlugin();
app.logging = true;
API методы
Работа с устройствами
Создание дочернего устройства
checkSubDevice(
model: string, // Модель устройства (например, 'zigbee2mqtt.light')
key: string, // Уникальный ключ
name: string, // Название устройства
params: object, // Параметры устройства
zone_id: number // ID зоны
): Promise<any>
Параметры устройства:
{
icon: 'light', // Иконка устройства
identifier: 'unique_id', // Уникальный идентификатор
capabilities: [ // Возможности устройства
{
index: 0,
property: 'state',
ident: 'state',
display_name: 'Состояние',
access: 'rw', // r=read, w=write
homekit: true,
yandex: true,
options: {
value_on: true,
value_off: false
}
}
],
parent_identifier: 123 // ID родительского устройства
}
Доступные иконки:
- Освещение:
light,chandelier,rgb_lamp,rgb_strip,table_lamp,spotlight,sconce - Безопасность:
camera,security,intercom,doorlock,door_sensor,motion,leak,gas_leak,smoke - Климат:
heater,convector,humidifier,ac_unit,fan,temp_sensor,humidity_sensor - Мультимедиа:
tv,gamepad,music_note - Бытовая техника:
kettle,fridge,washing_machine,microwave,vacuum_cleaner - Другое:
socket,gates,valve,hub
Получение списка устройств
getDevices(params?: object): Promise<Device[]>
Пример:
const devices = await this.getDevices({currentStatus: true});
devices.forEach(device => {
this.app.log('Устройство:', device.name, 'Статус:', device.currentStatus);
});
Отправка команды другому устройству
deviceCommand(
ident: string, // Идентификатор устройства
command: string, // Команда
data: object, // Данные
value: any // Значение
): Promise<any>
Пример:
await this.deviceCommand('light_bedroom', 'on', {}, true);
Публикация событий
Публикация статуса устройства
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, ...params: any[]): void
Пример:
this.publish(EventTypes.ChangedMotion, {
parent_identifier: this.device_id,
motion_sensor_1: true
});
Уведомления
Отправка уведомления пользователю
sendNotify(message: string, options?: object): void
Пример:
this.sendNotify('Устройство подключено успешно!');
Push-уведомление
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'
const lights = await this.getTable('devices', {
where: {type: 'light'}
});
// Создание записи
await this.createTable('custom_data', {
device_id: 123,
key: 'last_seen',
value: new Date().toISOString()
});
Облачные запросы
cloudRequest(params: object): Promise<any>
Типы событий (EventTypes)
Устройства
DeviceCreate— создание устройстваDeviceUpdate— обновление устройстваDeviceDelete— удаление устройстваDeviceConnect— подключение устройстваDeviceDiscover— обнаружение нового устройства
Обновления данных
UpdateTemperature— обновление температурыUpdatePower— обновление мощностиUpdateAlert— предупреждение
События датчиков
ChangedMotion— изменение датчика движенияChangedMagnet— изменение геркона (открытие/закрытие)ChangedPower— изменение состояния питанияChangedAction— пользовательское действиеChangedLeakChange— обнаружение протечки
Приложение
ApplicationReady— приложение готовоApplicationDeviceCommand— команда устройствуNewEvent— новое событие
База данных
DatabaseReady— БД готоваDatabaseQuery— запрос к БДDatabaseUpdate— обновление БД
Примеры плагинов
Пример 1: Плагин для HTTP устройств
import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';
import axios from 'axios';
class HttpDevicePlugin extends baseDriverModule {
private baseUrl: 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: 'Неизвестная команда'});
}
}
}
const app = new HttpDevicePlugin();
app.logging = true;
Пример 2: Плагин-шлюз с дочерними устройствами
import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';
class GatewayPlugin extends baseDriverModule {
private devices: Map<string, any> = new Map();
initDeviceEx(resolve, reject) {
super.initDeviceEx(() => {
this.app.log('Шлюз инициализирован');
resolve({});
}, reject);
}
connectEx(resolve, reject) {
// Сканирование устройств
this.scanDevices().then(() => {
resolve({});
});
}
async scanDevices() {
// Обнаружение устройств (например, через Zigbee, Z-Wave и т.д.)
const foundDevices = [
{id: '0x123', type: 'light', name: 'Свет в гостиной'},
{id: '0x456', type: 'sensor', name: 'Датчик температуры'}
];
for (const device of foundDevices) {
await this.registerDevice(device);
}
}
async registerDevice(device: any) {
let model, icon, capabilities;
if (device.type === 'light') {
model = 'gateway.light';
icon = 'light';
capabilities = [
{
property: 'state',
ident: 'state',
display_name: 'Включено',
access: 'rw',
homekit: 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}
}
];
} else if (device.type === 'sensor') {
model = 'gateway.temperature';
icon = 'temp_sensor';
capabilities = [
{
property: 'temperature',
ident: 'temperature',
display_name: 'Температура',
access: 'r',
scale: '°C'
}
];
}
const result = await this.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) {
const deviceId = options.ident;
// Отправка команды конкретному дочернему устройству
if (this.devices.has(deviceId)) {
// Здесь отправка команды через протокол шлюза
this.app.log('Команда для устройства', deviceId, ':', command, value);
resolve({success: true});
} else {
reject({message: 'Устройство не найдено'});
}
}
getSubDevicesEx(resolve, reject, zones) {
// Возврат списка всех дочерних устройств
const devices = 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);
}
}
const app = new GatewayPlugin();
app.logging = true;
Отладка плагинов
Локальный запуск
Для ручного тестирования плагина:
const app = new MyPlugin();
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('Информация'); // INFO
this.app.error('Ошибка'); // ERROR
this.app.debug('Отладка'); // DEBUG
this.app.info('Информация'); // INFO
Логи сохраняются в /var/log/bary/plugin-name.log с ротацией:
- Максимальный размер файла: 10 МБ
- Количество файлов: 5
Параметры запуска
# Запуск с логированием
node dist/my-plugin.js 123 logging=true
# Удаленное подключение к BARY
node dist/my-plugin.js 123 host=192.168.1.100 port=8000
Лучшие практики
1. Обработка ошибок
commandEx(command, value, params, options, resolve, reject, status) {
try {
// Ваш код
resolve(result);
} catch (error) {
this.app.error('Ошибка выполнения команды:', error.message);
reject({message: error.message, ignore: false});
}
}
2. Переподключение при ошибках
connectEx(resolve, reject) {
const tryConnect = (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();
}
3. Оптимизация публикации данных
// Используйте publishStatus для автоматической оптимизации
this.publishStatus(EventTypes.UpdateTemperature, {
temperature: 22.5, // Отправится только при изменении >10%
humidity: 45 // или раз в 15 секунд
});
// Для важных событий используйте publish напрямую
this.publish(EventTypes.ChangedMotion, {
motion: true // Отправится сразу
});
4. Управление зависимостями
В plugin.json указывайте только специфичные для вашего плагина зависимости:
{
"dependencies": {
"my-device-sdk": "^2.0.0"
}
}
BARY автоматически установит их при первом запуске плагина.
Публикация плагина
- Соберите плагин:
npm run build - Создайте архив:
tar -czf my-plugin.tar.gz dist/ package.json plugin.json - Установка пользователем:
- Загрузите архив в BARY через веб-интерфейс
- BARY автоматически установит зависимости и запустит плагин
Поддержка
Если у вас возникли вопросы по разработке плагинов:
- Email: support@bary.io
- Telegram: @bary_support
- Примеры плагинов: https://github.com/bary-io/plugins