Создание плагинов - noant/Lazurite GitHub Wiki

Функциональность Lazurite можно расширять с помощью плагинов, написанных на C#.

Для написания плагина понадобится создать проект C# WPF с целевой архитектурой x86 и версией .NET Framework <= 4.7.1.

Основные библиотеки, которые необходимо добавить в проект, можно скачать здесь.

После создания проектов и добавления библиотек должна получиться следующая картина:

Затем нужно будет скачать архиватор плагинов Lazurite. Он нужен для того, чтобы все библиотеки плагина, которые появятся после создания релиза, можно было упаковать в файл *.pyp. Создавать этот файл можно каждый раз вручную после постройки релиза, а можно автоматизированно, создав событие сборки. Один из примеров создания файла плагина автоматически.

Окно архиватора плагинов.

Интерфейс IAction

Основной интерфейс для создания плагинов - IAction. Он находится в пространстве имен Lazurite.ActionsDomain.

    public interface IAction
    {
        string GetValue(ExecutionContext context);
        void SetValue(ExecutionContext context, string value);
        string Caption { get; set; }
        ValueTypeBase ValueType { get; set; }
        void Initialize();
        bool UserInitializeWith(ValueTypeBase valueType, bool inheritsSupportedValues);
        event ValueChangedEventHandler ValueChanged;
        bool IsSupportsEvent { get; }
        bool IsSupportsModification { get; }
    }

