]> git.basschouten.com Git - openhab-addons.git/blob
bd9862f87c0035a6e7abfc6070e10e4e6679f7bf
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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 onOffCommand) {
129             if (OnOffType.OFF.equals(onOffCommand)) {
130                 nhcAction.execute(NHCOFF);
131             } else {
132                 nhcAction.execute(NHCON);
133             }
134         }
135     }
136
137     private void handleBrightnessCommand(Command command) {
138         NhcAction nhcAction = this.nhcAction;
139         if (nhcAction == null) {
140             logger.debug("action with ID {} not initialized", actionId);
141             return;
142         }
143
144         if (command instanceof OnOffType onOffCommand) {
145             if (OnOffType.OFF.equals(onOffCommand)) {
146                 nhcAction.execute(NHCOFF);
147             } else {
148                 nhcAction.execute(NHCON);
149             }
150         } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
151             int currentValue = nhcAction.getState();
152             int newValue;
153             if (IncreaseDecreaseType.INCREASE.equals(increaseDecreaseCommand)) {
154                 newValue = currentValue + stepValue;
155                 // round down to step multiple
156                 newValue = newValue - newValue % stepValue;
157                 nhcAction.execute(Integer.toString(newValue > 100 ? 100 : newValue));
158             } else {
159                 newValue = currentValue - stepValue;
160                 // round up to step multiple
161                 newValue = newValue + newValue % stepValue;
162                 if (newValue <= 0) {
163                     nhcAction.execute(NHCOFF);
164                 } else {
165                     nhcAction.execute(Integer.toString(newValue));
166                 }
167             }
168         } else if (command instanceof PercentType percentCommand) {
169             if (PercentType.ZERO.equals(percentCommand)) {
170                 nhcAction.execute(NHCOFF);
171             } else {
172                 nhcAction.execute(Integer.toString(percentCommand.intValue()));
173             }
174         }
175     }
176
177     private void handleRollershutterCommand(Command command) {
178         NhcAction nhcAction = this.nhcAction;
179         if (nhcAction == null) {
180             logger.debug("action with ID {} not initialized", actionId);
181             return;
182         }
183
184         if (command instanceof UpDownType upDownCommand) {
185             if (UpDownType.UP.equals(upDownCommand)) {
186                 nhcAction.execute(!invert ? NHCUP : NHCDOWN);
187             } else {
188                 nhcAction.execute(!invert ? NHCDOWN : NHCUP);
189             }
190         } else if (command instanceof StopMoveType) {
191             nhcAction.execute(NHCSTOP);
192         } else if (command instanceof PercentType percentCommand) {
193             nhcAction.execute(!invert ? Integer.toString(100 - percentCommand.intValue())
194                     : Integer.toString(percentCommand.intValue()));
195         }
196     }
197
198     @Override
199     public void initialize() {
200         initialized = false;
201
202         NikoHomeControlActionConfig config;
203         if (thing.getThingTypeUID().equals(THING_TYPE_DIMMABLE_LIGHT)) {
204             config = getConfig().as(NikoHomeControlActionDimmerConfig.class);
205             stepValue = ((NikoHomeControlActionDimmerConfig) config).step;
206         } else if (thing.getThingTypeUID().equals(THING_TYPE_BLIND)) {
207             config = getConfig().as(NikoHomeControlActionBlindConfig.class);
208             invert = ((NikoHomeControlActionBlindConfig) config).invert;
209         } else {
210             config = getConfig().as(NikoHomeControlActionConfig.class);
211         }
212         actionId = config.actionId;
213
214         NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
215         if (bridgeHandler == null) {
216             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
217                     "@text/offline.configuration-error.invalid-bridge-handler");
218             return;
219         }
220
221         updateStatus(ThingStatus.UNKNOWN);
222
223         Bridge bridge = getBridge();
224         if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
225             // We need to do this in a separate thread because we may have to wait for the
226             // communication to become active
227             scheduler.submit(this::startCommunication);
228         }
229     }
230
231     private synchronized void startCommunication() {
232         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
233
234         if (nhcComm == null) {
235             return;
236         }
237
238         if (!nhcComm.communicationActive()) {
239             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
240                     "@text/offline.communication-error");
241             return;
242         }
243
244         NhcAction nhcAction = nhcComm.getActions().get(actionId);
245         if (nhcAction == null) {
246             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
247                     "@text/offline.configuration-error.actionId");
248             return;
249         }
250
251         nhcAction.setEventHandler(this);
252
253         updateProperties(nhcAction);
254
255         String actionLocation = nhcAction.getLocation();
256         if (thing.getLocation() == null) {
257             thing.setLocation(actionLocation);
258         }
259
260         this.nhcAction = nhcAction;
261
262         logger.debug("action initialized {}", actionId);
263
264         Bridge bridge = getBridge();
265         if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
266             updateStatus(ThingStatus.ONLINE);
267         } else {
268             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
269         }
270
271         actionEvent(nhcAction.getState());
272
273         initialized = true;
274     }
275
276     @Override
277     public void dispose() {
278         NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
279         if (nhcComm != null) {
280             NhcAction action = nhcComm.getActions().get(actionId);
281             if (action != null) {
282                 action.unsetEventHandler();
283             }
284         }
285         nhcAction = null;
286         super.dispose();
287     }
288
289     private void updateProperties(NhcAction nhcAction) {
290         Map<String, String> properties = new HashMap<>();
291
292         properties.put("type", String.valueOf(nhcAction.getType()));
293         if (getThing().getThingTypeUID() == THING_TYPE_BLIND) {
294             properties.put("timeToOpen", String.valueOf(nhcAction.getOpenTime()));
295             properties.put("timeToClose", String.valueOf(nhcAction.getCloseTime()));
296         }
297
298         if (nhcAction instanceof NhcAction2 action) {
299             properties.put(PROPERTY_DEVICE_TYPE, action.getDeviceType());
300             properties.put(PROPERTY_DEVICE_TECHNOLOGY, action.getDeviceTechnology());
301             properties.put(PROPERTY_DEVICE_MODEL, action.getDeviceModel());
302         }
303
304         thing.setProperties(properties);
305     }
306
307     @Override
308     public void actionEvent(int actionState) {
309         NhcAction nhcAction = this.nhcAction;
310         if (nhcAction == null) {
311             logger.debug("action with ID {} not initialized", actionId);
312             return;
313         }
314
315         ActionType actionType = nhcAction.getType();
316
317         switch (actionType) {
318             case TRIGGER:
319                 updateState(CHANNEL_BUTTON, OnOffType.from(actionState != 0));
320                 updateStatus(ThingStatus.ONLINE);
321             case RELAY:
322                 updateState(CHANNEL_SWITCH, OnOffType.from(actionState != 0));
323                 updateStatus(ThingStatus.ONLINE);
324                 break;
325             case DIMMER:
326                 updateState(CHANNEL_BRIGHTNESS, new PercentType(actionState));
327                 updateStatus(ThingStatus.ONLINE);
328                 break;
329             case ROLLERSHUTTER:
330                 updateState(CHANNEL_ROLLERSHUTTER,
331                         !invert ? new PercentType(100 - actionState) : new PercentType(actionState));
332                 updateStatus(ThingStatus.ONLINE);
333                 break;
334             default:
335                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
336                         "@text/offline.configuration-error.actionType");
337         }
338     }
339
340     @Override
341     public void actionInitialized() {
342         Bridge bridge = getBridge();
343         if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
344             updateStatus(ThingStatus.ONLINE);
345         }
346     }
347
348     @Override
349     public void actionRemoved() {
350         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
351                 "@text/offline.configuration-error.actionRemoved");
352     }
353
354     private void restartCommunication(NikoHomeControlCommunication nhcComm) {
355         // We lost connection but the connection object is there, so was correctly started.
356         // Try to restart communication.
357         nhcComm.scheduleRestartCommunication();
358         // If still not active, take thing offline and return.
359         if (!nhcComm.communicationActive()) {
360             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
361                     "@text/offline.communication-error");
362             return;
363         }
364         // Also put the bridge back online
365         NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
366         if (nhcBridgeHandler != null) {
367             nhcBridgeHandler.bridgeOnline();
368         } else {
369             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
370                     "@text/offline.configuration-error.invalid-bridge-handler");
371         }
372     }
373
374     private @Nullable NikoHomeControlCommunication getCommunication(
375             @Nullable NikoHomeControlBridgeHandler nhcBridgeHandler) {
376         return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
377     }
378
379     private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
380         Bridge nhcBridge = getBridge();
381         return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
382     }
383
384     @Override
385     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
386         ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
387         if (ThingStatus.ONLINE.equals(bridgeStatus)) {
388             if (!initialized) {
389                 scheduler.submit(this::startCommunication);
390             } else {
391                 updateStatus(ThingStatus.ONLINE);
392             }
393         } else {
394             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
395         }
396     }
397 }