гаджеты и прочие приборы,  Документация,  дом,  Переводы,  софт

Описание правил openHAB 3

Этот перевод я сделал для того что бы самому детально разобраться в том как работают правила в openHAB и он мне помог понять многие свои ошибки. То что казалось запутанным стало теперь простым и понятным. Сама статья весьма длинная и скучная, но без неё сложно использовать функционал openHAB без «неприятных» сюрпризов. Важно отличать примитивные типы от объектов и учитывать, что по умолчанию в openHAB все значения рассматриваются как текст. Из-за этого при построении логических конструкций часто нужно использовать объекты и методы работы с ними, что по-началу может показаться сложным, особенно новичкам не имеющим опыта программирования.

Так как перевод выполнен не дословно, то в подзаголовках тэг # ведёт на оригинал статьи. Материалы по ссылкам тоже оригинальные. Но мне кажется, что внимательного прочтения этой статьи должно хватить для написания большинства сценариев умного дома.

Ещё одно важное замечание для тех кто не пишет на Java каждый день: если названием типа начинается с заглавной буквы, то это всегда объект! Это знание намного упрощает понимание кода.

«Правила» используются для автоматизации процессов. Каждое правило запускается по какому-либо событию и выполняет сценарий исполняющий задачи разного рода, такие как управление освещением, изменение значений элементов, математические вычисления или запускает таймер.

openHAB имеет высокоуровневый интегрированный, лёгкий, но мощный движок выполнения правил. Далее Вы узнаете как использовать его функциональность для реальной автоматизации дома.

#Определение правил

#Расположение файлов

Правила расположены в папке $OPENHAB_CONF/rules. Демонстрационный комплект включает в себя файл demo.rules, в котором есть пара примеров и он может стать хорошей отправной точкой

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

#Определение на основе пользовательского интерфейса

Правила могут создаваться и редактироваться через пользовательский интерфейс UI. Вы можете найти редактор просматривая раздел Settings -> Rules. Нажмите на иконку «+» что бы добавить правило, описать его название и на что оно должно срабатывать.

В нашем примере правило запускается при запуске openHAB для определения окружения. Если добавить второй триггер в раздел When, то будут учитываться срабатывания обоих триггеров (логическое ИЛИ). Если добавить дополнительное действие в Then, то оно бу выполняться последовательно. Если же добавить дополнительное условие в раздел «But only if», то оно будет работать совместно с остальными (логическое И).

Если нажать Show All, то вариантов станет заметно больше. За кнопкой. Show All зачастую скрываются очень полезные вещи. Например, при выборе триггера по элементу становятся доступны все элементы, а не только входящие в семантическую модель, как в базовом варианте выбора.

Нажмите Add Action и выберите Run Script.

Выберите Rule DSL и вводите правила по образу описанных в этий статье.

#Поддержка средств разработки (IDE)

Расширение openHAB VS Code Extension предлагает поддержку построения правил. Он включает в себя проверку синтаксиса и раскраску, проверку с маркерами ошибок, подсказки в контенте (Ctrl+Space), включая шаблоны и т. д. Это делает создание правил очень легким! Посетите страницу редакторов для получения дополнительной информации и дополнительных возможностей редактора.

#Синтаксис

Синтаксис правил основан на Xbase и в результате он во многом схож с Xtend, который также построен поверх Xbase. В результате мы часто будем ссылаться на документацию Xtend для получения подробной информации.

Файл правил — это текстовый файл следующей структуры:

  • Импорт
  • Описание переменных
  • Правила

Раздел «Импорт» содержит инструкцию импорта, как и в Java. Как и в Java, они делают импортированные типы доступными без необходимости использовать для них полное имя. Для получения дополнительной информации, пожалуйста, ознакомьтесь с документацией Xtend по импорту.

Например:

import java.net.URI

Несколько модулей импортированы по умолчанию, так что классы из них не требуется импортировать отдельно

org.openhab.core.items
org.openhab.core.persistence
org.openhab.core.library.types
org.openhab.core.library.items
org.openhab.model.script.actions

Раздел «Описание переменных» можно использовать для объявления переменных, которые должны быть доступны для всех правил в этом файле. Вы можете объявить переменные с начальными значениями или без них и изменяемыми или доступными только для чтения. Для получения дополнительной информации, пожалуйста, ознакомьтесь с документацией Xtend для объявлений переменных.

Пример:

// a variable with an initial value. Note that the variable type is automatically inferred
var counter = 0

// a read-only value, again the type is automatically inferred
val msg = "Здесь был Вася"

// an uninitialized variable where we have to provide the type (as it cannot be inferred from an initial value)
var Number x

Раздел Правила содержит список правил. Каждое правило имеет следующий синтаксис:

rule "<RULE_NAME>"
when
    <TRIGGER_CONDITION> [or <TRIGGER_CONDITION2> [or ...]]
