sw:openhab:examples:light

Свет

Автор: d.v.ermakov » 17 мар 2016, 20:24

Поделюсь немного опытом использования байндинга в управлении светом. В развитие уже имеющейся инструкции автора на Гитхабе (ссылка).

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

Вот правило для обычного включения/выключения кнопкой:

1
2
3
4
5
6
7
8
9
10
11
12
/* Кнопка 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%, затем снова уменьшает, и т. д.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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 должны быть у каждого диммера свои, и объявлены в начале файла.

Вторая серия. Использование датчиков присутствия/движения для управления светом. Это простое правило:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
А это правило для света в санузле, если у вас есть два датчика движения, один в самом санузле, а второй в коридоре, непосредственно перед санузлом. Включается свет по любому из двух датчиков, а выключается по датчику коридора, при условии, что внутри никого нет. У меня двери в санузлы имеют большие вставки из матового стекла и я использую свет в санузле как ночное освещение коридора. Кроме того, у меня в санузлах по две группы света, маленькая ночная лампочка и основное освещение. Ну и можно изменить правило по вкусу.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
Файлов с правилами в Опенхабе может быть много, удобно делить правила на файлы по функционалу.

Автор: bvasya » 06 июн 2017, 16:47

Хочу поделиться своим правилом для управления светом. Поскольку с java и Xtend я опыта раньше не имел, написано скорее всего не самым оптимальным образом. Кучу ошибок (в основном с типами) приходилось решать методом тыка, но сейчас по крайней мере редактор не ругается) Версия самая начальная, скорее всего буду переписывать, поэтому формат настроек может поменяться. У меня работает на OH2 + биндинг от OH1. На OH1 скорее всего работать не будет, там много поменялось в работе правил.

Основная концепция такая: Сделать код управления общим для всех, все различия вынести в настройки Каждый выключатель или датчик движения отвечает за один или несколько выходов (свет, вытяжки и т.п.) Если выходов несколько на одной кнопке, за основу берем первый по порядку Каждый выход настраивается отдельно и не зависит от того каким выключателем был включен В зависимости от текущего режима (у меня это время суток), выходы могут по разному реагировать на нажатие кнопок При старте режима, можно сменить состояние и т.п. В разных режимах - можно сделать разное значение диммера По длительному нажатию, выход включается или выключается без дополнительных настроек В данный момент, работает только с кнопками. Датчики движения, диммеры и смену состояния при смене режима пока не делал. Бывают небольшие глюки, но пока не отлавливал причину. Код старался по максимуму комментировать и в логи сейчас практически все изменения пишутся.

Теперь к коду: megadevice.items

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//група со всеми выключателями, на которые действует правило
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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

  • sw/openhab/examples/light.txt
  • Последнее изменение: 2023/12/24 20:46
  • lazygatto