]> git.basschouten.com Git - openhab-addons.git/blob
e5d456b4aaeba74655a348833cae417b8575c0c4
[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.hdpowerview.internal.handler;
14
15 import static org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants.*;
16
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
23 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewRepeaterConfiguration;
24 import org.openhab.binding.hdpowerview.internal.dto.Color;
25 import org.openhab.binding.hdpowerview.internal.dto.Firmware;
26 import org.openhab.binding.hdpowerview.internal.dto.responses.RepeaterData;
27 import org.openhab.binding.hdpowerview.internal.exceptions.HubException;
28 import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
29 import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
30 import org.openhab.core.library.types.HSBType;
31 import org.openhab.core.library.types.IncreaseDecreaseType;
32 import org.openhab.core.library.types.OnOffType;
33 import org.openhab.core.library.types.PercentType;
34 import org.openhab.core.library.types.StringType;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.openhab.core.types.UnDefType;
43 import org.openhab.core.util.ColorUtil;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * Handles commands for an HD PowerView Repeater
49  *
50  * @author Jacob Laursen - Initial contribution
51  */
52 @NonNullByDefault
53 public class HDPowerViewRepeaterHandler extends AbstractHubbedThingHandler {
54
55     private final Logger logger = LoggerFactory.getLogger(HDPowerViewRepeaterHandler.class);
56
57     private static final int REFRESH_INTERVAL_MINUTES = 5;
58     private static final int IDENTITY_PERIOD_SECONDS = 3;
59     private static final int BRIGHTNESS_STEP_PERCENT = 5;
60     private static final String COMMAND_IDENTIFY = "IDENTIFY";
61
62     private @Nullable ScheduledFuture<?> refreshStatusFuture = null;
63     private @Nullable ScheduledFuture<?> resetIdentifyStateFuture = null;
64     private int repeaterId;
65
66     public HDPowerViewRepeaterHandler(Thing thing) {
67         super(thing);
68     }
69
70     @Override
71     public void initialize() {
72         repeaterId = getConfigAs(HDPowerViewRepeaterConfiguration.class).id;
73         logger.debug("Initializing repeater handler for repeater {}", repeaterId);
74         Bridge bridge = getBridge();
75         if (bridge == null) {
76             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
77                     "@text/offline.conf-error.invalid-bridge-handler");
78             return;
79         }
80
81         updateStatus(ThingStatus.UNKNOWN);
82
83         scheduleRefreshJob();
84     }
85
86     @Override
87     public void dispose() {
88         logger.debug("Disposing repeater handler for repeater {}", repeaterId);
89         cancelRefreshJob();
90         cancelResetIdentifyStateJob();
91     }
92
93     @Override
94     public void handleCommand(ChannelUID channelUID, Command command) {
95         HDPowerViewHubHandler bridge = getBridgeHandler();
96         if (bridge == null) {
97             logger.warn("Missing bridge handler");
98             return;
99         }
100         if (command == RefreshType.REFRESH) {
101             scheduleRefreshJob();
102             return;
103         }
104         HDPowerViewWebTargets webTargets = bridge.getWebTargets();
105         try {
106             RepeaterData repeaterData;
107
108             switch (channelUID.getId()) {
109                 case CHANNEL_REPEATER_COLOR:
110                     handleColorCommand(command, webTargets);
111                     break;
112                 case CHANNEL_REPEATER_IDENTIFY:
113                     if (command instanceof StringType stringCommand) {
114                         if (COMMAND_IDENTIFY.equals(stringCommand.toString())) {
115                             repeaterData = webTargets.identifyRepeater(repeaterId);
116                             scheduler.submit(() -> updatePropertyAndStates(repeaterData));
117                             cancelResetIdentifyStateJob();
118                             resetIdentifyStateFuture = scheduler.schedule(() -> {
119                                 updateState(CHANNEL_REPEATER_IDENTIFY, UnDefType.UNDEF);
120                             }, IDENTITY_PERIOD_SECONDS, TimeUnit.SECONDS);
121                         } else {
122                             logger.warn("Unsupported command: {}. Supported commands are: " + COMMAND_IDENTIFY,
123                                     command);
124                         }
125                     }
126                     break;
127                 case CHANNEL_REPEATER_BLINKING_ENABLED:
128                     repeaterData = webTargets.enableRepeaterBlinking(repeaterId, OnOffType.ON == command);
129                     scheduler.submit(() -> updatePropertyAndStates(repeaterData));
130                     break;
131             }
132         } catch (HubInvalidResponseException e) {
133             Throwable cause = e.getCause();
134             if (cause == null) {
135                 logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
136             } else {
137                 logger.warn("Bridge returned a bad JSON response: {} -> {}", e.getMessage(), cause.getMessage());
138             }
139         } catch (HubMaintenanceException e) {
140             // exceptions are logged in HDPowerViewWebTargets
141         } catch (HubException e) {
142             logger.warn("Unexpected error: {}", e.getMessage());
143         }
144     }
145
146     private void handleColorCommand(Command command, HDPowerViewWebTargets webTargets) throws HubException {
147         if (command instanceof HSBType hsbCommand) {
148             var color = new Color(hsbCommand.getBrightness().intValue(), ColorUtil.hsbTosRgb(hsbCommand));
149             RepeaterData repeaterData = webTargets.setRepeaterColor(repeaterId, color);
150             scheduler.submit(() -> updatePropertyAndStates(repeaterData));
151             return;
152         }
153         Color currentColor = webTargets.getRepeater(repeaterId).color;
154         if (currentColor == null) {
155             return;
156         }
157         Color newColor;
158         if (command instanceof PercentType brightnessCommand) {
159             newColor = applyBrightnessToColor(currentColor, brightnessCommand.intValue());
160         } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
161             int brightness = switch (increaseDecreaseCommand) {
162                 case INCREASE -> currentColor.brightness + BRIGHTNESS_STEP_PERCENT;
163                 case DECREASE -> currentColor.brightness - BRIGHTNESS_STEP_PERCENT;
164             };
165             brightness = brightness < 0 ? 0 : brightness > 100 ? 100 : brightness;
166             newColor = applyBrightnessToColor(currentColor, brightness);
167         } else if (command instanceof OnOffType) {
168             // Light is turned off either by RGB black or zero brightness.
169             int brightness;
170             if (command == OnOffType.ON) {
171                 // Turn on with maximum brightness level per default,
172                 // if no existing brightness level is available.
173                 brightness = currentColor.brightness > 0 ? currentColor.brightness : 100;
174             } else {
175                 // Turn off by zero brightness to preserve color.
176                 brightness = 0;
177             }
178             newColor = applyBrightnessToColor(currentColor, brightness);
179         } else {
180             logger.warn("Unsupported command: {}", command);
181             return;
182         }
183         RepeaterData repeaterData = webTargets.setRepeaterColor(repeaterId, newColor);
184         scheduler.submit(() -> updatePropertyAndStates(repeaterData));
185     }
186
187     private Color applyBrightnessToColor(Color currentColor, int brightness) {
188         // If light is off by RGB black, then reset to white since otherwise brightness
189         // would have no effect; otherwise preserve color.
190         return currentColor.isBlack() ? new Color(brightness, java.awt.Color.WHITE)
191                 : new Color(brightness, currentColor);
192     }
193
194     private void cancelResetIdentifyStateJob() {
195         ScheduledFuture<?> scheduledJob = resetIdentifyStateFuture;
196         if (scheduledJob != null) {
197             scheduledJob.cancel(true);
198         }
199         resetIdentifyStateFuture = null;
200     }
201
202     private void scheduleRefreshJob() {
203         cancelRefreshJob();
204         logger.debug("Scheduling poll for repeater {} now, then every {} minutes", repeaterId,
205                 REFRESH_INTERVAL_MINUTES);
206         this.refreshStatusFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, REFRESH_INTERVAL_MINUTES,
207                 TimeUnit.MINUTES);
208     }
209
210     private void cancelRefreshJob() {
211         ScheduledFuture<?> future = this.refreshStatusFuture;
212         if (future != null) {
213             future.cancel(true);
214         }
215         this.refreshStatusFuture = null;
216     }
217
218     private synchronized void poll() {
219         HDPowerViewHubHandler bridge = getBridgeHandler();
220         if (bridge == null) {
221             logger.warn("Missing bridge handler");
222             return;
223         }
224         HDPowerViewWebTargets webTargets = bridge.getWebTargets();
225         try {
226             logger.debug("Polling for status information");
227
228             RepeaterData repeaterData = webTargets.getRepeater(repeaterId);
229             updatePropertyAndStates(repeaterData);
230
231         } catch (HubInvalidResponseException e) {
232             Throwable cause = e.getCause();
233             if (cause == null) {
234                 logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
235             } else {
236                 logger.warn("Bridge returned a bad JSON response: {} -> {}", e.getMessage(), cause.getMessage());
237             }
238         } catch (HubMaintenanceException e) {
239             // exceptions are logged in HDPowerViewWebTargets
240         } catch (HubException e) {
241             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, e.getMessage());
242         }
243     }
244
245     private void updatePropertyAndStates(RepeaterData repeaterData) {
246         updateStatus(ThingStatus.ONLINE);
247
248         Firmware firmware = repeaterData.firmware;
249         if (firmware != null) {
250             logger.debug("Repeater firmware version received: {}", firmware.toString());
251             updateProperty(Thing.PROPERTY_FIRMWARE_VERSION, firmware.toString());
252         } else {
253             logger.warn("Repeater firmware version missing in response");
254         }
255
256         Color color = repeaterData.color;
257         if (color != null) {
258             logger.debug("Repeater color data received: {}", color.toString());
259             HSBType hsb;
260             if (color.isBlack()) {
261                 // Light is off when RGB black, so discard brightness as otherwise it would appear on.
262                 hsb = HSBType.BLACK;
263             } else {
264                 hsb = HSBType.fromRGB(color.red, color.green, color.red);
265                 hsb = new HSBType(hsb.getHue(), hsb.getSaturation(), new PercentType(color.brightness));
266             }
267             updateState(CHANNEL_REPEATER_COLOR, hsb);
268         }
269
270         updateState(CHANNEL_REPEATER_BLINKING_ENABLED, OnOffType.from(repeaterData.blinkEnabled));
271     }
272 }