then
    <SCRIPT_BLOCK>
end
  • <RULE_NAME> — Каждое правило должно иметь уникальное имя (помещённое в кавычки). Рекомендуется использовать осмысленные названия правил.
  • <TRIGGER_CONDITION> — Инициирующее событие, при котором выполняется логика правила. Правило выполняется в ответ на одно или несколько условий триггера. Несколько условий разделяются ключевым словом or. Пожалуйста, смотрите ниже различные возможные триггеры.
  • <SCRIPT_BLOCK> — Содержит логику, которая должна быть выполнена при выполнении условия триггера, подробности о его синтаксисе см. в разделе сценария.

#Триггеры правил

Прежде чем правило начнёт выполняться его необходимо активировать.

Есть следующие категории триггеров (активаторов) правил:

  • Триггеры по элементам (Items): реагируют на события на шине событий openHAB, т.е. команды и обновления статуса для элементов;
  • Триггеры по группе: реагируют на события на шине событий openHAB относящиеся к элементам входящим в указанную группу;
  • Триггеры по времени: срабатывают в определенное время, например, в полночь, каждый час и т. д.;
  • Системные триггеры: реагируют на определенные системные состояния;
  • Триггеры по вещам (Things): реагируют на статус вещи, т.е. переходы с ONLINE на OFFLINE и наоборот.

Рассмотрим эти категории более подробно:

#Триггеры по элементам

Можно прослушивать команды для конкретного элемента, при обновлениях статуса или об изменении статуса (обновление может оставить статус без изменений). Можно реагировать только на конкретную команду/статус или любое изменение. Вот синтаксис для всех этих случаев (части в квадратных скобках являются необязательными):

Item <item> received command [<command>]
Item <item> received update [<state>]
Item <item> changed [from <state>] [to <state>]

