]> git.basschouten.com Git - openhab-addons.git/blob
4012f79faae234a1363adb6df23921c03289b504
[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.digiplex.internal.handler;
14
15 import static org.openhab.binding.digiplex.internal.DigiplexBindingConstants.*;
16 import static org.openhab.binding.digiplex.internal.handler.TypeUtils.openClosedFromBoolean;
17
18 import java.util.Optional;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21
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;
48
49 /**
50  * The {@link DigiplexAreaHandler} is responsible for handling commands, which are
51  * sent to one of the channels.
52  *
53  * @author Robert Michalak - Initial contribution
54  */
55 @NonNullByDefault
56 public class DigiplexAreaHandler extends BaseThingHandler {
57
58     private @Nullable DigiplexAreaConfiguration config;
59     private @Nullable DigiplexBridgeHandler bridgeHandler;
60     private DigiplexAreaMessageHandler visitor = new DigiplexAreaMessageHandler();
61     private int areaNo;
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();
71
72     private @Nullable ScheduledFuture<?> refreshTask;
73
74     public DigiplexAreaHandler(Thing thing) {
75         super(thing);
76     }
77
78     @Override
79     public void handleCommand(ChannelUID channelUID, Command command) {
80         switch (channelUID.getId()) {
81             case AREA_STATUS:
82                 if (command == RefreshType.REFRESH) {
83                     updateState(AREA_STATUS, status);
84                 }
85                 break;
86             case AREA_ARMED:
87                 if (command == RefreshType.REFRESH) {
88                     updateState(AREA_ARMED, armed);
89                 }
90                 break;
91             case AREA_ZONE_IN_MEMORY:
92                 if (command == RefreshType.REFRESH) {
93                     updateState(AREA_ZONE_IN_MEMORY, zoneInMemory);
94                 }
95                 break;
96             case AREA_TROUBLE:
97                 if (command == RefreshType.REFRESH) {
98                     updateState(AREA_TROUBLE, trouble);
99                 }
100                 break;
101             case AREA_READY:
102                 if (command == RefreshType.REFRESH) {
103                     updateState(AREA_READY, ready);
104                 }
105                 break;
106             case AREA_IN_PROGRAMMING:
107                 if (command == RefreshType.REFRESH) {
108                     updateState(AREA_IN_PROGRAMMING, inProgramming);
109                 }
110                 break;
111             case AREA_ALARM:
112                 if (command == RefreshType.REFRESH) {
113                     updateState(AREA_ALARM, alarm);
114                 }
115                 break;
116             case AREA_STROBE:
117                 if (command == RefreshType.REFRESH) {
118                     updateState(AREA_STROBE, strobe);
119                 }
120                 break;
121             case AREA_CONTROL:
122                 if (command == RefreshType.REFRESH) {
123                     updateState(AREA_CONTROL, lastCommandResult);
124                 } else if (command instanceof StringType stringCommand) {
125                     processControlCommand(stringCommand.toString());
126                 }
127                 break;
128         }
129     }
130
131     @SuppressWarnings("null")
132     private void processControlCommand(String command) {
133         if (command.length() < 2) {
134             updateControlChannel(COMMAND_FAIL);
135             return;
136         }
137
138         char commandType = command.charAt(0);
139         char commandSubType = command.charAt(1);
140         switch (commandType) {
141             case 'A':
142                 bridgeHandler.sendRequest(
143                         new AreaArmRequest(areaNo, ArmType.fromMessage(commandSubType), command.substring(2)));
144                 break;
145             case 'Q':
146                 bridgeHandler.sendRequest(new AreaQuickArmRequest(areaNo, ArmType.fromMessage(commandSubType)));
147                 break;
148             case 'D':
149                 bridgeHandler.sendRequest(new AreaDisarmRequest(areaNo, command.substring(1)));
150                 break;
151         }
152     }
153
154     @SuppressWarnings("null")
155     @Override
156     public void initialize() {
157         config = getConfigAs(DigiplexAreaConfiguration.class);
158         Bridge bridge = getBridge();
159         if (bridge == null) {
160             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
161             return;
162         }
163         bridgeHandler = (DigiplexBridgeHandler) bridge.getHandler();
164
165         String areaParm = getThing().getProperties().get(DigiplexBindingConstants.PROPERTY_AREA_NO);
166         if (areaParm != null) {
167             areaNo = Integer.parseInt(areaParm);
168         }
169         bridgeHandler.registerMessageHandler(visitor);
170
171         updateStatus(ThingStatus.ONLINE);
172
173         refreshTask = scheduler.scheduleWithFixedDelay(() -> {
174             sendStatusUpdateRequest();
175         }, 0, config.refreshPeriod, TimeUnit.SECONDS);
176     }
177
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);
187     }
188
189     @SuppressWarnings("null")
190     @Override
191     public void handleRemoval() {
192         if (visitor != null) {
193             bridgeHandler.unregisterMessageHandler(visitor);
194         }
195         if (refreshTask != null) {
196             refreshTask.cancel(true);
197         }
198         super.handleRemoval();
199     }
200
201     @Override
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();
208         }
209     }
210
211     private synchronized void updateControlChannel(StringType response) {
212         lastCommandResult = response;
213         updateState(AREA_CONTROL, lastCommandResult);
214     }
215
216     @SuppressWarnings("null")
217     private void sendStatusUpdateRequest() {
218         DigiplexRequest request = new AreaStatusRequest(areaNo);
219         bridgeHandler.sendRequest(request);
220     }
221
222     private class DigiplexAreaMessageHandler implements DigiplexMessageHandler {
223
224         @Override
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();
236             }
237         }
238
239         @Override
240         public void handleArmDisarmAreaResponse(AreaArmDisarmResponse response) {
241             if (response.areaNo == DigiplexAreaHandler.this.areaNo) {
242                 if (response.success) {
243                     updateControlChannel(COMMAND_OK);
244                 } else {
245                     updateControlChannel(COMMAND_FAIL);
246                 }
247             }
248         }
249
250         @Override
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();
257                         break;
258                     case ALARM_STROBE:
259                         strobe = OpenClosedType.OPEN;
260                         updateState(AREA_STROBE, strobe);
261                         // no break intentionally
262                     case ALARM_FIRE:
263                     case ALARM_AUDIBLE:
264                     case ALARM_IN_MEMORY:
265                     case ALARM_SILENT:
266                         alarm = OpenClosedType.OPEN;
267                         updateState(AREA_ALARM, alarm);
268                         break;
269                     case ARMED:
270                     case ARMED_FORCE:
271                     case ARMED_INSTANT:
272                     case ARMED_STAY:
273                         armed = OpenClosedType.OPEN;
274                         updateState(AREA_ARMED, armed);
275                         break;
276                     case SYSTEM_IN_TROUBLE:
277                         trouble = OpenClosedType.OPEN;
278                         updateState(AREA_TROUBLE, trouble);
279                         break;
280                     case ZONES_BYPASSED:
281                     case ENTRY_DELAY:
282                     case EXIT_DELAY:
283                     default:
284                         break;
285
286                 }
287                 // update status separately, for more concise logic
288                 Optional<AreaStatus> tempStatus = Optional.empty();
289                 switch (event.getType()) {
290                     case ARMED:
291                         tempStatus = Optional.of(AreaStatus.ARMED);
292                         break;
293                     case ARMED_FORCE:
294                         tempStatus = Optional.of(AreaStatus.ARMED_FORCE);
295                         break;
296                     case ARMED_INSTANT:
297                         tempStatus = Optional.of(AreaStatus.ARMED_INSTANT);
298                         break;
299                     case ARMED_STAY:
300                         tempStatus = Optional.of(AreaStatus.ARMED_STAY);
301                         break;
302                     case DISARMED:
303                         tempStatus = Optional.of(AreaStatus.DISARMED);
304                         break;
305                     default:
306                         break;
307                 }
308                 tempStatus.ifPresent(s -> updateState(AREA_STATUS, s.toStringType()));
309             }
310         }
311     }
312 }