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.digiplex.internal.handler;
15 import static org.openhab.binding.digiplex.internal.DigiplexBindingConstants.*;
16 import static org.openhab.binding.digiplex.internal.handler.TypeUtils.openClosedFromBoolean;
18 import java.util.Optional;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.digiplex.internal.DigiplexAreaConfiguration;
25 import org.openhab.binding.digiplex.internal.DigiplexBindingConstants;
26 import org.openhab.binding.digiplex.internal.communication.AreaArmDisarmResponse;
27 import org.openhab.binding.digiplex.internal.communication.AreaArmRequest;
28 import org.openhab.binding.digiplex.internal.communication.AreaDisarmRequest;
29 import org.openhab.binding.digiplex.internal.communication.AreaQuickArmRequest;
30 import org.openhab.binding.digiplex.internal.communication.AreaStatus;
31 import org.openhab.binding.digiplex.internal.communication.AreaStatusRequest;
32 import org.openhab.binding.digiplex.internal.communication.AreaStatusResponse;
33 import org.openhab.binding.digiplex.internal.communication.ArmType;
34 import org.openhab.binding.digiplex.internal.communication.DigiplexMessageHandler;
35 import org.openhab.binding.digiplex.internal.communication.DigiplexRequest;
36 import org.openhab.binding.digiplex.internal.communication.events.AreaEvent;
37 import org.openhab.core.library.types.OpenClosedType;
38 import org.openhab.core.library.types.StringType;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.ThingStatusInfo;
45 import org.openhab.core.thing.binding.BaseThingHandler;
46 import org.openhab.core.types.Command;
47 import org.openhab.core.types.RefreshType;
50 * The {@link DigiplexAreaHandler} is responsible for handling commands, which are
51 * sent to one of the channels.
53 * @author Robert Michalak - Initial contribution
56 public class DigiplexAreaHandler extends BaseThingHandler {
58 private @Nullable DigiplexAreaConfiguration config;
59 private @Nullable DigiplexBridgeHandler bridgeHandler;
60 private DigiplexAreaMessageHandler visitor = new DigiplexAreaMessageHandler();
62 private OpenClosedType armed = OpenClosedType.CLOSED;
63 private StringType status = AreaStatus.DISARMED.toStringType();
64 private OpenClosedType zoneInMemory = OpenClosedType.CLOSED;
65 private OpenClosedType trouble = OpenClosedType.CLOSED;
66 private OpenClosedType ready = OpenClosedType.CLOSED;
67 private OpenClosedType inProgramming = OpenClosedType.CLOSED;
68 private OpenClosedType alarm = OpenClosedType.CLOSED;
69 private OpenClosedType strobe = OpenClosedType.CLOSED;
70 private StringType lastCommandResult = new StringType();
72 private @Nullable ScheduledFuture<?> refreshTask;
74 public DigiplexAreaHandler(Thing thing) {
79 public void handleCommand(ChannelUID channelUID, Command command) {
80 switch (channelUID.getId()) {
82 if (command == RefreshType.REFRESH) {
83 updateState(AREA_STATUS, status);
87 if (command == RefreshType.REFRESH) {
88 updateState(AREA_ARMED, armed);
91 case AREA_ZONE_IN_MEMORY:
92 if (command == RefreshType.REFRESH) {
93 updateState(AREA_ZONE_IN_MEMORY, zoneInMemory);
97 if (command == RefreshType.REFRESH) {
98 updateState(AREA_TROUBLE, trouble);
102 if (command == RefreshType.REFRESH) {
103 updateState(AREA_READY, ready);
106 case AREA_IN_PROGRAMMING:
107 if (command == RefreshType.REFRESH) {
108 updateState(AREA_IN_PROGRAMMING, inProgramming);
112 if (command == RefreshType.REFRESH) {
113 updateState(AREA_ALARM, alarm);
117 if (command == RefreshType.REFRESH) {
118 updateState(AREA_STROBE, strobe);
122 if (command == RefreshType.REFRESH) {
123 updateState(AREA_CONTROL, lastCommandResult);
124 } else if (command instanceof StringType stringCommand) {
125 processControlCommand(stringCommand.toString());
131 @SuppressWarnings("null")
132 private void processControlCommand(String command) {
133 if (command.length() < 2) {
134 updateControlChannel(COMMAND_FAIL);
138 char commandType = command.charAt(0);
139 char commandSubType = command.charAt(1);
140 switch (commandType) {
142 bridgeHandler.sendRequest(
143 new AreaArmRequest(areaNo, ArmType.fromMessage(commandSubType), command.substring(2)));
146 bridgeHandler.sendRequest(new AreaQuickArmRequest(areaNo, ArmType.fromMessage(commandSubType)));
149 bridgeHandler.sendRequest(new AreaDisarmRequest(areaNo, command.substring(1)));
154 @SuppressWarnings("null")
156 public void initialize() {
157 config = getConfigAs(DigiplexAreaConfiguration.class);
158 Bridge bridge = getBridge();
159 if (bridge == null) {
160 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
163 bridgeHandler = (DigiplexBridgeHandler) bridge.getHandler();
165 String areaParm = getThing().getProperties().get(DigiplexBindingConstants.PROPERTY_AREA_NO);
166 if (areaParm != null) {
167 areaNo = Integer.parseInt(areaParm);
169 bridgeHandler.registerMessageHandler(visitor);
171 updateStatus(ThingStatus.ONLINE);
173 refreshTask = scheduler.scheduleWithFixedDelay(() -> {
174 sendStatusUpdateRequest();
175 }, 0, config.refreshPeriod, TimeUnit.SECONDS);
178 private void updateChannelsAfterStatusResponse() {
179 updateState(AREA_STATUS, status);
180 updateState(AREA_ARMED, armed);
181 updateState(AREA_ZONE_IN_MEMORY, zoneInMemory);
182 updateState(AREA_TROUBLE, trouble);
183 updateState(AREA_READY, ready);
184 updateState(AREA_IN_PROGRAMMING, inProgramming);
185 updateState(AREA_ALARM, alarm);
186 updateState(AREA_STROBE, strobe);
189 @SuppressWarnings("null")
191 public void handleRemoval() {
192 if (visitor != null) {
193 bridgeHandler.unregisterMessageHandler(visitor);
195 if (refreshTask != null) {
196 refreshTask.cancel(true);
198 super.handleRemoval();
202 public void bridgeStatusChanged(ThingStatusInfo thingStatusInfo) {
203 if (thingStatusInfo.getStatus() == ThingStatus.OFFLINE) {
204 updateStatus(ThingStatus.OFFLINE, thingStatusInfo.getStatusDetail());
205 } else if (thingStatusInfo.getStatus() == ThingStatus.ONLINE) {
206 updateStatus(ThingStatus.ONLINE);
207 sendStatusUpdateRequest();
211 private synchronized void updateControlChannel(StringType response) {
212 lastCommandResult = response;
213 updateState(AREA_CONTROL, lastCommandResult);
216 @SuppressWarnings("null")
217 private void sendStatusUpdateRequest() {
218 DigiplexRequest request = new AreaStatusRequest(areaNo);
219 bridgeHandler.sendRequest(request);
222 private class DigiplexAreaMessageHandler implements DigiplexMessageHandler {
225 public void handleAreaStatusResponse(AreaStatusResponse response) {
226 if (response.success && response.areaNo == DigiplexAreaHandler.this.areaNo) {
227 status = new StringType(response.status.toString());
228 armed = response.status.toOpenClosedType();
229 zoneInMemory = openClosedFromBoolean(response.zoneInMemory);
230 trouble = openClosedFromBoolean(response.trouble);
231 ready = openClosedFromBoolean(response.ready);
232 inProgramming = openClosedFromBoolean(response.inProgramming);
233 alarm = openClosedFromBoolean(response.alarm);
234 strobe = openClosedFromBoolean(response.strobe);
235 updateChannelsAfterStatusResponse();
240 public void handleArmDisarmAreaResponse(AreaArmDisarmResponse response) {
241 if (response.areaNo == DigiplexAreaHandler.this.areaNo) {
242 if (response.success) {
243 updateControlChannel(COMMAND_OK);
245 updateControlChannel(COMMAND_FAIL);
251 public void handleAreaEvent(AreaEvent event) {
252 if (event.isForArea(DigiplexAreaHandler.this.areaNo)) {
253 switch (event.getType()) {
254 case READY: // TODO: not sure what it means. Let's send status update request
255 case DISARMED: // in case of disarm we want to ensure that all other channels are updated as well
256 sendStatusUpdateRequest();
259 strobe = OpenClosedType.OPEN;
260 updateState(AREA_STROBE, strobe);
261 // no break intentionally
264 case ALARM_IN_MEMORY:
266 alarm = OpenClosedType.OPEN;
267 updateState(AREA_ALARM, alarm);
273 armed = OpenClosedType.OPEN;
274 updateState(AREA_ARMED, armed);
276 case SYSTEM_IN_TROUBLE:
277 trouble = OpenClosedType.OPEN;
278 updateState(AREA_TROUBLE, trouble);
287 // update status separately, for more concise logic
288 Optional<AreaStatus> tempStatus = Optional.empty();
289 switch (event.getType()) {
291 tempStatus = Optional.of(AreaStatus.ARMED);
294 tempStatus = Optional.of(AreaStatus.ARMED_FORCE);
297 tempStatus = Optional.of(AreaStatus.ARMED_INSTANT);
300 tempStatus = Optional.of(AreaStatus.ARMED_STAY);
303 tempStatus = Optional.of(AreaStatus.DISARMED);
308 tempStatus.ifPresent(s -> updateState(AREA_STATUS, s.toStringType()));