при написании правил через пользовательский интерфейс обратите внимание что не все поля обязательно заполнять. Для того что бы реагировать не на конкретное состояние а на любое изменение. А далее значение можно сравнить с числом в логическом блоке условий «But only if» ниже в форме. Это может быть полезно для реакции на превышение параметров с датчиков заранее заданных значений, таких как освещённость, температура или влажность, например. Так же бывает полезно анализировать появление конкретного состояния элемента игнорируя его предыдущее состояние (для этого можно использовать конструкцию changed to или просто не указывать лишнее состояние.

Упрощенное объяснение различий между command и update можно найти в статье об основных действиях openHAB. Суть: command — управление исполнительными устройствами (освещением, воротами, жалюзи и т.п.), update — изменение состояния свойственное датчикам.

При использовании срабатывания по received command правило может начать исполняться до дого как элмент поменяет своё состояние. Поэтому, если правилу важно знать какая конкретно команда была отправлена следует использовать неявную переменную receivedCommand вместо <ItemName>.state.

#Триггеры по группе

Как и в случае с триггерами на основе событий, описанными выше, вы можете прослушивать команды, обновления статуса или изменения статуса для членов данной группы. Вы также можете решить, хотите ли вы реагировать только определенную команду / статус или любое изменение. Все неявные переменные заполняются с помощью элемента, вызвавшего событие. Неявная переменная triggeringItem заполняется элементом, который вызвал срабатывание правила.

Member of <group> received command [<command>]
Member of <group> received update [<state>]
Member of <group> changed [from <state>] [to <state>]

Триггер по группе работает только с элементами, являющимися прямыми участниками групы. Он не реагирует на элементы вложенных груп. Так же как в случае с триггерами по событию при использовании received command правило может начать выполняться до того как поменяется состояние. Если правилу необходимо знать какая была отправлена команда то нужно использовать неявную переменную receivedCommand вместо <ItemName>.state.

#Триггеры по времени

Вы можете использовать некоторые предопределенные выражения для таймеров или использовать выражение cron вместо:

Time is midnight
Time is noon
Time cron "<выражение cron>"

Выражение cron имеет форму шести или семи полей:

  1. Секунды
  2. Минуты
  3. Часы
  4. День месяца
  5. Месяц
  6. День недели
  7. Год (необязательное поле)

Можно использовать FreeFormatter.com для создания выражений cron.

#Системные триггеры

Существует один системный триггер описанный ниже:

ТриггерОписание
System startedSystem started запускается при запуске openHAB. 
В openHAB версии 2 System started также срабатывает после изменения файла правил, содержащего триггер System started, или после изменения элементов в файле .items.

Вы можете использовать системный триггер ‘System started’ для устнаовки начальных значений если они ещё не установлены.

Пример:

rule "Инициализация Speedtest"
when
    System started
then
    createTimer(now.plusSeconds(30), [|
        if (Speedtest_Summary.state == NULL || Speedtest_Summary.state == "") Speedtest_Summary.postUpdate("unknown")
    ])
end

#Триггеры по вещам

Ваши правила могут предпринимать действия, основанные на обновлениях статуса или изменениях статуса описанных вещей (Things). Вы можете решить, хотите ли вы отслеживать только определенный статус или любой статус. Вот синтаксис для всех этих случаев (части в квадратных скобках необязательны):

Thing <thingUID> received update [<status>]
Thing <thingUID> changed [from <status>] [to <status>]

Статус, используемый в триггере и сценарии, представляет собой строку (без кавычек). Вы можете найти все возможные значения статуса в Thing Status. Чтобы узнать, как получить статус объекта в сценарии, обратитесь к разделу «Действие по статусу объекта».

Идентификатор thingUID, присвоенный Вещи вручную в вашей конфигурации или автоматически во время автоматического обнаружения. Вы можете найти его в пользовательском интерфейсе или с удаленной консоли Karaf. Например, одно устройство z-wave может быть «zwave: device: c5155aa4: node14».

Необходимо помещать thingUID в кавычки если оно содержит специальные символы, такие как :.

#Триггеры по каналам

Некоторые надстройки предоставляют триггерные каналы. По сравнению с другими типами каналов, канал запуска предоставляет информацию о дискретных событиях, но не предоставляет непрерывную информацию о состоянии.

Ваши правила могут предпринимать действия на основе триггерных событий, генерируемых этими каналами. Вы можете решить, хотите ли вы поймать только определенный или любой триггер, который предоставляет канал. Вот синтаксис для этих случаев (части в квадратных скобках необязательны):

Необходимо помещать triggerChannel в кавычки если оно содержит специальные символы, такие как :.

Channel "<triggerChannel>" triggered [<triggerEvent>]

triggerChannel — это идентификатор конкретного канала.

Информацию о создаваемых каналах можно найти в документации надстройки (Add-on). Не существует списка возможных значений triggerEvent, так как это зависит от конкретной реализации надстройки. Если необходимо знать какое именно событие произошло необходимо использовать неявную переменную receivedEvent.

Пример:

rule "Начинаем пробуждение на рассвете"
when
    Channel "astro:sun:home:rise#event" triggered START
then
    ...
end

#Сценарии

Язык выражений, используемый в сценариях, тот же, что используется в языке Xtend — см. Документацию по выражениям на домашней странице Xtend.

Синтаксис очень похож на Java, но имеет много хороших функций, позволяющих писать лаконичный код. Это особенно эффективно при работе с коллекциями. Что делает его подходящим для openHAB с технической точки зрения, так это то, что нет необходимости компилировать сценарии, поскольку они могут быть интерпретированы во время выполнения.

Чтобы иметь возможность делать что-то полезное со сценариями, openHAB предоставляет доступ:

  • ко всем определенным элементам, чтобы вы могли легко получить к ним доступ по их имени
  • ко всем перечисленным состояниям / командам, например, ON, OFF, DOWN, INCREASE и т. д.
  • ко всем стандартныем действиям

Комбинируя эти функции, вы можете легко написать такой код, как:

if (Temperature.state < 20) {
    Heating.sendCommand(ON)
}

#Управление состояниями элементов

Правила часто используются для управления состоянием объекта, например, для включения и выключения света при определенных условиях. Две команды могут изменить значение или состояние элемента в рамках правил:

  • MyItem.postUpdate(<new_state>) — Изменить статус элемента, не вызывая каких-либо неявных действий. Может использоваться для отражения изменений, которые могут быть вызваны другими способами.
  • MyItem.sendCommand(<new_state>) — Изменение статуса элемента и инициирующее возможные дальнейшие действия, например, отправку команды на связанное устройство / привязку.

По отношению к событийному правилу управление в сценариях запускает команды

Command \ Rule Triggerreceived updatereceived commandИзменения
postUpdate⚡ срабатывает(в зависимости)
sendCommand(❌) см. ниже⚡ срабатывает(в зависимости)
Изменения через надстройку⚡ срабатывает⚡ срабатывает(в зависимости)

Осторожно: в большинстве случаев правило с триггером received update срабатывает впоследствии отправки команды sendCommand в следующих ситуациях:

  • openHAB автоматически обновляет статус элементов, для которых определение элемента не содержит autoupdate=»false»;
  • Вещь отправляет элементу обновление статуса.

Помимо конкретных команд управления методы MyItem.sendCommand(<new_state>) и MyItem.postUpdate(<new_state>) так же доступны и основные команды управления sendCommand(MyItem, <new_state>) и postUpdate(MyItem, <new_state>). Обычно рекомендуется использовать конкретные команды.

#Сравнение MyItem.sendCommand(«new state») и sendCommand(MyItem, «new state»)

Использование методов MyItem.sendCommand(<new_state>) и MyItem.postUpdate(<new_state>) обычно предпочтительно. В этих методах объекты могут быть разных типов.

А методы sendCommand(MyItem, "<new_state>") и postUpdate(MyItem, "<new_state>") напротив могут иметь в качестве аргумента только строку.

Причины этого кроются в Java — языке объектно-ориентированного программирования на котором написан openHAB. Java и Rules DSL имеют два базовых типа: Примитивы и Объекты. Начинающиеся со строчной буквы названия типов после val или var, например var int, указывает на примитивный тип. Начинающийся с заглавной буквы тип данных, например, var Number, подразумевает Объект. Объекты являются более сложными сущностями по сравнению с примитивами.

У объектов есть специальные методы, которые могут автоматически выполнять многие необходимые преобразования типов. При использовании Myitem.sendCommand(new_state) или Myitem.postUpdate(new_state) в большинстве случаев new_state преобразуется в тип объекта myItem.

Директива sendCommand(MyItem, new_state) не обладает такой гибкостью. Например, если new_state имеет примитивный тип (например, var int new_state = 3) а myItem является объектом типа диммер:

  • следующая команда недопустима: sendCommand(MyItem, new_state);
  • при этом команда MyItem.sendCommand(new_state) будет работать.

Using MyItem.postUpdate(new_state) or MyItem.sendCommand(new_state) will create the most stable code. It provides by far the best option for avoiding most problems. This syntax ensures that any conversion (typing) of the new_state is done in a way that is most suitable for myItem.

Использование MyItem.postUpdate(new_state) или  MyItem.sendCommand(new_state) создают более стабильный код. Это, безусловно, лучший способ избежать большинства проблем. Этот синтаксис гарантирует, что любое преобразование (типизация) new_state выполняется наиболее подходящим для myItem.

Исключение: действия полезны, когда имя элемента доступно только в виде строки. Например, если имя элемента для получения обновления или команды было вычислено в правиле путем построения строки:

val index = 5
sendCommand("My_Lamp_" + index, ON)

#Использование состояний элементов в правилах

Часто требуется вычислить другие значения из состояний элемента или сравнить состояния элемента с другими значениями.

В openHAB каждый элемент имеет состояние. Состояние элемента — это сам объект, к которому можно получить доступ с помощью MyItem.state. Полный и актуальный список типов элементов в настоящее время разрешен в OpenHAB, а типы команд, которые может принимать каждый элемент, приведены в документации openHab для элементов . Чтобы использовать состояние элемента в правилах, часто необходимо знать, какой тип состояния несет элемент и как преобразовать его в типы, которые можно использовать в таких операциях. И наоборот, чтобы использовать результат вычисления для изменения состояния элемента, может потребоваться его преобразование в подходящий тип.

В этом разделе проводится различие между типом команды и типом состояния. Для удобства чтения можно просто добавить «тип» в конец типа команды, получая, таким образом, тип состояния. Например, цветовой элемент может получать OnOffType, IncreaseDecreaseType, PercentType или HSBType. Следовательно, все следующие действительные команды, которые можно отправить объекту цвета:

  • MyColorItem.sendCommand(ON)
  • MyColorItem.sendCommand(INCREASE)
  • MyColorItem.sendCommand(new PercentType(50))
  • MyColorItem.sendCommand(new HSBType(new DecimalType(123), new PercentType(45), new PercentType(67)))

Альтернативный способ указать или обновить состояние элемента — использовать специально отформатированные строки. В разделе документации элемента, посвященном форматированию, подробно описаны требования к форматированию.

Несмотря на то, что многие элементы принимают команды и обновления различных типов, каждый сохраняет свое состояние внутри, используя только один тип. Color Item из приведенного выше примера будет принимать различные типы команд, но вернет только HSBType.

Группы могут быть объявлены с любым типом элемента, и внутреннее состояние группы будет соответствовать этому типу. Например, Group:Switchвернет OnOffType для своего состояния.

Каждый тип состояния предоставляет ряд удобных методов, которые значительно облегчат преобразование и вычисления. Есть два способа изучить эти методы:

  • При использовании openHAB VS Code Extension нажатие комбинации клавиш <ctrl><space> показывет список доступных методов;
  • Посмотрите JavaDocs для данного типа. Например, JavaDoc for HSBType показывает методы getRedgetBlue, и getGreen. Эти три метода могут быть вызваны в Rules-DSL без части своего имени, например, (MyColorItem.state as HSBType).red. Они получают состояние MyColorItem и приводят его к типу HSBType что бы можно было применять методы свойственные HSBType.

#Работа с состояниями элементов: преобразования

Напоминание: для получения полного и актуального списка типов элементов, которые в настоящее время разрешены в openHAB, и типов команд, которые может принимать каждый элемент, обратитесь к разделу по элементам в документации openHAB .

Ниже приведен неполный список некоторых наиболее распространенных преобразований. Заинтересованным читателям предлагается также посетить форум. (открывается в новом окне)где можно найти еще много примеров.

#Преобразование Item.state в String

Все состояния элемента можно преобразовать в строку, вызвав MyItem.state.toString.

#Цветовой элемент

Цвет сохраняется как HSBType. HSB означает Оттенок (Hue), Насыщенность (Saturation) и Яркость (Brightness). Часто желаемый цвет отображается в виде значения RGB (красный, зеленый, синий). Следующий код можно использовать для отправки значения RGB в элемент цвета.

import java.awt.Color

// Создаём элемент newColor
// где red, blue и green целые числа от 0 до 255
val newColor = new Color(red, blue, green)

// Сохраняем элемент
MyColorItem.sendCommand(new HSBType(newColor))

Когда отдельные значения цвета из HSBType извлекаются как PercentType, необходимо разделить PercentType на 100 и умножить на 255, чтобы получить стандартный 8-битный цветовой канал RGB. Соответственно, для 16- или 32-битного представления PercentType необходимо разделить на 100 и умножить на 65535 (2 ^ 16-1) или 4294967295 (2 ^ 32-1) соответственно.

// Пример преобразования для 8-битного представления
// В теле правила
val red = (MyColorItem.state as HSBType).red / 100 * 255
val green = (MyColorItem.state as HSBType).green / 100 * 255
val blue = (MyColorItem.state as HSBType).blue / 100 * 255
#Элемент Contact

Элемент типа контакт имеет тип OpenCloseType. OpenCloseType — это перечисление. Он может быть преобразован из Открыто и Закрыто в 1 и 0 следующим кодом:

val contactNum = if (MyContactItem.state == OPEN) 1 else 0
#Элемент DateTime

Элемент DateTime имеет тип DateTimeType , который внутри содержит ZonedDateTime объект Java .

// Получение epoch (Unix-эпоха - число миллисекунд от 00:00:00 UTC 1 января 1970 года) из DateTimeType
val Number epoch = (MyDateTimeItem.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli

// Получение epoch bp Java ZonedDateTime
val Number nowEpoch = now.toInstant.toEpochMilli

// Преобразование DateTimeType в Java ZonedDateTime
val javaZonedDateTime = (MyDateTimeItem.state as DateTimeType).zonedDateTime

// Преобразование Java ZonedDateTime в DateTimeType
val DateTimeType date = new DateTimeType(now)

В некоторых случаях необходимо преобразовать временную метку эпохи в удобочитаемую и / или сохранить ее в DateTimeType и DateTime Item. Вот возможность сделать это с помощью SimpleDateFormat:

import java.time.format.DateTimeFormatter

// Преобразование Unix-эпохи в понятное кожаным мешкам время
val DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
val long epoch = now.toInstant.toEpochMilli
val ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(epoch), ZoneOffset.UTC);
val String dateTimeString = zdt.format(formatter)

// Преобразование обратно в нормальное время DateTimeType
val DateTimeType dtt = DateTimeType.valueOf(dateTimeString)

// Преобразование состояние элемента типа DateTimeType в строку
val String datetime_string  = DateTime_Item.state.format("%1$td.%1$tm.%1$ty %1$tH:%1$tM"))

