2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.loxone.internal.controls;
15 import static org.openhab.binding.loxone.internal.LxBindingConstants.*;
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;
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;
36 * Loxone Control that controls the Burglar Alarm
38 * @author Michael Mattan - Initial contribution
41 class LxControlAlarm extends LxControl {
43 static class Factory extends LxControlInstance {
45 LxControl create(LxUuid uuid) {
46 return new LxControlAlarm(uuid);
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";
67 * If the alarm control is armed
69 private static final String STATE_ARMED = "armed";
72 * The id of the next alarm level
74 private static final String STATE_NEXT_LEVEL = "nextlevel";
77 * The delay of the next level in seconds
79 private static final String STATE_NEXT_LEVEL_DELAY = "nextleveldelay";
82 * The total delay of the next level in seconds
84 private static final String STATE_NEXT_LEVEL_DELAY_TOTAL = "nextleveldelaytotal";
87 * The id of the current alarm level
89 private static final String STATE_LEVEL = "level";
92 * Timestamp when alarm started
94 private static final String STATE_START_TIME = "starttime";
97 * The delay of the alarm control being armed
99 private static final String STATE_ARMED_DELAY = "armeddelay";
102 * The total delay of the alarm control being armed
104 private static final String STATE_ARMED_DELAY_TOTAL = "armeddelaytotal";
107 * A string of sensors separated by a pipe
109 private static final String STATE_SENSOR = "sensors";
112 * If the movement is disabled or not
114 private static final String STATE_DISABLED_MOVE = "disabledmove";
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");
122 private LxControlAlarm(LxUuid uuid) {
127 public void initialize(LxControlConfig config) {
128 super.initialize(config);
129 if (details.presenceConnected != null) {
130 presenceConnected = details.presenceConnected;
133 addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH), defaultChannelLabel,
134 "Alarm armed", tags, this::handleArmAlarm, () -> getStateOnOffValue(STATE_ARMED));
136 addChannel("Switch", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_SWITCH),
137 defaultChannelLabel + " / Arm Delayed", "Arm with delay", tags, this::handleArmDelayedAlarm,
138 () -> OnOffType.OFF);
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));
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));
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));
152 addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_NUMBER),
153 defaultChannelLabel + " / Level", "Current alarm level", tags, null,
154 () -> getStateDecimalValue(STATE_LEVEL));
156 startTimeId = addChannel("DateTime", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_DATETIME),
157 defaultChannelLabel + " / Start Time", "Time when alarm started", tags, null, () -> startTime);
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));
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));
167 addChannel("String", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_RO_TEXT),
168 defaultChannelLabel + " / Sensors", "Alarm sensors", tags, null,
169 () -> getStateStringValue(STATE_SENSOR));
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());
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);
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()) {
193 LocalDateTime ldt = LocalDateTime.parse(str, dateTimeFormatter);
194 ZonedDateTime dt = ldt.atZone(ZoneId.systemDefault());
195 startTime = new DateTimeType(dt);
196 } catch (DateTimeParseException e) {
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);
207 super.onStateChange(state);
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);
220 sendAction(onWithMovementAction);
223 sendAction(onAction);
231 private void handleArmAlarm(Command command) throws IOException {
232 handleArming(command, CMD_ON, CMD_ON_WITH_MOVEMENT, CMD_ON_WITHOUT_MOVEMENT);
235 private void handleArmDelayedAlarm(Command command) throws IOException {
236 handleArming(command, CMD_DELAYED_ON, CMD_DELAYED_ON_WITH_MOVEMENT, CMD_DELAYED_ON_WITHOUT_MOVEMENT);
239 private void handleQuitAlarm(Command command) throws IOException {
240 if (command instanceof OnOffType && command == OnOffType.ON) {
241 sendAction(CMD_QUIT);
245 private void handleMotionSensors(Command command) throws IOException {
246 if (command instanceof OnOffType) {
247 if (command == OnOffType.ON) {
248 sendAction(CMD_ENABLE_MOVEMENT);
250 sendAction(CMD_DISABLE_MOVEMENT);
255 private State getStateMotionSensors() {
256 Double value = getStateDoubleValue(STATE_DISABLED_MOVE);
259 return OnOffType.OFF;
263 return UnDefType.UNDEF;