Свет
Вариант #1
Автор: d.v.ermakov » 17 мар 2016, 20:24
Поделюсь немного опытом использования байндинга в управлении светом. В развитие уже имеющейся инструкции автора на Гитхабе (ссылка).
Во-первых, я не пользуюсь советом из инструкции переключать порт Меги (к которому подключена кнопка) в режим P&R. Для корректной работы правила по включению/выключению этого не нужно, а также это мешает нормальной работе автономного режима Меги (если сервер выключить). Поэтому обычные кнопки у меня в режиме P (и автономный режим настроен на всякий случай), только кнопки диммеров и датчики присутствия в режиме P&R.
Вот правило для обычного включения/выключения кнопкой:
/* Кнопка X */ rule "Button_X" when Item Button_X changed to ON then if (Light_X.state == OFF) { sendCommand(Light_X, ON) postUpdate(Light_X, ON) } else { sendCommand(Light_X, OFF) postUpdate(Light_X, OFF) } postUpdate(Button_X, OFF) // Возвращаем кнопку в положение выключено endВо-вторых, вот правило для управления яркостью (диммирования) одной кнопкой. Эта кнопка должна быть в режиме P&R. Действует так: короткое нажатие выключает/включает на прежний уровень яркости, длинное нажатие плавно уменьшает яркость до 1%, затем увеличивает до 100%, затем снова уменьшает, и т. д.
val int DimmerDelay = 500 // Задержка нажатия кнопки для срабатывания диммера val int DimmerSpeed = 100 // Задержка шага диммера val int DimmerStep = 3 // Величина шага диммера var Number Stored_Y = 1 // Яркость при первом включении 1, а потом запоминаем сюда яркость var int TimeON_Y = 0 // Переменная для запоминания времени включения кнопки диммера /* Диммер Y */ rule "Dimmer_Y ON" when Item Dimmer_Y changed to ON then TimeON_Y = now.getMillisOfDay var Timer timer = null timer = createTimer(now.plusMillis(DimmerDelay)) [| var Number Percent = Light_Y.state as DecimalType var Number Flag = 0 while (Dimmer_Y.state == ON) { Percent = Percent - DimmerStep + Flag if (Percent > 100) { Percent = 100 Flag = 0 } if (Percent < 1) { Percent = 1 Flag = DimmerStep + DimmerStep } sendCommand (Light_Y, Percent) postUpdate (Light_Y, Percent) Thread::sleep (DimmerSpeed) } ] end rule "Dimmer_Y OFF" when Item Dimmer_Y changed to OFF then var int TimeOFF = now.getMillisOfDay var int TimePressed = TimeOFF - TimeON_Y if (TimePressed < DimmerDelay) { if (Light_Y.state > 0) { Stored_Y = Light_Y.state as DecimalType sendCommand (Light_Y, 0) postUpdate (Light_Y, 0) } else { sendCommand (Light_Y, Stored_Y) postUpdate (Light_Y, Stored_Y) } } end
Если у вас несколько диммеров в одном файле, переменные внутри правил переименовывать в каждом не нужно, они локальные. Только Stored_Y и TimeON_Y должны быть у каждого диммера свои, и объявлены в начале файла.
Вторая серия. Использование датчиков присутствия/движения для управления светом. Это простое правило:
val int PresenceDelay = 60 /* Задержка выключения света по датчикам присутствия в секундах */ /* Свет в гардеробной */ rule "Light_Closet ON" when Item Presence_Closet changed to ON then if (Light_Closet.state == OFF) { sendCommand(Light_Closet, ON) postUpdate(Light_Closet, ON) } end rule "Light_Closet OFF" when Item Presence_Closet changed to OFF then var Timer timer = null timer = createTimer(now.plusSeconds(PresenceDelay)) [| if (Presence_Closet.state == OFF) { if (Light_Closet.state == ON) { sendCommand(Light_Closet, OFF) postUpdate(Light_Closet, OFF) } } ] endА это правило для света в санузле, если у вас есть два датчика движения, один в самом санузле, а второй в коридоре, непосредственно перед санузлом. Включается свет по любому из двух датчиков, а выключается по датчику коридора, при условии, что внутри никого нет. У меня двери в санузлы имеют большие вставки из матового стекла и я использую свет в санузле как ночное освещение коридора. Кроме того, у меня в санузлах по две группы света, маленькая ночная лампочка и основное освещение. Ну и можно изменить правило по вкусу.
val int PresenceDelay = 60 /* Задержка выключения света по датчикам присутствия в секундах */ /* Свет в санузле */ rule "Light_ WC" when Item Presence_Corridor changed to ON or Item Presence_WC changed to ON then if (Light_WC.state == OFF) { sendCommand(Light_WC, ON) postUpdate(Light_WC, ON) } end rule "Light_WC OFF" when Item Presence_Corridor changed to OFF then var Timer timer = null timer = createTimer(now.plusSeconds(PresenceDelay)) [| if (Presence_Corridor.state == OFF) { if (Presence_WC.state == OFF) { if (Light_WC.state == ON) { sendCommand(Light_WC, OFF) postUpdate(Light_WC, OFF) } } } ] endФайлов с правилами в Опенхабе может быть много, удобно делить правила на файлы по функционалу.
Вариант #2
Автор: bvasya » 06 июн 2017, 16:47
Хочу поделиться своим правилом для управления светом. Поскольку с java и Xtend я опыта раньше не имел, написано скорее всего не самым оптимальным образом. Кучу ошибок (в основном с типами) приходилось решать методом тыка, но сейчас по крайней мере редактор не ругается) Версия самая начальная, скорее всего буду переписывать, поэтому формат настроек может поменяться. У меня работает на OH2 + биндинг от OH1. На OH1 скорее всего работать не будет, там много поменялось в работе правил.
Основная концепция такая: Сделать код управления общим для всех, все различия вынести в настройки Каждый выключатель или датчик движения отвечает за один или несколько выходов (свет, вытяжки и т.п.) Если выходов несколько на одной кнопке, за основу берем первый по порядку Каждый выход настраивается отдельно и не зависит от того каким выключателем был включен В зависимости от текущего режима (у меня это время суток), выходы могут по разному реагировать на нажатие кнопок При старте режима, можно сменить состояние и т.п. В разных режимах - можно сделать разное значение диммера По длительному нажатию, выход включается или выключается без дополнительных настроек В данный момент, работает только с кнопками. Датчики движения, диммеры и смену состояния при смене режима пока не делал. Бывают небольшие глюки, но пока не отлавливал причину. Код старался по максимуму комментировать и в логи сейчас практически все изменения пишутся.
Теперь к коду: megadevice.items
//група со всеми выключателями, на которые действует правило Group:Switch:OR(ON,OFF) gSwitches "All switches" (All) //група со всеми выходами, на которые действует правило Group:Switch:OR(ON,OFF) gLights "All lights" (All) //переключатель режимов //в sitemap прописан как //Switch item=modeSelector mappings=["morning"="Morning","day"="Day","evening"="Evening","night"="Night", "guard"="Guard"] String modeSelector "M" Switch F2BedroomBedButton "Спальня, выключатель у кровати [%s]" (F2, Bedroom, gSwitches) {megadevice="sec:192.168.0.22:3"} Switch F2BedroomHallButtonL "Спальня, выключатель в корридоре левый [%s]" (F2, Bedroom, gSwitches) {megadevice="sec:192.168.0.22:5"} Switch F2BedroomHallButtonR "Спальня, выключатель в корридоре правый [%s]" (F2, Bedroom, gSwitches) {megadevice="sec:192.168.0.22:6"} Switch F2BedroomBra "Спальня, бра [%s]" (F2, Bedroom, gLights) {megadevice="sec:192.168.0.22:8"} Switch F2BedroomBathMirror "Спальня санузел, зеркало [%s]" (F2, BedroomBath, gLights) {megadevice="sec:192.168.0.22:9"} Switch F2BedroomBathLight "Спальня санузел, свет [%s]" (F2, BedroomBath, gLights) {megadevice="sec:192.168.0.22:10"} Switch F2BedroomBathAir "Спальня санузел, вытяжка [%s]" (F2, BedroomBath, gLights) {megadevice="sec:192.168.0.31:22"} Switch F2BedroomLight1 "Спальня, свет 1 [%s]" (F2, Bedroom, gLights) {megadevice="sec:192.168.0.22:12"} Switch F2BedroomLight2 "Спальня, свет 2 [%s]" (F2, Bedroom, gLights) {megadevice="sec:192.168.0.22:13"}
Ниже в коде пример настройки для спальни с санузлом: бра обычно включается на 3 минуты, вечером и ночью без таймера. Основной свет в комнате вечером и ночью не включается (только по длительному нажатию). В санузле вчером и ночью вытяжка включается сразу и работает после выключения 10 минут, в остальное время вытяжка включается только после выключения света на 5 минут
megadevice.rules
import java.util.HashMap import java.util.LinkedHashMap import java.util.ArrayList import java.util.Map import java.util.concurrent.locks.Lock import java.util.concurrent.locks.ReentrantLock import java.io.Serializable /* Настройки ламп по умолчанию * * Для таймеров: * Double.NEGATIVE_INFINITY - не включаем * Double.POSITIVE_INFINITY - включаем без таймера * null - ? * * Для старта: * TODO: реализовать * Double.NEGATIVE_INFINITY - выключаем * Double.POSITIVE_INFINITY - включаем без таймера * null - ничего не делаем * */ val Double NEVER = Double.NEGATIVE_INFINITY val Double FOREVER = Double.POSITIVE_INFINITY var _default = newHashMap( "buttonTimeout" -> Double.POSITIVE_INFINITY, // таймаут при включении с кнопки "sensorTimeout" -> 10, // таймаут при включении с сенсора "started" -> null, // значение при старте режима "offTimeout" -> 0, // таймаут на выключение после выключения с кнопки "dimmer" -> 100 //значение диммера ) var HashMap<String, HashMap<String, ?>> _lights = newHashMap( "F2BedroomBathMirror" -> (newHashMap( ), "F2BedroomBathLight" -> (newHashMap( ), "F2BedroomBathAir" -> (newHashMap( "buttonTimeout" -> Double.NEGATIVE_INFINITY, "buttonTimeout_evening" -> Double.POSITIVE_INFINITY, "buttonTimeout_night" -> Double.POSITIVE_INFINITY, "offTimeout" -> 300, "offTimeout_evening" -> 600, "offTimeout_night" -> 600 ), "F2BedroomBra" -> (newHashMap( "buttonTimeout" -> 180, "buttonTimeout_evening" -> Double.POSITIVE_INFINITY, "buttonTimeout_night" -> Double.POSITIVE_INFINITY ), "F2BedroomLight1" -> (newHashMap( "buttonTimeout_evening" -> Double.NEGATIVE_INFINITY, "buttonTimeout_night" -> Double.NEGATIVE_INFINITY ), "F2BedroomLight2" -> (newHashMap( "buttonTimeout_evening" -> Double.NEGATIVE_INFINITY, "buttonTimeout_night" -> Double.NEGATIVE_INFINITY ) ) //кнопки var HashMap<String, ArrayList<String>> _switches = newLinkedHashMap( "F2BedroomBedButton" -> newArrayList("F2BedroomBra", "F2BedroomLight1", "F2BedroomLight2"), "F2BedroomHallButtonL" -> newArrayList("F2BedroomBra", "F2BedroomLight1", "F2BedroomLight2"), "F2BedroomHallButtonR" -> newArrayList("F2BedroomBathLight", "F2BedroomBathMirror", "F2BedroomBathAir") ) //датчики //TODO: реализовать var HashMap<String, ArrayList<String>> _sensors = newLinkedHashMap( "sensor1" -> newArrayList("light1"), "sensor2" -> newArrayList("light2") ) // Таймеры на отключение по timeout var HashMap<String, Timer> _timeoutTimers = newHashMap // Триггеры на сработавший механизм, чтобы датчики движения не переназначали действия кнопок var HashMap<String, String> _lastTriggers = newHashMap // время нажатия now.getMillisOfDay var HashMap<String, Number> _switches_time = newHashMap //лямбда для получения настроек в зависимости от режима //приоритет настроек: mode настройка для лампы -> общая для лампы -> общая для всех //mode берем из modeSelector val Functions$Function3 getSettingsByCurrentMode = [ String item, HashMap<String, HashMap<String, Number>> gSettings, HashMap<String, Number> def | var mode = modeSelector.state.toString var HashMap<String, Number> result = newHashMap if (gSettings.containsKey(item)) { var settings = gSettings.get(item) for (def_entry : def.entrySet()) { var v = def_entry.getKey() if (settings.containsKey(v+"_"+mode)) { result.put(v, settings.get(v+"_"+mode)) } else { if(settings.containsKey(v)) { result.put(v, settings.get(v)) } else { result.put(v, def_entry.getValue()) } } } } else { result=def } result ] rule "Set lights timers on system start" when System started then //создаем таймеры для лампочек //TODO: добавлять только для тех, где есть timeout в настройках for (t : _lights.entrySet()) { _timeoutTimers.put(t.getKey(), null) _lastTriggers.put(t.getKey(), "") } end rule "switch _lights by settings" when Item gSwitches received update then (gSwitches).members.filter(x|x.state == ON).forEach [ sw | //Сохраняем время нажатия кнопки if (!_switches_time.containsKey(sw.name)) { _switches_time.put(sw.name, now.getMillisOfDay) } //Лампы на данном выключателе val ArrayList<String> swLights = _switches.get(sw.name) //Проверяем есть ли настройки для выключателя if (swLights != null && swLights.size>=1) { //первая лампа в группе val firstLight = swLights.get(0) //State первой лампы в группе val firstLightState = gLights.members.findFirst[name.equals(firstLight)].state //настройки первой лампы //val firstLightSettings = getSettingsByCurrentMode.apply(firstLight, _lights, _default) as HashMap<String, Number> /* * Обработка длинного нажатия */ // Таймер на 2 секунды var Timer longTimer = createTimer(now.plusSeconds(2), [ | //если при срабатывании таймера кнопка по прежнему нажата if (sw.state == ON) { swLights.forEach [ swl | //если было выключено - включаем и обнуляем таймеры if (firstLightState==OFF) { sendCommand(swl, "ON") postUpdate(swl, "ON") var Timer offTimer = _timeoutTimers.get(swl) if (offTimer != null) { offTimer.cancel _timeoutTimers.put(swl, null) logInfo("SLbyS", swl+": timeoutTimer : clear") } logInfo("SLbyS", swl+": long_press : ON") } //если было включено - выключаем и обнуляем таймеры else { sendCommand(swl, "OFF") postUpdate(swl, "OFF") var Timer offTimer = _timeoutTimers.get(swl) if (offTimer != null) { offTimer.cancel _timeoutTimers.put(swl, null) logInfo("SLbyS", swl+": timeoutTimer : clear") } logInfo("SLbyS", swl+": long_press : OFF") } ] } ]) /* * Обработка короткого нажатия */ swLights.forEach [ swl | //настройки лампы var lightSettings = getSettingsByCurrentMode.apply(swl, _lights, _default) as HashMap<String, Number> //Item лампы var lightItem = gLights.members.findFirst[name.equals(swl)] /* * переключаем всю группу в одно состояние обратное от первого элемента * TODO проверить на отключение таймеров */ // если лампа включена - выключаем if (firstLightState==ON) { //TODO что делать если у первой лампы в группе стоит NEVER? // если есть offTimeout, выключаем через некоторое время if (lightSettings.get("offTimeout").intValue()>0) { val timerItem=lightItem //включаем, на случай, если было выключено sendCommand(timerItem, "ON") postUpdate(timerItem, "ON") logInfo("SLbyS", swl+": offTimer : ON -> "+lightSettings.get("offTimeout").intValue()+" -> OFF") var Timer offTimer = createTimer(now.plusSeconds(lightSettings.get("offTimeout").intValue()), [ | sendCommand(timerItem, "OFF") postUpdate(timerItem, "OFF") logInfo("SLbyS", swl+": offTimer : OFF") ]) _timeoutTimers.put(swl, offTimer) } // просто выключаем else { sendCommand(lightItem, "OFF") postUpdate(lightItem, "OFF") var Timer offTimer = _timeoutTimers.get(swl) if (offTimer != null) { offTimer.cancel _timeoutTimers.put(swl, null) logInfo("SLbyS", swl+": timeoutTimer : clear") } logInfo("SLbyS", swl+": button : OFF") } } // если лампа выключена - включаем else { if (lightSettings.get("buttonTimeout").doubleValue() == Double.NEGATIVE_INFINITY) { //если NEGATIVE_INFINITY не включаем logInfo("SLbyS", swl+": button : skip") } else if (lightSettings.get("buttonTimeout").doubleValue() == Double.POSITIVE_INFINITY) { //POSITIVE_INFINITY просто включаем sendCommand(lightItem, "ON") postUpdate(lightItem, "ON") logInfo("SLbyS", swl+": button : ON") } else if (lightSettings.get("buttonTimeout").intValue()>0) { //включаем с таймером sendCommand(lightItem, "ON") postUpdate(lightItem, "ON") val timerItem=lightItem var Timer offTimer = createTimer(now.plusSeconds(lightSettings.get("buttonTimeout").intValue()), [ | sendCommand(timerItem, "OFF") postUpdate(timerItem, "OFF") logInfo("SLbyS", swl+": startTimer : OFF") ]) _timeoutTimers.put(swl, offTimer) logInfo("SLbyS", swl+": startTimer : ON -> "+lightSettings.get("buttonTimeout").intValue()+" -> OFF") } } ] } ] //обнуляем время нажатия кнопки после отпускания for (sw : _switches_time.entrySet()) { if (gSwitches.members.findFirst[name.equals(sw.getKey())].state==OFF){ _switches_time.remove(sw.getKey()) } } end