ZonedDateTimes предоставляет ряд полезных методов для сравнения даты и времени вместе и / или извлечения частей даты:

// Проверяем не пришелец ли из будущего закрался с типом DateTimeType
if(now.isBefore((MyDateTimeItem.state as DateTimeType).zonedDateTime)) ...

// А может быть всё уже пропало типа DateTimeType
if(now.isAfter((MyDateTimeItem.state as DateTimeType).zonedDateTime)) ...

// узнаёт который час у типа DateTimeType
val hour = (MyDateTimeItem.state as DateTimeType).zonedDateTime.hour
#Элемент Dimmer

Элемент Dimmer (регулятор) имеет тип PercentType. PercentType может быть приведен к типу java.lang.Number и обрабатываться как java.lang.Number, где Number представляет любой тип числового значения. Язык правил поддерживает выполнение математических и логических операций с числами. Объект Number поддерживает методы получения примитивных версий этого числа, если это необходимо.

// Загружаем данные из элемента в переменную 
val dimVal = MyDimmerItem.state as Number
// как целое число
val int dimAsInt = dimVal.intValue
// и как число с плавающей запятой
val float dimAsFloat = dimVal.floatValue

Если преобразование из или в шестнадцатеричные значения необходимо, могут быть полезны следующие примеры:

// преобразовать hex_code (число, выраженное в шестнадцатеричной системе) в числовой тип
val dimVal =  Integer.parseInt(hex_code, 16) as Number
// для очень больших large_hex_codes другой вариант
val dimVal = Long.valueOf(large_hex_code, 16).longValue() as Number

// и дополнительный пример преобразования integer_value в строку hex_code
var String hex = Long.toHexString(integer_value);

Дополнительные преобразования, которые могут быть полезны, перечислены ниже в разделе NumberItem.

#Элемент Location

Элемент Location имеет тип PointType. PointType содержит два или три числа DecimalType представляющих широту и долготу в градуcах и опционально высоту в метрах. Вот несколько примеров:

// Создание
val location = new PointType(new DecimalType(50.12345), new DecimalType(10.12345))
// Создание из строки. ВНИМАНИЕ: нельзя ставить пробел после запятой
val PointType home = new PointType("12.121212,123.123123")

// Загрузка из элемента
val PointType location = Device_Coordinates.state as PointType
#Числовые элементы

Числовые элементы имеют тип DecimalType или QuantityType в случае если к кроме значения сохраняется размерность. например, Number:Temperature. В правилах размерность добавляется к числу при помощи символа |, где размерность указывается в кавычках если оно содержит символы отличные от букв и цифр, например, 10|m, but 20|"km/h". Некоторые часто используемые единицы не требуют кавычек °C°FΩ°% and  (зачем эти исключения?), например можно написать 20|»°C», но 20|°C тоже прокатит. Типы DecimalType и QuantityType так же являются java.lang.Number и все преобразования описанные выше для Dimmer так же применимы к элементу Number.

