]> git.basschouten.com Git - openhab-addons.git/blob
e29aa43ad1b3277fdbb2c1409d3f90700a6b400b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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) {
125                     processControlCommand(((StringType) command).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         areaNo = Integer.parseInt(areaParm);
167         bridgeHandler.registerMessageHandler(visitor);
168
169         updateStatus(ThingStatus.ONLINE);
170
171         refreshTask = scheduler.scheduleWithFixedDelay(() -> {
172             sendStatusUpdateRequest();
173         }, 0, config.refreshPeriod, TimeUnit.SECONDS);
174     }
175
176     private void updateChannelsAfterStatusResponse() {
177         updateState(AREA_STATUS, status);
178         updateState(AREA_ARMED, armed);
179         updateState(AREA_ZONE_IN_MEMORY, zoneInMemory);
180         updateState(AREA_TROUBLE, trouble);
181         updateState(AREA_READY, ready);
182         updateState(AREA_IN_PROGRAMMING, inProgramming);
183         updateState(AREA_ALARM, alarm);
184         updateState(AREA_STROBE, strobe);
185     }
186
187     @SuppressWarnings("null")
188     @Override
189     public void handleRemoval() {
190         if (visitor != null) {
191             bridgeHandler.unregisterMessageHandler(visitor);
192         }
193         if (refreshTask != null) {
194             refreshTask.cancel(true);
195         }
196         super.handleRemoval();
197     }
198
199     @Override
200     public void bridgeStatusChanged(ThingStatusInfo thingStatusInfo) {
201         if (thingStatusInfo.getStatus() == ThingStatus.OFFLINE) {
202             updateStatus(ThingStatus.OFFLINE, thingStatusInfo.getStatusDetail());
203         } else if (thingStatusInfo.getStatus() == ThingStatus.ONLINE) {
204             updateStatus(ThingStatus.ONLINE);
205             sendStatusUpdateRequest();
206         }
207     }
208
209     private synchronized void updateControlChannel(StringType response) {
210         lastCommandResult = response;
211         updateState(AREA_CONTROL, lastCommandResult);
212     }
213
214     @SuppressWarnings("null")
215     private void sendStatusUpdateRequest() {
216         DigiplexRequest request = new AreaStatusRequest(areaNo);
217         bridgeHandler.sendRequest(request);
218     }
219
220     private class DigiplexAreaMessageHandler implements DigiplexMessageHandler {
221
222         @Override
223         public void handleAreaStatusResponse(AreaStatusResponse response) {
224             if (response.success && response.areaNo == DigiplexAreaHandler.this.areaNo) {
225                 status = new StringType(response.status.toString());
226                 armed = response.status.toOpenClosedType();
227                 zoneInMemory = openClosedFromBoolean(response.zoneInMemory);
228                 trouble = openClosedFromBoolean(response.trouble);
229                 ready = openClosedFromBoolean(response.ready);
230                 inProgramming = openClosedFromBoolean(response.inProgramming);
231                 alarm = openClosedFromBoolean(response.alarm);
232                 strobe = openClosedFromBoolean(response.strobe);
233                 updateChannelsAfterStatusResponse();
234             }
235         }
236
237         @Override
238         public void handleArmDisarmAreaResponse(AreaArmDisarmResponse response) {
239             if (response.areaNo == DigiplexAreaHandler.this.areaNo) {
240                 if (response.success) {
241                     updateControlChannel(COMMAND_OK);
242                 } else {
243                     updateControlChannel(COMMAND_FAIL);
244                 }
245             }
246         }
247
248         @Override
249         public void handleAreaEvent(AreaEvent event) {
250             if (event.isForArea(DigiplexAreaHandler.this.areaNo)) {
251                 switch (event.getType()) {
252                     case READY: // TODO: not sure what it means. Let's send status update request
253                     case DISARMED: // in case of disarm we want to ensure that all other channels are updated as well
254                         sendStatusUpdateRequest();
255                         break;
256                     case ALARM_STROBE:
257                         strobe = OpenClosedType.OPEN;
258                         updateState(AREA_STROBE, strobe);
259                         // no break intentionally
260                     case ALARM_FIRE:
261                     case ALARM_AUDIBLE:
262                     case ALARM_IN_MEMORY:
263                     case ALARM_SILENT:
264                         alarm = OpenClosedType.OPEN;
265                         updateState(AREA_ALARM, alarm);
266                         break;
267                     case ARMED:
268                     case ARMED_FORCE:
269                     case ARMED_INSTANT:
270                     case ARMED_STAY:
271                         armed = OpenClosedType.OPEN;
272                         updateState(AREA_ARMED, armed);
273                         break;
274                     case SYSTEM_IN_TROUBLE:
275                         trouble = OpenClosedType.OPEN;
276                         updateState(AREA_TROUBLE, trouble);
277                         break;
278                     case ZONES_BYPASSED:
279                     case ENTRY_DELAY:
280                     case EXIT_DELAY:
281                     default:
282                         break;
283
284                 }
285                 // update status separately, for more concise logic
286                 Optional<AreaStatus> tempStatus = Optional.empty();
287                 switch (event.getType()) {
288                     case ARMED:
289                         tempStatus = Optional.of(AreaStatus.ARMED);
290                         break;
291                     case ARMED_FORCE:
292                         tempStatus = Optional.of(AreaStatus.ARMED_FORCE);
293                         break;
294                     case ARMED_INSTANT:
295                         tempStatus = Optional.of(AreaStatus.ARMED_INSTANT);
296                         break;
297                     case ARMED_STAY:
298                         tempStatus = Optional.of(AreaStatus.ARMED_STAY);
299                         break;
300                     case DISARMED:
301                         tempStatus = Optional.of(AreaStatus.DISARMED);
302                         break;
303                     default:
304                         break;
305                 }
306                 tempStatus.ifPresent(s -> updateState(AREA_STATUS, s.toStringType()));
307             }
308         }
309     }
310 }