Классы, наследуемые от этого интерфейса, будут добавлены в список всех действий и их можно будет выполнить в алгоритме сценария. Каждое действие должно иметь свой тип данных (FloatValueType, InfoValueType и т.д.), все доступные типы находятся в пространстве имен Lazurite.ActionsDomain.ValueTypes. Далее следует немного рассмотреть базовый класс типов ValueTypeBase:

    public abstract class ValueTypeBase
    {
        public string[] AcceptedValues { get; set; }
    .....

Свойство AcceptedValues содержит в себе значения, доступные для выставления в действии. Например, в типе данных Статус это будет список строк, а в типе Число - максимум и минимум.

Таким образом, задав значение ValueType интерфейса IAction, будет задан тип значения действия. Это свойство необходимо задавать на этапе инициализации или настройки (о них ниже) действия.

Метод bool UserInitializeWith(ValueTypeBase valueType, bool inheritsSupportedValues) производит инициализацию действия с помощью интерфейса пользователя. Интерфейс пользователя плагина тоже реализуется на стороне плагина и вызывается внутри метода UserInitializeWith, после чего производится настройка параметров плагина, подбирается подходящий тип значения (если необходимо и он не задан явно в параметре valueType) и возвращается bool значение того, удачно ли пользователь выполнил настройки или передумал и нажал "Отмена". Параметр valueType этого метода задает тип значения плагина явно, это обычно происходит когда действие выставляется в правой части выражения и должно точно соответствовать типу данных значения слева. Параметр inheritsSupportedValues указывает на то, должны ли параметры AcceptedValues параметра valueType совпадать полностью (в некоторых случаях это не обязательно).

В некоторых ситуациях действие может реализовывать разные типы данных в зависимости от настройки (например, плагин ZWave), поэтому, для того, чтобы Lazurite знал, какие типы могут появиться после вызова метода UserInitializeWith, нужно их заранее задать в атрибуте [SuitableValueTypes] из Lazurite.ActionsDomain.Attributes.

[SuitableValueTypes(typeof(FloatValueType))] //действие будет поддерживать только числовой тип данных
[SuitableValueTypes(typeof(StateValueType), typeof(InfoValueType))] //действие будет поддерживать тип данных Статус и Информация
[SuitableValueTypes(true)] //поддерживает все типы данных

Для некоторых действий вызов метода UserInitializeWith не нужен, так как они не имеют настроек, например действие "День недели сейчас" не имеет настроек и просто возвращает значение типа "Статус". Чтобы Lazurite игнорировал UserInitializeWith необходимо выставить bool IsSupportsModification => false.

Функция string GetValue(ExecutionContext context) возвращает необходимое значение. В действии "День недели сейчас" она возвращает "Среда", "Суббота" и т.д., в действии "Температура в комнате" - температуру.

Если действие не должно возвращать никаких значений (например, действие "Ожидание"), то весь класс, наследуемый от IAction, необходимо помечать атрибутом [OnlyExecute], который находится в пространстве имен Lazurite.ActionsDomain.Attributes.

Функция void SetValue(ExecutionContext context, string value) задает значение действию. В действии "Ожидание" эта функция принимает величину времени ожидания.

Некоторые действия не принимают никаких значений, а лишь возвращают некие данные, например действие "Час сейчас" не может принимать ничего на вход. Если Вы хотите, чтобы ваше действие не принимало ничего на вход, то пометьте действие атрибутом [OnlyGetValue] из пространства имен Lazurite.ActionsDomain.Attributes.

Функция void Initialize() вызывается во время запуска Lazurite и производит настройку действия. В этот метод можно добавлять загрузку данных из файла, соединение с контроллерами и т.д. Это метод вызывается всегда и без исключения.

Свойство string Caption { get; set; } позволяет выводить строковую информацию о текущих настройках действия.

На картинке строка в действии "Пользователь в локации", отрисованная белым цветом - результат выполнения свойства string Caption { get; set; }.

Для того, чтобы задать наименование действию, необходимо добавить атрибут [HumanFriendlyName("Имя действия")], который находится в пространстве имен Lazurite.ActionsDomain.Attributes.

В некоторых случаях может быть такая ситуация, что значение некоторых действий невозможно или нецелесообразно вычислять каждый раз (например, все устройства ZWave сами периодически отсылают отчет об изменении температуры, освещения, значении датчика движения), и программе очень сложно отследить такие моменты, поэтому в интерфейсе IAction есть событие event ValueChangedEventHandler ValueChanged. Если в действии выставить свойство IsSupportsEvent => true, то Lazurite больше не будет вызывать метод string GetValue(ExecutionContext context) и полностью будет надеяться на событие изменения (даже сразу после задания значения в SetValue в действии будет необходимо выполнять событие ValueChanged!). Если IsSupportsEvent будет false, то Lazurite не будет обращать внимание на событие ValueChanged. Одно изменение свойства IsSupportsEvent сразу изменит режим работы всего действия. Событийный режим работы подходит для работы с API, которые сами уведомляют об изменении своего внутреннего состояния посредством своих внутренних событий, например, так работает OpenZWave.

Интерфейс IUsersGeolocationAccess

Дополнительно к классу, который наследуется от IAction, можно добавить реализацию интерфейса IUsersGeolocationAccess.

    public interface IUsersGeolocationAccess
    {
        void SetNeedTargets(Func<IGeolocationTarget[]> needUsers);
    }

Именно этим интерфейсом пользуется плагин UserGeolocationPlugin.

    public class GetUserLocationAction : IAction, IUsersGeolocationAccess
    {
    ...

Метод void SetNeedTargets(Func<IGeolocationTarget[]> needUsers) передает в класс объект needUsers, который содержит делегат, позволяющий получать свежие данные о геолокациях пользователя.

    public interface IGeolocationTarget
    {
        string Name { get; }
        string Id { get; }
        GeolocationInfo[] Geolocations { get; }
    }

Каждый объект IGeolocationTarget содержит имя пользователя, его ID и геоданные, которые содержат информацию о текущем устройстве, сами геокоординаты и время, в которое были получены эти данные.

    public class GeolocationInfo
    {
        public DateTime DateTime { get; set; }
        public Geolocation Geolocation { get; private set; }
        public string Device { get; set; }
    ...

Таким образом, если унаследовать свое действие от интерфейса IUsersGeolocationAccess, то у Вас будет возможность обрабатывать в плагине данные о перемещениях пользователей.

Интерфейс IMessagesSender

Интерфейс IMessagesSender позволяет отправлять сообщения пользователям из действий плагина.

    public interface IMessagesSender
    {
        void SetNeedTargets(Func<IMessageTarget[]> needTargets);
    }

Метод void SetNeedTargets(Func<IMessageTarget[]> needTargets) передает в класс объект needTargets, который содержит делегат, возвращающий объекты пользователей, позволяющие отправлять сообщения конкретному из них.

    public interface IMessageTarget
    {
        void SetMessage(string message, string title);
        Messages ExtractMessages();
        string Id { get; set; }
        string Name { get; set; }
    ...

Каждый объект IMessageTarget содержит ID пользователя, его имя и метод SetMessage, с помощью которого и производится отправка сообщения пользователю. Метод ExtractMessages, с помощью которого Lazurite извлекает новые сообщения для пользователя, нельзя вызывать из плагина.

Интерфейс IScenariosEnumerator

Позволяет запускать сценарии из плагина.

    public interface IScenariosEnumerator
    {
        void SetCasts(Func<ScenarioCast[]> needCasts);
    }

Принимает функцию, которая возвращает объекты ScenarioCast, которые содержат в себе "слепки" сценариев.

public class ScenarioCast
    {
        ...

        public string Value // Позволяет задать значение сценария
        {
            get;
            set;
        }

        public string Name { get; private set; }
        public ValueTypeBase ValueType { get; private set; } // Тип сценария и возможные значения для Value
        public string ID { get; private set; }
        public bool Enabled { get; private set; } // Текущая доступность сценария. Если false - значит сценарию нельзя задать значение или посмотреть его
        public bool CanView { get; private set; } // Показывает, может ли сценарий показывать значение
        public bool CanSet { get; private set; } // Показывает, можно ли задавать значение сценарию
    }

Этот интерфейс может быть полезен для создания плагина голосовой активации.

Дополнительные атрибуты

Атрибут [LazuriteIcon] позволяет задать иконку для действий плагина, которая будет отображаться в конструкторе и в меню выбора действия. Весь список иконок.

Атрибут [Category] позволяет задать категорию действия плагина, разделяющую все действия в меню выбора действия.

Действия, их иконки и разделение на категории в окне выбора действия.

Задание иконки и категории на примере действия из плагина Modbus:

    [HumanFriendlyName("Modbus - чтение и запись регистров")]
    [SuitableValueTypes(typeof(FloatValueType), typeof(InfoValueType))]
    [LazuriteIcon(Icon.NetworkHome)] //иконка
    [Category(Category.Control)] //категория
    public class ModbusRegistersAction : IAction, IModbusRegistersAction
    {
    ...

Элементы управления для плагинов

Программист может сам выбрать визуальные элементы управления для UI плагина, но в сборке LazuriteUI.Windows.Controls и LazuriteUI.Icons содержатся иконки и контролы, которые можно использовать для приближения визуального стиля плагина к стилю Lazurite.

Хранение дополнительных данных плагина

Несмотря на то, что Lazurite путем сериализации сам сохраняет значения всех публичных полей класса, наследуемого от IAction, иногда требуется дополнительная возможность сохранять данные. Например, это необходимо чтобы сохранять список COM портов, на которых находятся ZWave устройства компьютера. Реализовать эту возможность можно с помощью класса PluginsDataManagerBase.

Для получения экземпляра следует PluginsDataManagerBase использовать следующую конструкцию:

var dataManager = Singleton.Resolve<PluginsDataManagerBase>();

Внутри класс выглядит так:

    public abstract class PluginsDataManagerBase : IDataManager
    {
        public abstract T Get<T>(string key); // Получить объект по ключу
        public abstract void Set<T>(string key, T data); // Записать объект
        public abstract void Clear(string key); // Удалить объект по ключу
        public abstract bool Has(string key); // Узнать, имеется ли запись по ключу
    }
⚠️ **GitHub.com Fallback** ⚠️