Вот еще несколько часто используемых преобразований:

  • Для состояний DecimalType::
// преобразует целое число в строку содержащую шестнадцатиричный код
var String hex_code = Long.toHexString(integer_number)

// преобразует шетнадцатиричный код в объект типа Number
var myNumber = Integer.parseInt(hex_code, 16) as Number
// то же для large_hex_code
var myNumber = Long.parseLong(hex, 16) as Number

// преобразует hex_code в DecimalType
var DecimalType parsedResult = new DecimalType(Long.parseLong(hex_code, 16))
  • Для состояний QuantityType:
// описание переменной типа QuantityType
var myTemperature = 20|°C

// получить размерность элемнта в виде текста
var myUnits = myTemperature.getUnit.toString  // gives "°C"

// преобразование количественного состояния в различные единицы:
var fahrenheit = myTemperature.toUnit("°F")   // will contain quantity 68°F

// количественное значение в десятичное
var myDecimal = new DecimalType(fahrenheit.doubleValue) // myDecimal == 68
var myCentigrade = fahrenheit.toUnit("°C").toBigDecimal  // 20

// доступ к скалярным значениям как int, double, float
var myInt = fahrenheit.intValue
var mydouble = fahrenheit.doubleValue
var myfloat = fahrenheit.floatValue

// проверка не является ли состояние элемента количеством
var isQuantity = myItem.state instanceof QuantityType

// сравнение количества
// Заманчиво, но конструкция if (fahrenheit > 10) НЕ РАБОТАЕТ!!
if (fahrenheit > 10|°C) { logInfo("test", "It's warm.") }

Другие полезные преобразования можно найти в разделе Dimmer.

Одно предупреждение про DecimalType (оно так же применимо к QuantityType). Подробное описание выходит за рамки этой инструкции, но вы обязательно должны это прочесть. Или необязательно… или не должны… Но во избежание ошибок «неоднозначного вызова метода» всегда приводите состояние типа DecimalType к Number, а не наоборот.

Take care with maths around Quantity Types. While you can freely mix units in many cases, there are pitfalls.

Позаботьтесь о математике, связанной с QuantityType. Хотя во многих случаях вы можете свободно смешивать единицы но есть подводные камни типа «грабли».

// сложим количественные переменные разных размерностей
var miles = 2|mi
var metres = 10|m
var distance = miles + metres // результат 2.0062 mi
// Размерность у результата та же что у первого слагаемого

var area = metres * metres // результат 100 m²
// Новые подходящие единицы используются для результата

var fahr = 68|°F
var centi = 1|°C
var sumTemps = fahr + centi // результат 101.80 °F
// Результат может показаться неожиданным...
// Однако, температура всегда считается абсолютной, а не интервалом или приращеним.
// 1°C был преобразован в 33.8°F, а не в интервал 1.8°F

// Для этого есть математический трюк
var increment = fahr + centi - 0|°C  // результат 69.80 °F
// "выделение нуля" фиксирует смещение между разными размерностями
#Элемент Player

Элемент проигрыватель (player) позволяет управлять проигрыванием (например аудиоплеера) при помощи команд Play, Pause, Next, Previous, Rewind и Fastforward. Элемент типа проигрыватель имеет три типа с определёнными командами

Тип СостоянияКоманды
PlayPauseTypePLAY, PAUSE
RewindFastforwardTypeREWIND, FASTFORWARD
NextPreviousTypeNEXT, PREVIOUS

Эти три типа могут быть преобразованы по типу контакта из Открыто и Закрыто в 1 и 0 при помощи этого простого скопированного кода (из раздела про контакты)

// Получение состояния из элемента
val int Playing = if (MyPlayerItem.state == PLAY) 1 else 0
#Тип Point

