2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bondhome.internal.handler;
15 import static org.openhab.binding.bondhome.internal.BondHomeBindingConstants.*;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.List;
21 import java.util.Objects;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.bondhome.internal.BondException;
29 import org.openhab.binding.bondhome.internal.api.BondDevice;
30 import org.openhab.binding.bondhome.internal.api.BondDeviceAction;
31 import org.openhab.binding.bondhome.internal.api.BondDeviceProperties;
32 import org.openhab.binding.bondhome.internal.api.BondDeviceState;
33 import org.openhab.binding.bondhome.internal.api.BondDeviceType;
34 import org.openhab.binding.bondhome.internal.api.BondHttpApi;
35 import org.openhab.binding.bondhome.internal.config.BondDeviceConfiguration;
36 import org.openhab.core.config.core.Configuration;
37 import org.openhab.core.library.types.DecimalType;
38 import org.openhab.core.library.types.IncreaseDecreaseType;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.library.types.PercentType;
41 import org.openhab.core.library.types.StopMoveType;
42 import org.openhab.core.library.types.StringType;
43 import org.openhab.core.library.types.UpDownType;
44 import org.openhab.core.thing.Bridge;
45 import org.openhab.core.thing.Channel;
46 import org.openhab.core.thing.ChannelUID;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingStatus;
49 import org.openhab.core.thing.ThingStatusDetail;
50 import org.openhab.core.thing.ThingStatusInfo;
51 import org.openhab.core.thing.binding.BaseThingHandler;
52 import org.openhab.core.thing.binding.builder.ThingBuilder;
53 import org.openhab.core.types.Command;
54 import org.openhab.core.types.RefreshType;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 * The {@link BondDeviceHandler} is responsible for handling commands, which are
60 * sent to one of the channels.
62 * @author Sara Geleskie Damiano - Initial contribution
63 * @author Cody Cutrer - Significant rework on channels to more closely match openHAB's model.
66 public class BondDeviceHandler extends BaseThingHandler {
67 private final Logger logger = LoggerFactory.getLogger(BondDeviceHandler.class);
69 private @NonNullByDefault({}) BondDeviceConfiguration config;
70 private @Nullable BondHttpApi api;
72 private @Nullable BondDevice deviceInfo;
73 private @Nullable BondDeviceProperties deviceProperties;
74 private @Nullable BondDeviceState deviceState;
76 private @Nullable ScheduledFuture<?> pollingJob;
78 private volatile boolean disposed;
79 private volatile boolean fullyInitialized;
81 public BondDeviceHandler(Thing thing) {
84 fullyInitialized = false;
88 public void handleCommand(ChannelUID channelUID, Command command) {
89 if (hasConfigurationError() || !fullyInitialized) {
91 "Bond device handler for {} received command {} on channel {} but is not yet prepared to handle it.",
92 config.deviceId, command, channelUID);
95 String deviceId = Objects.requireNonNull(config.deviceId);
97 logger.trace("Bond device handler for {} received command {} on channel {}", config.deviceId, command,
99 final BondHttpApi api = this.api;
101 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.comm-error.no-api");
102 // Re-attempt initialization
103 scheduler.schedule(() -> {
104 logger.trace("Re-attempting initialization");
106 }, 30, TimeUnit.SECONDS);
110 if (command instanceof RefreshType) {
111 logger.trace("Executing refresh command");
113 deviceState = api.getDeviceState(deviceId);
114 updateChannelsFromState(deviceState);
115 } catch (BondException e) {
116 if (!e.wasBridgeSetOffline()) {
117 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
123 BondDeviceAction action = null;
125 Integer value = null;
126 final BondDevice devInfo = Objects.requireNonNull(this.deviceInfo);
127 switch (channelUID.getId()) {
129 logger.trace("Power state command");
130 api.executeDeviceAction(deviceId,
131 command == OnOffType.ON ? BondDeviceAction.TURN_ON : BondDeviceAction.TURN_OFF, null);
134 case CHANNEL_COMMAND:
135 logger.trace("{} command", command.toString());
137 action = BondDeviceAction.valueOf(command.toString());
138 } catch (IllegalArgumentException e) {
139 logger.warn("Received unknown command {}.", command);
143 if (devInfo.actions.contains(action)) {
144 api.executeDeviceAction(deviceId, action, null);
146 logger.warn("Device {} does not support command {}.", config.deviceId, command);
150 case CHANNEL_FAN_POWER:
151 logger.trace("Fan power state command");
152 api.executeDeviceAction(deviceId,
153 command == OnOffType.ON ? BondDeviceAction.TURN_FP_FAN_ON : BondDeviceAction.TURN_FP_FAN_OFF,
157 case CHANNEL_FAN_SPEED:
158 logger.trace("Fan speed command");
159 if (command instanceof PercentType) {
160 if (devInfo.actions.contains(BondDeviceAction.SET_FP_FAN)) {
161 value = ((PercentType) command).intValue();
163 action = BondDeviceAction.TURN_FP_FAN_OFF;
166 action = BondDeviceAction.SET_FP_FAN;
169 BondDeviceProperties devProperties = this.deviceProperties;
170 if (devProperties != null) {
171 int maxSpeed = devProperties.maxSpeed;
172 value = (int) Math.ceil(((PercentType) command).intValue() * maxSpeed / 100);
177 action = BondDeviceAction.TURN_OFF;
180 action = BondDeviceAction.SET_SPEED;
183 logger.trace("Fan speed command with speed set as {}", value);
184 api.executeDeviceAction(deviceId, action, value);
185 } else if (command instanceof IncreaseDecreaseType) {
186 logger.trace("Fan increase/decrease speed command");
187 api.executeDeviceAction(deviceId,
188 ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
189 ? BondDeviceAction.INCREASE_SPEED
190 : BondDeviceAction.DECREASE_SPEED),
192 } else if (command instanceof OnOffType) {
193 logger.trace("Fan speed command {}", command);
194 if (devInfo.actions.contains(BondDeviceAction.TURN_FP_FAN_ON)) {
195 action = command == OnOffType.ON ? BondDeviceAction.TURN_FP_FAN_ON
196 : BondDeviceAction.TURN_FP_FAN_OFF;
197 } else if (devInfo.actions.contains(BondDeviceAction.TURN_ON)) {
198 action = command == OnOffType.ON ? BondDeviceAction.TURN_ON : BondDeviceAction.TURN_OFF;
200 if (action != null) {
201 api.executeDeviceAction(deviceId, action, null);
204 logger.info("Unsupported command on fan speed channel");
208 case CHANNEL_FAN_BREEZE_STATE:
209 logger.trace("Fan enable/disable breeze command");
210 api.executeDeviceAction(deviceId,
211 command == OnOffType.ON ? BondDeviceAction.BREEZE_ON : BondDeviceAction.BREEZE_OFF, null);
214 case CHANNEL_FAN_BREEZE_MEAN:
215 // TODO(SRGDamia1): write array command fxn
216 logger.trace("Support for fan breeze settings not yet available");
219 case CHANNEL_FAN_BREEZE_VAR:
220 // TODO(SRGDamia1): write array command fxn
221 logger.trace("Support for fan breeze settings not yet available");
224 case CHANNEL_FAN_DIRECTION:
225 logger.trace("Fan direction command {}", command.toString());
226 if (command instanceof StringType) {
227 api.executeDeviceAction(deviceId, BondDeviceAction.SET_DIRECTION,
228 command.toString().equals("winter") ? -1 : 1);
232 case CHANNEL_LIGHT_POWER:
233 logger.trace("Fan light state command");
234 api.executeDeviceAction(deviceId,
235 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
239 case CHANNEL_LIGHT_BRIGHTNESS:
240 if (command instanceof PercentType) {
241 PercentType pctCommand = (PercentType) command;
242 value = pctCommand.intValue();
244 action = BondDeviceAction.TURN_LIGHT_OFF;
247 action = BondDeviceAction.SET_BRIGHTNESS;
249 logger.trace("Fan light brightness command with value of {}", value);
250 api.executeDeviceAction(deviceId, action, value);
251 } else if (command instanceof IncreaseDecreaseType) {
252 logger.trace("Fan light brightness increase/decrease command {}", command);
253 api.executeDeviceAction(deviceId,
254 ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
255 ? BondDeviceAction.INCREASE_BRIGHTNESS
256 : BondDeviceAction.DECREASE_BRIGHTNESS),
258 } else if (command instanceof OnOffType) {
259 logger.trace("Fan light brightness command {}", command);
260 api.executeDeviceAction(deviceId,
261 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
264 logger.info("Unsupported command on fan light brightness channel");
268 case CHANNEL_UP_LIGHT_ENABLE:
269 api.executeDeviceAction(deviceId, command == OnOffType.ON ? BondDeviceAction.TURN_UP_LIGHT_ON
270 : BondDeviceAction.TURN_UP_LIGHT_OFF, null);
273 case CHANNEL_UP_LIGHT_POWER:
274 // To turn on the up light, we first have to enable it and then turn on the lights
276 api.executeDeviceAction(deviceId,
277 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
281 case CHANNEL_UP_LIGHT_BRIGHTNESS:
283 if (command instanceof PercentType) {
284 PercentType pctCommand = (PercentType) command;
285 value = pctCommand.intValue();
287 action = BondDeviceAction.TURN_LIGHT_OFF;
290 action = BondDeviceAction.SET_UP_LIGHT_BRIGHTNESS;
292 logger.trace("Fan up light brightness command with value of {}", value);
293 api.executeDeviceAction(deviceId, action, value);
294 } else if (command instanceof IncreaseDecreaseType) {
295 logger.trace("Fan uplight brightness increase/decrease command {}", command);
296 api.executeDeviceAction(deviceId,
297 ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
298 ? BondDeviceAction.INCREASE_UP_LIGHT_BRIGHTNESS
299 : BondDeviceAction.DECREASE_UP_LIGHT_BRIGHTNESS),
301 } else if (command instanceof OnOffType) {
302 logger.trace("Fan up light brightness command {}", command);
303 api.executeDeviceAction(deviceId,
304 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
307 logger.info("Unsupported command on fan up light brightness channel");
311 case CHANNEL_DOWN_LIGHT_ENABLE:
312 api.executeDeviceAction(deviceId, command == OnOffType.ON ? BondDeviceAction.TURN_DOWN_LIGHT_ON
313 : BondDeviceAction.TURN_DOWN_LIGHT_OFF, null);
316 case CHANNEL_DOWN_LIGHT_POWER:
317 // To turn on the down light, we first have to enable it and then turn on the lights
318 api.executeDeviceAction(deviceId, BondDeviceAction.TURN_DOWN_LIGHT_ON, null);
319 api.executeDeviceAction(deviceId,
320 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
324 case CHANNEL_DOWN_LIGHT_BRIGHTNESS:
326 if (command instanceof PercentType) {
327 PercentType pctCommand = (PercentType) command;
328 value = pctCommand.intValue();
330 action = BondDeviceAction.TURN_LIGHT_OFF;
333 action = BondDeviceAction.SET_DOWN_LIGHT_BRIGHTNESS;
335 logger.trace("Fan down light brightness command with value of {}", value);
336 api.executeDeviceAction(deviceId, action, value);
337 } else if (command instanceof IncreaseDecreaseType) {
338 logger.trace("Fan down light brightness increase/decrease command");
339 api.executeDeviceAction(deviceId,
340 ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
341 ? BondDeviceAction.INCREASE_DOWN_LIGHT_BRIGHTNESS
342 : BondDeviceAction.DECREASE_DOWN_LIGHT_BRIGHTNESS),
344 } else if (command instanceof OnOffType) {
345 logger.trace("Fan down light brightness command {}", command);
346 api.executeDeviceAction(deviceId,
347 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
350 logger.debug("Unsupported command on fan down light brightness channel");
355 if (command instanceof PercentType) {
356 PercentType pctCommand = (PercentType) command;
357 value = pctCommand.intValue();
359 action = BondDeviceAction.TURN_OFF;
362 action = BondDeviceAction.SET_FLAME;
364 logger.trace("Fireplace flame command with value of {}", value);
365 api.executeDeviceAction(deviceId, action, value);
366 } else if (command instanceof IncreaseDecreaseType) {
367 logger.trace("Fireplace flame increase/decrease command");
368 api.executeDeviceAction(deviceId,
369 ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
370 ? BondDeviceAction.INCREASE_FLAME
371 : BondDeviceAction.DECREASE_FLAME),
373 } else if (command instanceof OnOffType) {
374 api.executeDeviceAction(deviceId,
375 command == OnOffType.ON ? BondDeviceAction.TURN_ON : BondDeviceAction.TURN_OFF, null);
377 logger.info("Unsupported command on flame channel");
381 case CHANNEL_ROLLERSHUTTER:
382 logger.trace("Rollershutter command {}", command);
383 if (command.equals(PercentType.ZERO)) {
384 command = UpDownType.UP;
385 } else if (command.equals(PercentType.HUNDRED)) {
386 command = UpDownType.DOWN;
388 if (command == UpDownType.UP) {
389 action = BondDeviceAction.OPEN;
390 } else if (command == UpDownType.DOWN) {
391 action = BondDeviceAction.CLOSE;
392 } else if (command == StopMoveType.STOP) {
393 action = BondDeviceAction.HOLD;
395 if (action != null) {
396 api.executeDeviceAction(deviceId, action, null);
401 logger.info("Command {} on unknown channel {}, {}", command.toFullString(), channelUID.getId(),
402 channelUID.toString());
407 private void enableUpLight() {
408 Objects.requireNonNull(api).executeDeviceAction(Objects.requireNonNull(config.deviceId),
409 BondDeviceAction.TURN_UP_LIGHT_ON, null);
412 private void enableDownLight() {
413 Objects.requireNonNull(api).executeDeviceAction(Objects.requireNonNull(config.deviceId),
414 BondDeviceAction.TURN_DOWN_LIGHT_ON, null);
418 public void initialize() {
419 config = getConfigAs(BondDeviceConfiguration.class);
420 logger.trace("Starting initialization for Bond device with device id {}.", config.deviceId);
421 fullyInitialized = false;
424 // set the thing status to UNKNOWN temporarily
425 updateStatus(ThingStatus.UNKNOWN);
427 scheduler.execute(this::initializeThing);
431 public synchronized void dispose() {
432 logger.debug("Disposing thing handler for {}.", this.getThing().getUID());
433 // Mark handler as disposed as soon as possible to halt updates
435 fullyInitialized = false;
437 final ScheduledFuture<?> pollingJob = this.pollingJob;
438 if (pollingJob != null && !pollingJob.isCancelled()) {
439 pollingJob.cancel(true);
441 this.pollingJob = null;
444 private void initializeThing() {
445 String deviceId = config.deviceId;
447 if (deviceId == null) {
448 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
449 "@text/offline.conf-error.no-device-id");
453 if (!getBridgeAndAPI()) {
456 BondHttpApi api = this.api;
458 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.comm-error.no-api");
463 logger.trace("Getting device information for {} ({})", config.deviceId, this.getThing().getLabel());
464 deviceInfo = api.getDevice(deviceId);
465 logger.trace("Getting device properties for {} ({})", config.deviceId, this.getThing().getLabel());
466 deviceProperties = api.getDeviceProperties(deviceId);
467 } catch (BondException e) {
468 if (!e.wasBridgeSetOffline()) {
469 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
474 final BondDevice devInfo = this.deviceInfo;
475 final BondDeviceProperties devProperties = this.deviceProperties;
476 BondDeviceType devType;
478 if (devInfo == null || devProperties == null || (devType = devInfo.type) == null
479 || (devHash = devInfo.hash) == null) {
480 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
481 "@text/offline.conf-error.no-device-properties");
485 // Anytime the configuration has changed or the binding has been updated,
486 // recreate the thing to make sure all possible channels are available
487 // NOTE: This will cause the thing to be disposed and re-initialized
488 if (wasThingUpdatedExternally(devInfo)) {
489 recreateAllChannels(devType, devHash);
493 updateDevicePropertiesFromBond(devInfo, devProperties);
495 deleteExtraChannels(devInfo.actions);
500 updateStatus(ThingStatus.ONLINE);
501 fullyInitialized = true;
502 logger.debug("Finished initializing device!");
505 private void updateProperty(Map<String, String> thingProperties, String key, @Nullable String value) {
509 thingProperties.put(key, value);
512 private void updateDevicePropertiesFromBond(BondDevice devInfo, BondDeviceProperties devProperties) {
513 // Update all the thing properties based on the result
514 Map<String, String> thingProperties = new HashMap<String, String>();
515 updateProperty(thingProperties, CONFIG_DEVICE_ID, config.deviceId);
516 logger.trace("Updating device name to {}", devInfo.name);
517 updateProperty(thingProperties, PROPERTIES_DEVICE_NAME, devInfo.name);
518 logger.trace("Updating other device properties for {} ({})", config.deviceId, this.getThing().getLabel());
519 updateProperty(thingProperties, PROPERTIES_TEMPLATE_NAME, devInfo.template);
520 thingProperties.put(PROPERTIES_MAX_SPEED, String.valueOf(devProperties.maxSpeed));
521 thingProperties.put(PROPERTIES_TRUST_STATE, String.valueOf(devProperties.trustState));
522 thingProperties.put(PROPERTIES_ADDRESS, String.valueOf(devProperties.addr));
523 thingProperties.put(PROPERTIES_RF_FREQUENCY, String.valueOf(devProperties.freq));
524 logger.trace("Saving properties for {} ({})", config.deviceId, this.getThing().getLabel());
525 updateProperties(thingProperties);
528 private synchronized void recreateAllChannels(BondDeviceType currentType, String currentHash) {
529 if (hasConfigurationError()) {
530 logger.trace("Don't recreate channels, I've been disposed!");
534 logger.debug("Recreating all possible channels for a {} for {} ({})",
535 currentType.getThingTypeUID().getAsString(), config.deviceId, this.getThing().getLabel());
537 // Create a new configuration
538 final Map<String, Object> map = new HashMap<>();
539 map.put(CONFIG_DEVICE_ID, Objects.requireNonNull(config.deviceId));
540 map.put(CONFIG_LATEST_HASH, currentHash);
541 Configuration newConfiguration = new Configuration(map);
543 // Change the thing type back to itself to force all channels to be re-created from XML
544 changeThingType(currentType.getThingTypeUID(), newConfiguration);
547 private synchronized void deleteExtraChannels(List<BondDeviceAction> currentActions) {
548 logger.trace("Deleting channels based on the available actions");
549 // Get the thing to edit
550 ThingBuilder thingBuilder = editThing();
552 // Now, look at the whole list of possible channels
553 List<Channel> possibleChannels = this.getThing().getChannels();
554 Set<String> availableChannelIds = new HashSet<>();
556 for (BondDeviceAction action : currentActions) {
557 String actionType = action.getChannelTypeId();
558 if (actionType != null) {
559 availableChannelIds.add(actionType);
560 logger.trace(" Action: {}, Relevant Channel Type Id: {}", action.getActionId(), actionType);
563 // Remove power channels if we have a dimmer channel for them;
564 // the dimmer channel already covers the power case
565 if (availableChannelIds.contains(CHANNEL_FAN_SPEED)) {
566 availableChannelIds.remove(CHANNEL_POWER);
567 availableChannelIds.remove(CHANNEL_FAN_POWER);
569 if (availableChannelIds.contains(CHANNEL_LIGHT_BRIGHTNESS)) {
570 availableChannelIds.remove(CHANNEL_LIGHT_POWER);
572 if (availableChannelIds.contains(CHANNEL_UP_LIGHT_BRIGHTNESS)) {
573 availableChannelIds.remove(CHANNEL_UP_LIGHT_POWER);
575 if (availableChannelIds.contains(CHANNEL_DOWN_LIGHT_BRIGHTNESS)) {
576 availableChannelIds.remove(CHANNEL_DOWN_LIGHT_POWER);
578 if (availableChannelIds.contains(CHANNEL_FLAME)) {
579 availableChannelIds.remove(CHANNEL_POWER);
582 for (Channel channel : possibleChannels) {
583 if (availableChannelIds.contains(channel.getUID().getId())) {
584 logger.trace(" ++++ Keeping: {}", channel.getUID().getId());
586 thingBuilder.withoutChannel(channel.getUID());
587 logger.trace(" ---- Dropping: {}", channel.getUID().getId());
591 // Add all the channels
592 logger.trace("Saving the thing with extra channels removed");
593 updateThing(thingBuilder.build());
596 public String getDeviceId() {
597 String deviceId = config.deviceId;
598 return deviceId == null ? "" : deviceId;
601 public synchronized void updateChannelsFromState(@Nullable BondDeviceState updateState) {
602 if (hasConfigurationError()) {
606 if (updateState == null) {
607 logger.debug("No state information provided to update channels with");
611 logger.debug("Updating channels from state for {} ({})", config.deviceId, this.getThing().getLabel());
613 updateStatus(ThingStatus.ONLINE);
615 updateState(CHANNEL_POWER, updateState.power == 0 ? OnOffType.OFF : OnOffType.ON);
617 final BondDevice devInfo = this.deviceInfo;
618 if (devInfo != null && devInfo.actions.contains(BondDeviceAction.TURN_FP_FAN_OFF)) {
619 fanOn = updateState.fpfanPower != 0;
620 updateState(CHANNEL_FAN_POWER, fanOn ? OnOffType.OFF : OnOffType.ON);
621 updateState(CHANNEL_FAN_SPEED, new PercentType(updateState.fpfanSpeed));
623 fanOn = updateState.power != 0;
625 BondDeviceProperties devProperties = this.deviceProperties;
626 if (devProperties != null) {
627 double maxSpeed = devProperties.maxSpeed;
628 value = (int) (((double) updateState.speed / maxSpeed) * 100);
629 logger.trace("Raw fan speed: {}, Percent: {}", updateState.speed, value);
630 } else if (updateState.speed != 0 && this.getThing().getThingTypeUID().equals(THING_TYPE_BOND_FAN)) {
631 logger.info("Unable to convert fan speed to a percent for {}!", this.getThing().getLabel());
633 updateState(CHANNEL_FAN_SPEED, formPercentType(fanOn, value));
635 updateState(CHANNEL_FAN_BREEZE_STATE, updateState.breeze[0] == 0 ? OnOffType.OFF : OnOffType.ON);
636 updateState(CHANNEL_FAN_BREEZE_MEAN, new PercentType(updateState.breeze[1]));
637 updateState(CHANNEL_FAN_BREEZE_VAR, new PercentType(updateState.breeze[2]));
638 updateState(CHANNEL_FAN_DIRECTION,
639 updateState.direction == 1 ? new StringType("summer") : new StringType("winter"));
640 updateState(CHANNEL_FAN_TIMER, new DecimalType(updateState.timer));
642 updateState(CHANNEL_LIGHT_POWER, updateState.light == 0 ? OnOffType.OFF : OnOffType.ON);
643 updateState(CHANNEL_LIGHT_BRIGHTNESS, formPercentType(updateState.light != 0, updateState.brightness));
645 updateState(CHANNEL_UP_LIGHT_ENABLE, updateState.upLight == 0 ? OnOffType.OFF : OnOffType.ON);
646 updateState(CHANNEL_UP_LIGHT_POWER,
647 (updateState.upLight == 1 && updateState.light == 1) ? OnOffType.ON : OnOffType.OFF);
648 updateState(CHANNEL_UP_LIGHT_BRIGHTNESS,
649 formPercentType((updateState.upLight == 1 && updateState.light == 1), updateState.upLightBrightness));
651 updateState(CHANNEL_DOWN_LIGHT_ENABLE, updateState.downLight == 0 ? OnOffType.OFF : OnOffType.ON);
652 updateState(CHANNEL_DOWN_LIGHT_POWER,
653 (updateState.downLight == 1 && updateState.light == 1) ? OnOffType.ON : OnOffType.OFF);
654 updateState(CHANNEL_DOWN_LIGHT_BRIGHTNESS, formPercentType(
655 (updateState.downLight == 1 && updateState.light == 1), updateState.downLightBrightness));
657 updateState(CHANNEL_FLAME, formPercentType(updateState.power != 0, updateState.flame));
659 updateState(CHANNEL_ROLLERSHUTTER, formPercentType(updateState.open != 0, 100));
662 private PercentType formPercentType(boolean isOn, int value) {
664 return PercentType.ZERO;
666 return new PercentType(value);
670 private boolean hasConfigurationError() {
671 final ThingStatusInfo statusInfo = getThing().getStatusInfo();
672 return statusInfo.getStatus() == ThingStatus.OFFLINE
673 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR || disposed
674 || config.deviceId == null;
677 private synchronized boolean wasThingUpdatedExternally(BondDevice devInfo) {
678 // Check if the Bond hash tree has changed
679 final String lastDeviceConfigurationHash = config.lastDeviceConfigurationHash;
680 boolean updatedHashTree = !devInfo.hash.equals(lastDeviceConfigurationHash);
681 if (updatedHashTree) {
682 logger.debug("Hash tree of device has been updated by Bond.");
683 logger.debug("Current state is {}, prior state was {}.", devInfo.hash, lastDeviceConfigurationHash);
685 return updatedHashTree;
688 private boolean getBridgeAndAPI() {
689 Bridge myBridge = this.getBridge();
690 if (myBridge == null) {
691 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
692 "@text/offline.conf-error.no-bridge");
696 BondBridgeHandler myBridgeHandler = (BondBridgeHandler) myBridge.getHandler();
697 if (myBridgeHandler != null) {
698 this.api = myBridgeHandler.getBridgeAPI();
701 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
702 "@text/offline.conf-error.no-bridge");
708 // Start polling for state
709 private synchronized void startPollingJob() {
710 final ScheduledFuture<?> pollingJob = this.pollingJob;
711 if (pollingJob == null || pollingJob.isCancelled()) {
712 Runnable pollingCommand = () -> {
713 BondHttpApi api = this.api;
715 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
716 "@text/offline.comm-error.no-api");
720 String deviceId = Objects.requireNonNull(config.deviceId);
721 logger.trace("Polling for current state for {} ({})", deviceId, this.getThing().getLabel());
723 deviceState = api.getDeviceState(deviceId);
724 updateChannelsFromState(deviceState);
725 } catch (BondException e) {
726 if (!e.wasBridgeSetOffline()) {
727 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
731 this.pollingJob = scheduler.scheduleWithFixedDelay(pollingCommand, 60, 300, TimeUnit.SECONDS);
736 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
737 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
738 && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
739 if (!fullyInitialized) {
740 scheduler.execute(this::initializeThing);
742 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
743 // restart the polling job when the bridge goes back online
746 } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
747 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
748 // stop the polling job when the bridge goes offline
749 ScheduledFuture<?> pollingJob = this.pollingJob;
750 if (pollingJob != null) {
751 pollingJob.cancel(true);
752 this.pollingJob = null;