]> git.basschouten.com Git - openhab-addons.git/blob
9ce84e92dfe93529dbb18873bc7f5fa2072d16cc
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.loxone.internal.controls;
14
15 import static org.openhab.binding.loxone.internal.LxBindingConstants.*;
16
17 import java.io.IOException;
18 import java.time.LocalDateTime;
19 import java.time.ZoneId;
20 import java.time.ZonedDateTime;
21 import java.time.format.DateTimeFormatter;
22 import java.time.format.DateTimeParseException;
23
24 import org.openhab.binding.loxone.internal.types.LxState;
25 import org.openhab.binding.loxone.internal.types.LxUuid;
26 import org.openhab.core.library.types.DateTimeType;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.type.ChannelTypeUID;
30 import org.openhab.core.types.Command;
31 import org.openhab.core.types.State;
32 import org.openhab.core.types.StateDescriptionFragmentBuilder;
33 import org.openhab.core.types.UnDefType;
34
35 /**
36  * Loxone Control that controls the Burglar Alarm
37  *
38  * @author Michael Mattan - Initial contribution
39  *
40  */
41 class LxControlAlarm extends LxControl {
42
43     static class Factory extends LxControlInstance {
44         @Override
45         LxControl create(LxUuid uuid) {
46             return new LxControlAlarm(uuid);
47         }
48
49         @Override
50         String getType() {
51             return "alarm";
52         }
53     }
54
55     private static final String CMD_ON = "on";
56     private static final String CMD_ON_WITH_MOVEMENT = "on/1";
57     private static final String CMD_ON_WITHOUT_MOVEMENT = "on/0";
58     private static final String CMD_DELAYED_ON = "delayedon";
59     private static final String CMD_DELAYED_ON_WITH_MOVEMENT = "delayedon/1";
60     private static final String CMD_DELAYED_ON_WITHOUT_MOVEMENT = "delayedon/0";
61     private static final String CMD_OFF = "off";
62     private static final String CMD_QUIT = "quit";
63     private static final String CMD_DISABLE_MOVEMENT = "dismv/1";
64     private static final String CMD_ENABLE_MOVEMENT = "dismv/0";
65
66     /**
67      * If the alarm control is armed
68      */
69     private static final String STATE_ARMED = "armed";
70
71     /**
72      * The id of the next alarm level
73      */
74     private static final String STATE_NEXT_LEVEL = "nextlevel";
75
76     /**
77      * The delay of the next level in seconds
78      */
79     private static final String STATE_NEXT_LEVEL_DELAY = "nextleveldelay";
80
81     /**
82      * The total delay of the next level in seconds
83      */
84     private static final String STATE_NEXT_LEVEL_DELAY_TOTAL = "nextleveldelaytotal";
85
86     /**
87      * The id of the current alarm level
88      */
89     private static final String STATE_LEVEL = "level";
90
91     /**
92      * Timestamp when alarm started
93      */
94     private static final String STATE_START_TIME = "starttime";
95
96     /**
97      * The delay of the alarm control being armed
98      */
99     private static final String STATE_ARMED_DELAY = "armeddelay";
100
101     /**
102      * The total delay of the alarm control being armed
103      */
104     private static final String STATE_ARMED_DELAY_TOTAL = "armeddelaytotal";
105
106     /**
107      * A string of sensors separated by a pipe
108      */
109     private static final String STATE_SENSOR = "sensors";
110
111     /**
112      * If the movement is disabled or not
113      */
114     private static final String STATE_DISABLED_MOVE = "disabledmove";
115
116     private ChannelUID startTimeId;
117     private ChannelUID ackChannelId;
118     private State startTime = UnDefType.UNDEF;
119     private boolean presenceConnected = false;
120     private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
121
122     private LxControlAlarm(LxUuid uuid) {
123         super(uuid);
124     }
125
126     @Override
127     public void initialize(LxControlConfig config) {
128         super.initialize(config);
129         if (details.presenceConnected != null) {
130             presenceConnected = details.presenceConnected;
131         }
132
133         addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH), defaultChannelLabel,
134                 "Alarm armed", tags, this::handleArmAlarm, () -> getStateOnOffValue(STATE_ARMED));
135
136         addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
137                 defaultChannelLabel + " / Arm Delayed", "Arm with delay", tags, this::handleArmDelayedAlarm,
138                 () -> OnOffType.OFF);
139
140         addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
141                 defaultChannelLabel + " / Next Level", "ID of the next alarm level", tags, null,
142                 () -> getStateDecimalValue(STATE_NEXT_LEVEL));
143
144         addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
145                 defaultChannelLabel + " / Next Level Delay", "Delay of the next level", tags, null,
146                 () -> getStateDecimalValue(STATE_NEXT_LEVEL_DELAY));
147
148         addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
149                 defaultChannelLabel + " / Next Level Delay Total", "Total delay of the next level", tags, null,
150                 () -> getStateDecimalValue(STATE_NEXT_LEVEL_DELAY_TOTAL));
151
152         addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
153                 defaultChannelLabel + " / Level", "Current alarm level", tags, null,
154                 () -> getStateDecimalValue(STATE_LEVEL));
155
156         startTimeId = addChannel("DateTime", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_DATETIME),
157                 defaultChannelLabel + " / Start Time", "Time when alarm started", tags, null, () -> startTime);
158
159         addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
160                 defaultChannelLabel + " / Armed Delay", "Delay of the alarm being armed", tags, null,
161                 () -> getStateDecimalValue(STATE_ARMED_DELAY));
162
163         addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
164                 defaultChannelLabel + " / Armed Total Delay", "Total delay of the alarm being armed", tags, null,
165                 () -> getStateDecimalValue(STATE_ARMED_DELAY_TOTAL));
166
167         addChannel("String", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_TEXT),
168                 defaultChannelLabel + " / Sensors", "Alarm sensors", tags, null,
169                 () -> getStateStringValue(STATE_SENSOR));
170
171         ackChannelId = addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
172                 defaultChannelLabel + " / Acknowledge", "Acknowledge alarm", tags, this::handleQuitAlarm,
173                 () -> OnOffType.OFF);
174         addChannelStateDescriptionFragment(ackChannelId,
175                 StateDescriptionFragmentBuilder.create().withReadOnly(true).build());
176
177         if (presenceConnected) {
178             // this channel has reversed logic - we show state of enabled option, but receive state updates if disabled
179             addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
180                     defaultChannelLabel + " / Motion Sensors", "Motion sensors enabled", tags,
181                     this::handleMotionSensors, this::getStateMotionSensors);
182         }
183     }
184
185     @Override
186     public void onStateChange(LxState state) {
187         String stateName = state.getName();
188         if (STATE_START_TIME.equals(stateName)) {
189             startTime = UnDefType.UNDEF;
190             Object obj = state.getStateValue();
191             if (obj instanceof String str && !str.isEmpty()) {
192                 try {
193                     LocalDateTime ldt = LocalDateTime.parse(str, dateTimeFormatter);
194                     ZonedDateTime dt = ldt.atZone(ZoneId.systemDefault());
195                     startTime = new DateTimeType(dt);
196                 } catch (DateTimeParseException e) {
197                     startTime = null;
198                 }
199             }
200             setChannelState(startTimeId, startTime);
201         } else if (STATE_LEVEL.equals(stateName)) {
202             Object obj = state.getStateValue();
203             addChannelStateDescriptionFragment(ackChannelId, StateDescriptionFragmentBuilder.create()
204                     .withReadOnly(obj instanceof Double && ((Double) obj) == 0.0).build());
205             super.onStateChange(state);
206         } else {
207             super.onStateChange(state);
208         }
209     }
210
211     private void handleArming(Command command, String onAction, String onWithMovementAction,
212             String onWithoutMovementAction) throws IOException {
213         if (command instanceof OnOffType) {
214             if (command == OnOffType.ON) {
215                 if (presenceConnected) {
216                     Double value = getStateDoubleValue(STATE_DISABLED_MOVE);
217                     if (value == null || value == 1.0) {
218                         sendAction(onWithoutMovementAction);
219                     } else {
220                         sendAction(onWithMovementAction);
221                     }
222                 } else {
223                     sendAction(onAction);
224                 }
225             } else {
226                 sendAction(CMD_OFF);
227             }
228         }
229     }
230
231     private void handleArmAlarm(Command command) throws IOException {
232         handleArming(command, CMD_ON, CMD_ON_WITH_MOVEMENT, CMD_ON_WITHOUT_MOVEMENT);
233     }
234
235     private void handleArmDelayedAlarm(Command command) throws IOException {
236         handleArming(command, CMD_DELAYED_ON, CMD_DELAYED_ON_WITH_MOVEMENT, CMD_DELAYED_ON_WITHOUT_MOVEMENT);
237     }
238
239     private void handleQuitAlarm(Command command) throws IOException {
240         if (command instanceof OnOffType && command == OnOffType.ON) {
241             sendAction(CMD_QUIT);
242         }
243     }
244
245     private void handleMotionSensors(Command command) throws IOException {
246         if (command instanceof OnOffType) {
247             if (command == OnOffType.ON) {
248                 sendAction(CMD_ENABLE_MOVEMENT);
249             } else {
250                 sendAction(CMD_DISABLE_MOVEMENT);
251             }
252         }
253     }
254
255     private State getStateMotionSensors() {
256         Double value = getStateDoubleValue(STATE_DISABLED_MOVE);
257         if (value != null) {
258             if (value == 1.0) {
259                 return OnOffType.OFF;
260             }
261             return OnOffType.ON;
262         }
263         return UnDefType.UNDEF;
264     }
265 }