]> git.basschouten.com Git - openhab-addons.git/blob
30f317afea033063836ca1039f45abef9fca7eaf
[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.nikohomecontrol.internal.handler;
14
15 import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
16 import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
17 import static org.openhab.core.types.RefreshType.REFRESH;
18
19 import java.util.HashMap;
20 import java.util.Map;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
25 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcActionEvent;
26 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
27 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
28 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAction2;
29 import org.openhab.core.library.types.IncreaseDecreaseType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.StopMoveType;
33 import org.openhab.core.library.types.UpDownType;
34 import org.openhab.core.thing.Bridge;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.ThingStatusInfo;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.types.Command;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link NikoHomeControlActionHandler} is responsible for handling commands, which are
47  * sent to one of the channels.
48  *
49  * @author Mark Herwege - Initial Contribution
50  */
51 @NonNullByDefault
52 public class NikoHomeControlActionHandler extends BaseThingHandler implements NhcActionEvent {
53
54     private final Logger logger = LoggerFactory.getLogger(NikoHomeControlActionHandler.class);
55
56     private volatile @Nullable NhcAction nhcAction;
57
58     private volatile boolean initialized = false;
59
60     private String actionId = "";
61     private int stepValue;
62     private boolean invert;
63
64     public NikoHomeControlActionHandler(Thing thing) {
65         super(thing);
66     }
67
68     @Override
69     public void handleCommand(ChannelUID channelUID, Command command) {
70         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
71         if (nhcComm == null) {
72             logger.debug("communication not up yet, cannot handle command {} for {}", command, channelUID);
73             return;
74         }
75
76         // This can be expensive, therefore do it in a job.
77         scheduler.submit(() -> {
78             if (!nhcComm.communicationActive()) {
79                 restartCommunication(nhcComm);
80             }
81
82             if (nhcComm.communicationActive()) {
83                 handleCommandSelection(channelUID, command);
84             }
85         });
86     }
87
88     private void handleCommandSelection(ChannelUID channelUID, Command command) {
89         NhcAction nhcAction = this.nhcAction;
90         if (nhcAction == null) {
91             logger.debug("action with ID {} not initialized", actionId);
92             return;
93         }
94
95         logger.debug("handle command {} for {}", command, channelUID);
96
97         if (REFRESH.equals(command)) {
98             actionEvent(nhcAction.getState());
99             return;
100         }
101
102         switch (channelUID.getId()) {
103             case CHANNEL_BUTTON:
104             case CHANNEL_SWITCH:
105                 handleSwitchCommand(command);
106                 updateStatus(ThingStatus.ONLINE);
107                 break;
108             case CHANNEL_BRIGHTNESS:
109                 handleBrightnessCommand(command);
110                 updateStatus(ThingStatus.ONLINE);
111                 break;
112             case CHANNEL_ROLLERSHUTTER:
113                 handleRollershutterCommand(command);
114                 updateStatus(ThingStatus.ONLINE);
115                 break;
116             default:
117                 logger.debug("unexpected command for channel {}", channelUID.getId());
118         }
119     }
120
121     private void handleSwitchCommand(Command command) {
122         NhcAction nhcAction = this.nhcAction;
123         if (nhcAction == null) {
124             logger.debug("action with ID {} not initialized", actionId);
125             return;
126         }
127
128         if (command instanceof OnOffType) {
129             OnOffType s = (OnOffType) command;
130             if (OnOffType.OFF.equals(s)) {
131                 nhcAction.execute(NHCOFF);
132             } else {
133                 nhcAction.execute(NHCON);
134             }
135         }
136     }
137
138     private void handleBrightnessCommand(Command command) {
139         NhcAction nhcAction = this.nhcAction;
140         if (nhcAction == null) {
141             logger.debug("action with ID {} not initialized", actionId);
142             return;
143         }
144
145         if (command instanceof OnOffType) {
146             OnOffType s = (OnOffType) command;
147             if (OnOffType.OFF.equals(s)) {
148                 nhcAction.execute(NHCOFF);
149             } else {
150                 nhcAction.execute(NHCON);
151             }
152         } else if (command instanceof IncreaseDecreaseType) {
153             IncreaseDecreaseType s = (IncreaseDecreaseType) command;
154             int currentValue = nhcAction.getState();
155             int newValue;
156             if (IncreaseDecreaseType.INCREASE.equals(s)) {
157                 newValue = currentValue + stepValue;
158                 // round down to step multiple
159                 newValue = newValue - newValue % stepValue;
160                 nhcAction.execute(Integer.toString(newValue > 100 ? 100 : newValue));
161             } else {
162                 newValue = currentValue - stepValue;
163                 // round up to step multiple
164                 newValue = newValue + newValue % stepValue;
165                 if (newValue <= 0) {
166                     nhcAction.execute(NHCOFF);
167                 } else {
168                     nhcAction.execute(Integer.toString(newValue));
169                 }
170             }
171         } else if (command instanceof PercentType) {
172             PercentType p = (PercentType) command;
173             if (PercentType.ZERO.equals(p)) {
174                 nhcAction.execute(NHCOFF);
175             } else {
176                 nhcAction.execute(Integer.toString(p.intValue()));
177             }
178         }
179     }
180
181     private void handleRollershutterCommand(Command command) {
182         NhcAction nhcAction = this.nhcAction;
183         if (nhcAction == null) {
184             logger.debug("action with ID {} not initialized", actionId);
185             return;
186         }
187
188         if (command instanceof UpDownType) {
189             UpDownType s = (UpDownType) command;
190             if (UpDownType.UP.equals(s)) {
191                 nhcAction.execute(!invert ? NHCUP : NHCDOWN);
192             } else {
193                 nhcAction.execute(!invert ? NHCDOWN : NHCUP);
194             }
195         } else if (command instanceof StopMoveType) {
196             nhcAction.execute(NHCSTOP);
197         } else if (command instanceof PercentType) {
198             PercentType p = (PercentType) command;
199             nhcAction.execute(!invert ? Integer.toString(100 - p.intValue()) : Integer.toString(p.intValue()));
200         }
201     }
202
203     @Override
204     public void initialize() {
205         initialized = false;
206
207         NikoHomeControlActionConfig config;
208         if (thing.getThingTypeUID().equals(THING_TYPE_DIMMABLE_LIGHT)) {
209             config = getConfig().as(NikoHomeControlActionDimmerConfig.class);
210             stepValue = ((NikoHomeControlActionDimmerConfig) config).step;
211         } else if (thing.getThingTypeUID().equals(THING_TYPE_BLIND)) {
212             config = getConfig().as(NikoHomeControlActionBlindConfig.class);
213             invert = ((NikoHomeControlActionBlindConfig) config).invert;
214         } else {
215             config = getConfig().as(NikoHomeControlActionConfig.class);
216         }
217         actionId = config.actionId;
218
219         NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
220         if (bridgeHandler == null) {
221             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
222                     "@text/offline.configuration-error.invalid-bridge-handler");
223             return;
224         }
225
226         updateStatus(ThingStatus.UNKNOWN);
227
228         Bridge bridge = getBridge();
229         if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
230             // We need to do this in a separate thread because we may have to wait for the
231             // communication to become active
232             scheduler.submit(this::startCommunication);
233         }
234     }
235
236     private synchronized void startCommunication() {
237         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
238
239         if (nhcComm == null) {
240             return;
241         }
242
243         if (!nhcComm.communicationActive()) {
244             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
245                     "@text/offline.communication-error");
246             return;
247         }
248
249         NhcAction nhcAction = nhcComm.getActions().get(actionId);
250         if (nhcAction == null) {
251             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
252                     "@text/offline.configuration-error.actionId");
253             return;
254         }
255
256         nhcAction.setEventHandler(this);
257
258         updateProperties(nhcAction);
259
260         String actionLocation = nhcAction.getLocation();
261         if (thing.getLocation() == null) {
262             thing.setLocation(actionLocation);
263         }
264
265         this.nhcAction = nhcAction;
266
267         logger.debug("action initialized {}", actionId);
268
269         Bridge bridge = getBridge();
270         if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
271             updateStatus(ThingStatus.ONLINE);
272         } else {
273             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
274         }
275
276         actionEvent(nhcAction.getState());
277
278         initialized = true;
279     }
280
281     @Override
282     public void dispose() {
283         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
284         if (nhcComm != null) {
285             NhcAction action = nhcComm.getActions().get(actionId);
286             if (action != null) {
287                 action.unsetEventHandler();
288             }
289         }
290         nhcAction = null;
291         super.dispose();
292     }
293
294     private void updateProperties(NhcAction nhcAction) {
295         Map<String, String> properties = new HashMap<>();
296
297         properties.put("type", String.valueOf(nhcAction.getType()));
298         if (getThing().getThingTypeUID() == THING_TYPE_BLIND) {
299             properties.put("timeToOpen", String.valueOf(nhcAction.getOpenTime()));
300             properties.put("timeToClose", String.valueOf(nhcAction.getCloseTime()));
301         }
302
303         if (nhcAction instanceof NhcAction2) {
304             NhcAction2 action = (NhcAction2) nhcAction;
305             properties.put(PROPERTY_DEVICE_TYPE, action.getDeviceType());
306             properties.put(PROPERTY_DEVICE_TECHNOLOGY, action.getDeviceTechnology());
307             properties.put(PROPERTY_DEVICE_MODEL, action.getDeviceModel());
308         }
309
310         thing.setProperties(properties);
311     }
312
313     @Override
314     public void actionEvent(int actionState) {
315         NhcAction nhcAction = this.nhcAction;
316         if (nhcAction == null) {
317             logger.debug("action with ID {} not initialized", actionId);
318             return;
319         }
320
321         ActionType actionType = nhcAction.getType();
322
323         switch (actionType) {
324             case TRIGGER:
325                 updateState(CHANNEL_BUTTON, (actionState == 0) ? OnOffType.OFF : OnOffType.ON);
326                 updateStatus(ThingStatus.ONLINE);
327             case RELAY:
328                 updateState(CHANNEL_SWITCH, (actionState == 0) ? OnOffType.OFF : OnOffType.ON);
329                 updateStatus(ThingStatus.ONLINE);
330                 break;
331             case DIMMER:
332                 updateState(CHANNEL_BRIGHTNESS, new PercentType(actionState));
333                 updateStatus(ThingStatus.ONLINE);
334                 break;
335             case ROLLERSHUTTER:
336                 updateState(CHANNEL_ROLLERSHUTTER,
337                         !invert ? new PercentType(100 - actionState) : new PercentType(actionState));
338                 updateStatus(ThingStatus.ONLINE);
339                 break;
340             default:
341                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
342                         "@text/offline.configuration-error.actionType");
343         }
344     }
345
346     @Override
347     public void actionInitialized() {
348         Bridge bridge = getBridge();
349         if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
350             updateStatus(ThingStatus.ONLINE);
351         }
352     }
353
354     @Override
355     public void actionRemoved() {
356         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
357                 "@text/offline.configuration-error.actionRemoved");
358     }
359
360     private void restartCommunication(NikoHomeControlCommunication nhcComm) {
361         // We lost connection but the connection object is there, so was correctly started.
362         // Try to restart communication.
363         nhcComm.scheduleRestartCommunication();
364         // If still not active, take thing offline and return.
365         if (!nhcComm.communicationActive()) {
366             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
367                     "@text/offline.communication-error");
368             return;
369         }
370         // Also put the bridge back online
371         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
372         if (nhcBridgeHandler != null) {
373             nhcBridgeHandler.bridgeOnline();
374         } else {
375             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
376                     "@text/offline.configuration-error.invalid-bridge-handler");
377         }
378     }
379
380     private @Nullable NikoHomeControlCommunication getCommunication(
381             @Nullable NikoHomeControlBridgeHandler nhcBridgeHandler) {
382         return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
383     }
384
385     private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
386         Bridge nhcBridge = getBridge();
387         return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
388     }
389
390     @Override
391     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
392         ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
393         if (ThingStatus.ONLINE.equals(bridgeStatus)) {
394             if (!initialized) {
395                 scheduler.submit(this::startCommunication);
396             } else {
397                 updateStatus(ThingStatus.ONLINE);
398             }
399         } else {
400             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
401         }
402     }
403 }