]> git.basschouten.com Git - openhab-addons.git/blob
634c290e485a85d3a83cd0a3bba0a7bbdf55832c
[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.neeo.internal.handler;
14
15 import java.io.IOException;
16 import java.util.HashMap;
17 import java.util.Map;
18 import java.util.Objects;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.atomic.AtomicReference;
23
24 import org.apache.commons.lang.StringUtils;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.neeo.internal.NeeoBrainApi;
28 import org.openhab.binding.neeo.internal.NeeoConstants;
29 import org.openhab.binding.neeo.internal.NeeoDeviceConfig;
30 import org.openhab.binding.neeo.internal.NeeoDeviceProtocol;
31 import org.openhab.binding.neeo.internal.NeeoHandlerCallback;
32 import org.openhab.binding.neeo.internal.NeeoRoomConfig;
33 import org.openhab.binding.neeo.internal.NeeoUtil;
34 import org.openhab.binding.neeo.internal.UidUtils;
35 import org.openhab.binding.neeo.internal.models.NeeoDevice;
36 import org.openhab.binding.neeo.internal.models.NeeoDeviceDetails;
37 import org.openhab.binding.neeo.internal.models.NeeoDeviceDetailsTiming;
38 import org.openhab.binding.neeo.internal.models.NeeoRoom;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.thing.Bridge;
41 import org.openhab.core.thing.ChannelUID;
42 import org.openhab.core.thing.Thing;
43 import org.openhab.core.thing.ThingStatus;
44 import org.openhab.core.thing.ThingStatusDetail;
45 import org.openhab.core.thing.ThingUID;
46 import org.openhab.core.thing.binding.BaseThingHandler;
47 import org.openhab.core.thing.binding.BridgeHandler;
48 import org.openhab.core.thing.binding.builder.ThingBuilder;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.RefreshType;
51 import org.openhab.core.types.State;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * An extension of {@link BaseThingHandler} that is responsible for handling commands for a device
57  *
58  * @author Tim Roberts - Initial contribution
59  */
60 @NonNullByDefault
61 public class NeeoDeviceHandler extends BaseThingHandler {
62
63     /** The logger */
64     private final Logger logger = LoggerFactory.getLogger(NeeoDeviceHandler.class);
65
66     /**
67      * The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
68      */
69     private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>(null);
70
71     /**
72      * The refresh task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
73      */
74     private final AtomicReference<@Nullable ScheduledFuture<?>> refreshTask = new AtomicReference<>(null);
75
76     /** The {@link NeeoDeviceProtocol} (null until set by {@link #initializationTask}) */
77     private final AtomicReference<@Nullable NeeoDeviceProtocol> deviceProtocol = new AtomicReference<>();
78
79     /**
80      * Instantiates a new neeo device handler.
81      *
82      * @param thing the non-null thing
83      */
84     NeeoDeviceHandler(Thing thing) {
85         super(thing);
86         Objects.requireNonNull(thing, "thing cannot be null");
87     }
88
89     @Override
90     public void handleCommand(ChannelUID channelUID, Command command) {
91         Objects.requireNonNull(channelUID, "channelUID cannot be null");
92         Objects.requireNonNull(command, "command cannot be null");
93
94         final NeeoDeviceProtocol protocol = deviceProtocol.get();
95         if (protocol == null) {
96             logger.debug("Protocol is null - ignoring update: {}", channelUID);
97             return;
98         }
99
100         final String[] channelIds = UidUtils.parseChannelId(channelUID);
101         if (channelIds.length == 0) {
102             logger.debug("Bad group declaration: {}", channelUID);
103             return;
104         }
105
106         final String localGroupId = channelUID.getGroupId();
107         final String groupId = localGroupId == null || StringUtils.isEmpty(localGroupId) ? "" : localGroupId;
108         final String channelId = channelIds[0];
109         final String channelKey = channelIds.length > 1 ? channelIds[1] : "";
110
111         if (StringUtils.isEmpty(groupId)) {
112             logger.debug("GroupID for channel is null - ignoring command: {}", channelUID);
113             return;
114         }
115
116         if (command instanceof RefreshType) {
117             refreshChannel(protocol, groupId, channelId, channelKey);
118         } else {
119             switch (groupId) {
120                 case NeeoConstants.DEVICE_GROUP_MACROS_ID:
121                     switch (channelId) {
122                         case NeeoConstants.DEVICE_CHANNEL_STATUS:
123                             if (command instanceof OnOffType) {
124                                 protocol.setMacroStatus(channelKey, command == OnOffType.ON);
125                             }
126                             break;
127                         default:
128                             logger.debug("Unknown channel to set: {}", channelUID);
129                             break;
130                     }
131                     break;
132                 default:
133                     logger.debug("Unknown group to set: {}", channelUID);
134                     break;
135             }
136         }
137     }
138
139     /**
140      * Refresh the specified channel section, key and id using the specified protocol
141      *
142      * @param protocol a non-null protocol to use
143      * @param groupId the non-empty groupId
144      * @param channelId the non-empty channel id
145      * @param channelKey the non-empty channel key
146      */
147     private void refreshChannel(NeeoDeviceProtocol protocol, String groupId, String channelId, String channelKey) {
148         Objects.requireNonNull(protocol, "protocol cannot be null");
149         NeeoUtil.requireNotEmpty(groupId, "groupId must not be empty");
150         NeeoUtil.requireNotEmpty(channelId, "channelId must not be empty");
151         NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
152
153         switch (groupId) {
154             case NeeoConstants.DEVICE_GROUP_MACROS_ID:
155                 switch (channelId) {
156                     case NeeoConstants.DEVICE_CHANNEL_STATUS:
157                         protocol.refreshMacroStatus(channelKey);
158                         break;
159                 }
160                 break;
161         }
162     }
163
164     @Override
165     public void initialize() {
166         NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
167             initializeTask();
168         })));
169     }
170
171     /**
172      * Initializes the task be creating the {@link NeeoDeviceProtocol}, going online and then scheduling the refresh
173      * task.
174      */
175     private void initializeTask() {
176         final NeeoDeviceConfig config = getConfigAs(NeeoDeviceConfig.class);
177
178         final String roomKey = getRoomKey();
179         if (roomKey == null || StringUtils.isEmpty(roomKey)) {
180             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
181                     "Room key (from the parent room bridge) was not found");
182             return;
183         }
184
185         final String deviceKey = config.getDeviceKey();
186         if (deviceKey == null || StringUtils.isEmpty(deviceKey)) {
187             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
188                     "Device key was not found or empty");
189             return;
190         }
191
192         try {
193             NeeoUtil.checkInterrupt();
194             final NeeoBrainApi brainApi = getNeeoBrainApi();
195             if (brainApi == null) {
196                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Cannot find the NEEO Brain API");
197                 return;
198             }
199
200             final NeeoRoom room = brainApi.getRoom(roomKey);
201
202             final NeeoDevice device = room.getDevices().getDevice(deviceKey);
203             if (device == null) {
204                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
205                         "Device (" + config.getDeviceKey() + ") was not found in room (" + roomKey + ")");
206                 return;
207             }
208
209             final ThingUID thingUid = getThing().getUID();
210
211             final Map<String, String> properties = new HashMap<>();
212             final NeeoDeviceDetails details = device.getDetails();
213             if (details != null) {
214                 /** The following properties have matches in org.openhab.io.neeo.OpenHabToDeviceConverter.java */
215                 addProperty(properties, "Source Name", details.getSourceName());
216                 addProperty(properties, "Adapter Name", details.getAdapterName());
217                 addProperty(properties, "Type", details.getType());
218                 addProperty(properties, "Manufacturer", details.getManufacturer());
219                 addProperty(properties, "Name", details.getName());
220
221                 final NeeoDeviceDetailsTiming timing = details.getTiming();
222                 if (timing != null) {
223                     properties.put("Standby Command Delay", toString(timing.getStandbyCommandDelay()));
224                     properties.put("Source Switch Delay", toString(timing.getSourceSwitchDelay()));
225                     properties.put("Shutdown Delay", toString(timing.getShutdownDelay()));
226                 }
227
228                 properties.put("Device Capabilities", StringUtils.join(details.getDeviceCapabilities(), ','));
229             }
230
231             final ThingBuilder thingBuilder = editThing();
232             thingBuilder.withLabel(device.getName() + " (NEEO " + brainApi.getBrain().getKey() + ")")
233                     .withProperties(properties).withChannels(ChannelUtils.generateChannels(thingUid, device));
234             updateThing(thingBuilder.build());
235
236             NeeoUtil.checkInterrupt();
237             final NeeoDeviceProtocol protocol = new NeeoDeviceProtocol(new NeeoHandlerCallback() {
238
239                 @Override
240                 public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
241                     updateStatus(status, detail, msg);
242                 }
243
244                 @Override
245                 public void stateChanged(String channelId, State state) {
246                     updateState(channelId, state);
247                 }
248
249                 @Override
250                 public void setProperty(String propertyName, String propertyValue) {
251                     getThing().setProperty(propertyName, propertyValue);
252                 }
253
254                 @Override
255                 public void scheduleTask(Runnable task, long milliSeconds) {
256                     scheduler.schedule(task, milliSeconds, TimeUnit.MILLISECONDS);
257                 }
258
259                 @Override
260                 public void triggerEvent(String channelID, String event) {
261                     triggerChannel(channelID, event);
262                 }
263
264                 @Nullable
265                 @Override
266                 public NeeoBrainApi getApi() {
267                     return getNeeoBrainApi();
268                 }
269             }, roomKey, deviceKey);
270             deviceProtocol.getAndSet(protocol);
271
272             NeeoUtil.checkInterrupt();
273             updateStatus(ThingStatus.ONLINE);
274         } catch (IOException e) {
275             logger.debug("IOException during initialization", e);
276             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
277                     "Room " + roomKey + " couldn't be found");
278         } catch (InterruptedException e) {
279             logger.debug("Initialization was interrupted", e);
280             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
281                     "Initialization was interrupted");
282         }
283     }
284
285     /**
286      * Helper method to add a property to the properties map if the value is not null
287      *
288      * @param properties a non-null properties map
289      * @param key a non-null, non-empty key
290      * @param value a possibly null, possibly empty key
291      */
292     private void addProperty(Map<String, String> properties, String key, @Nullable String value) {
293         Objects.requireNonNull(properties, "properties cannot be null");
294         NeeoUtil.requireNotEmpty(key, "key cannot be empty");
295         if (value != null && StringUtils.isNotEmpty(value)) {
296             properties.put(key, value);
297         }
298     }
299
300     /**
301      * Helper method to get the room key from the parent bridge (which should be a room)
302      *
303      * @return a non-null, non-empty room key if found, null if not found
304      */
305     @Nullable
306     private String getRoomKey() {
307         final Bridge bridge = getBridge();
308         if (bridge != null) {
309             final BridgeHandler handler = bridge.getHandler();
310             if (handler instanceof NeeoRoomHandler) {
311                 return handler.getThing().getConfiguration().as(NeeoRoomConfig.class).getRoomKey();
312             }
313         }
314         return null;
315     }
316
317     /**
318      * Helper method to simply create a string from an integer
319      *
320      * @param i the integer
321      * @return the resulting string representation
322      */
323     private static String toString(@Nullable Integer i) {
324         if (i == null) {
325             return "";
326         }
327         return i.toString();
328     }
329
330     /**
331      * Helper method to return the {@link NeeoRoomHandler} associated with this handler
332      *
333      * @return a possibly null {@link NeeoRoomHandler}
334      */
335     @Nullable
336     private NeeoRoomHandler getRoomHandler() {
337         final Bridge parent = getBridge();
338         if (parent != null) {
339             final BridgeHandler handler = parent.getHandler();
340             if (handler instanceof NeeoRoomHandler) {
341                 return ((NeeoRoomHandler) handler);
342             }
343         }
344         return null;
345     }
346
347     /**
348      * Returns the {@link NeeoBrainApi} associated with this handler.
349      *
350      * @return the {@link NeeoBrainApi} or null if not found
351      */
352     @Nullable
353     private NeeoBrainApi getNeeoBrainApi() {
354         final NeeoRoomHandler handler = getRoomHandler();
355         return handler == null ? null : handler.getNeeoBrainApi();
356     }
357
358     /**
359      * Returns the brain ID associated with this handler.
360      *
361      * @return the brain ID or null if not found
362      */
363     @Nullable
364     public String getNeeoBrainId() {
365         final NeeoRoomHandler handler = getRoomHandler();
366         return handler == null ? null : handler.getNeeoBrainId();
367     }
368
369     @Override
370     public void dispose() {
371         NeeoUtil.cancel(initializationTask.getAndSet(null));
372         NeeoUtil.cancel(refreshTask.getAndSet(null));
373         deviceProtocol.getAndSet(null);
374     }
375 }