Смотри тип Location выше

#Тип Rollershutter (жалюзи)

Смотри тип Dimmer выше. В дополнение к командам свойственным Dimmer элемент Rollershutter понимает StopMoveType с командами STOP и MOVE.

#Элемент Строка (String)

Чтобы преобразовать состояние элемента, содержащего StringType, можно вызвать метод toString.

// получения строки из эдлемента
val stateAsString = MyStringItem.state.toString

Если элемент возвращает строку, содержащую значение в виде шестнадцатеричного числа, его можно преобразовать в целое число с помощью

// получение числа из шестнадцатиричного числа
val itemvalue = new java.math.BigDecimal(Integer::parseInt(myHexValue, 16))
#Элемент Выключатель (Switch)

Элемент Switch имеет OnOffType. OnOffType — это перечисление. Можно преобразовать из ВКЛ и ВЫКЛ в 1 и 0 с помощью кода, подобного следующему:

val SwitchNum = if (MySwitchItem.state == ON) 1 else 0

#Более глубокое погружение

Взаимодействуя с состояниями Item, необходимо внимательно понимать разницу между объектами и примитивами. Как и все объектно-ориентированные компьютерные языки, Java и Rules DSL реализовали концепцию наследования. Однако наследование применяется только к объектам и не применяется к примитивам; примерами примитивов являются integerи boolean. Наследование позволяет взять существующий тип объекта, называемый классом, и добавить к нему, чтобы превратить его во что-то другое. Это «нечто иное» становится Дочерним от исходного Класса, родителем. Ребенок по-прежнему может делать все, что может делать родитель. Базовый класс верхнего уровня для всех объектов в Java и DSL правил называется просто Object.

В дополнение к другим полезным вещам в классе Objectреализован метод с именем toString. А поскольку Objectявляется родительским для всех объектов, ВСЕ классы также реализуют toStringметод. Однако примитивы не наследуются от Object. Они ни от чего не наследуются, и у них вообще нет никаких методов, включая отсутствие метода toString.

Объекты обычно оснащены гораздо большим количеством методов преобразования типов, в то время как примитивы не поддерживают преобразование типов. Это различие очень важно при попытке использовать результат вычисления и применить его к состоянию статьи. Это sendCommandуниверсальное действие, которое должно работать со всеми типами элементов. Действия поддерживают только два аргумента String, поскольку все объекты будут поддерживать преобразование toStringsendCommand (MyItem, new_state)будет автоматически использовать MyItem.toStringметод для преобразования MyItem в String. Он также попытается сделать это со вторым аргументом, если new_stateэто еще не String. Однако, если второй аргумент является примитивом, а не объектом, он не несет метода toString. Таким образом, правила DSL не могут быть преобразованы new_stateв строку. Как следствие, использованиеsendCommand(MyItem, primitive)при использовании примитива в качестве второго аргумента почти всегда не удастся.

Различный синтаксис для общего и специфического различается и приведен в таблице ниже:

Generic (Action)Specific (Method)
postUpdate(MyItem, new_state)MyItem.postUpdate(new_state)
sendCommand(MyItem, new_state)MyItem.sendCommand(new_state)

Преимущество использования объектов над примитивами проявляется в следующих преобразованиях типов, которые автоматически вызываются объектом в зависимости от контекста. Использование метода, MyItems.sendCommand()которым владеет MyItem, будет использовать sendCommandметод, который подходит для выполнения необходимых преобразований типов. Например, NumberItemкласс будет иметь sendCommand(int)sendCommand(long)sendCommand(float)sendCommand(double)sendCommand(Number)sendCommand(DecimalType), и sendCommand(String)метод. Каждый из этих отдельных методов индивидуально написан для обработки всех этих различных типов объектов. MyItem автоматически применит метод, соответствующий типу аргумента.

Неявные переменные внутри выполняемого блока

Besides the implicitly available variables for items and commands/states, rules can have additional pre-defined variables, depending on their triggers:

  • receivedCommand — implicitly available in every rule that has at least one command event trigger.
  • previousState — implicitly available in every rule that has at least one status change event trigger.
  • newState — implicitly available in every rule that has at least one status update or status change event trigger.
  • triggeringItemName — implicitly available in every rule that has at least one status update, status change or command event trigger.
  • triggeringItem — implicitly available in every rule that has a «Member of» trigger.
  • receivedEvent — implicitly available in every rule that has a channel-based trigger.

Помимо неявно доступных переменных для элементов и команд / состояний, правила могут иметь дополнительные предварительно определенные переменные в зависимости от их триггеров:

  • receivedCommand — неявно доступно в каждом правиле, имеющем хотя бы один триггер командного события;
  • previousState — неявно доступно в каждом правиле, имеющем хотя бы один триггер события изменения статуса;
  • newState — неявно доступно в каждом правиле, имеющем хотя бы один триггер события обновления или изменения статуса;
  • triggeringItemName — неявно доступно в каждом правиле, которое имеет хотя бы одно обновление статуса, изменение статуса или триггер командного события;
  • triggeringItem — неявно доступен в каждом правиле, имеющем триггер «Член в» (крутой машынный перевод!);
  • receivedEvent — неявно доступно в каждом правиле с канальным триггером.

#Раннее возвращение

Можно досрочно вернуться из правила, не выполняя остальные инструкции, подобные этому:

if (Temperature.state > 20) {
 return;
}
Heating.sendCommand(ON)

Предостережение: обратите внимание на точку с запятой после оператора return, который завершает команду без дополнительного аргумента.

#Защита конкуренции

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

import java.util.concurrent.locks.ReentrantLock

val ReentrantLock lock  = new ReentrantLock()

rule ConcurrentCode
when
    Item Dummy received update
then
    lock.lock()
    try {
        // выполнение действий
    } finally {
        lock.unlock()
    }
end

#Трансформации

Сервисы openHAB Transformation могут использоваться в правилах для преобразования / перевода / преобразования данных.

Общий синтаксис следующий:

transform("<transformation-identifier>", "<transf. expression or transf. file name>", <input-data or variable>)
  • <transformation-identifier> — Сокращенный идентификатор услуги трансформации
  • <transf. expression or transf. file name> — Специальная услуга трансформации
  • <input-data or variable>— Данные для преобразования ДОЛЖНЫ иметь тип данных String.

Примеры:

var condition = transform("MAP", "window_esp.map", "CLOSED")
var temperature = transform("JSONPATH", "$.temperature", jsonstring)
var fahrenheit = transform("JS", "convert-C-to-F.js", temperature)

Метод transform пытается преобразовать заданное значение , и если это не удастся это возвращает исходное значение неизменным. В отличие от transform метода, transformRaw метод не улавливает никакие TransformationException символы внутри себя, а пропускает их, так что вызывающий может обработать их.

transformRaw("<transformation-identifier>", "<transf. expression or transf. file name>", <input-data or variable>)

Пример:

try {
    var temperature = transformRaw("JSONPATH", "$.temperature", jsonstring)
}
catch(TransformationException e) {
    logError("Error", "Я написал какое-то кривое правило: " + e.getMessage)
}
finally {
    // всегда запускается даже если произошла ошибка! Можно использовать для очистки
}

For all available Transformation services please refer to the list of Transformation Add-ons.

#Запись журналов

Вы можете создавать сообщения журнала из ваших правил, чтобы облегчить отладку. Есть несколько методов ведения журнала, доступных из ваших правил, подписи Java:

logDebug(String loggerName, String format, Object... args)
logInfo(String loggerName, String format, Object... args)
logWarn(String loggerName, String format, Object... args)
logError(String loggerName, String format, Object... args)

В каждом случае loggerNameпараметр объединяется со строкой org.openhab.core.model.script.для создания имени регистратора log4j. Например, если ваш файл правил содержит следующее сообщение журнала:

logDebug("kitchen", "Kitchen light turned on")

для того что бы сообщения появлялись на консоли можно сделать следующие настройки журнала:

log:set DEBUG org.openhab.core.model.script.kitchen

#Примеры правил

Ниже приведены некоторые примеры общих правил:

var Number counter

// установка начальных значений счётчиков
// хотя можно это и в объявлении переменной делать
rule "Поехали"
when
    System started
then
    counter = 0
end

// Увеличиваем счётчик в полночь
rule "а счётчик-то тикает"
when
    Time cron "0 0 0 * * ?"
then
    counter = counter + 1
end

// в полдень доложить о количестве суток автономного полёта!
rule "Сообщить сколько дней стоит"
when
    Time is noon or
    Item AnnounceButton received command ON
then
    say("Всё пучком уже " + counter + " дней")
end

// меняет счётчик по команде от начальства
rule "Улучшить счётчик"
when
    Item SetCounterItem received command
then
    counter = receivedCommand as DecimalType
end

// включает свет, когда один из нескольких датчиков движения получил команду ВКЛ, выключает его, когда один из нескольких полученных команд ВЫКЛ.
rule "Прожектор в глаз"
when
    Member of MotionSensors received command
then
    if(receivedCommand == ON) Light.sendCommand(ON)
    else Light.sendCommand(OFF)
end

rule "Побудка на рассвете"
when
    Channel "astro:sun:home:rise#event" triggered
then
    switch(receivedEvent) {
        case "START": {
            Light.sendCommand(ON)
        }
    }
end

#Дальнейшие примеры

Еще много примеров можно найти в Учебниках и примерах. (открывается в новом окне)категория форума сообщества. Они предоставляются сообществом, и постоянно добавляются новые.

Другие мои переводы про openHAB и опыт обустройства умного дома

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Спасибо!

Теперь редакторы в курсе.