2 * Copyright (c) 2010-2023 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 percentCommand) {
160 if (devInfo.actions.contains(BondDeviceAction.SET_FP_FAN)) {
161 value = percentCommand.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(percentCommand.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 increaseDecreaseCommand) {
186 logger.trace("Fan increase/decrease speed command");
187 api.executeDeviceAction(deviceId,
188 (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? BondDeviceAction.INCREASE_SPEED
189 : BondDeviceAction.DECREASE_SPEED),
191 } else if (command instanceof OnOffType) {
192 logger.trace("Fan speed command {}", command);
193 if (devInfo.actions.contains(BondDeviceAction.TURN_FP_FAN_ON)) {
194 action = command == OnOffType.ON ? BondDeviceAction.TURN_FP_FAN_ON
195 : BondDeviceAction.TURN_FP_FAN_OFF;
196 } else if (devInfo.actions.contains(BondDeviceAction.TURN_ON)) {
197 action = command == OnOffType.ON ? BondDeviceAction.TURN_ON : BondDeviceAction.TURN_OFF;
199 if (action != null) {
200 api.executeDeviceAction(deviceId, action, null);
203 logger.info("Unsupported command on fan speed channel");
207 case CHANNEL_RAW_FAN_SPEED:
208 if (command instanceof DecimalType decimalCommand) {
209 value = decimalCommand.intValue();
210 BondDeviceProperties devProperties = this.deviceProperties;
211 if (devProperties != null) {
213 // Interpret any 0 or less value as a request to turn off
214 action = BondDeviceAction.TURN_OFF;
217 action = BondDeviceAction.SET_SPEED;
218 value = Math.min(value, devProperties.maxSpeed);
220 logger.trace("Fan raw speed command with speed set as {}, action as {}", value, action);
221 api.executeDeviceAction(deviceId, action, value);
224 logger.info("Unsupported command on raw fan speed channel");
228 case CHANNEL_FAN_BREEZE_STATE:
229 logger.trace("Fan enable/disable breeze command");
230 api.executeDeviceAction(deviceId,
231 command == OnOffType.ON ? BondDeviceAction.BREEZE_ON : BondDeviceAction.BREEZE_OFF, null);
234 case CHANNEL_FAN_BREEZE_MEAN:
235 // TODO(SRGDamia1): write array command fxn
236 logger.trace("Support for fan breeze settings not yet available");
239 case CHANNEL_FAN_BREEZE_VAR:
240 // TODO(SRGDamia1): write array command fxn
241 logger.trace("Support for fan breeze settings not yet available");
244 case CHANNEL_FAN_DIRECTION:
245 logger.trace("Fan direction command {}", command.toString());
246 if (command instanceof StringType) {
247 api.executeDeviceAction(deviceId, BondDeviceAction.SET_DIRECTION,
248 "winter".equals(command.toString()) ? -1 : 1);
252 case CHANNEL_LIGHT_POWER:
253 logger.trace("Fan light state command");
254 api.executeDeviceAction(deviceId,
255 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
259 case CHANNEL_LIGHT_BRIGHTNESS:
260 if (command instanceof PercentType percentCommand) {
261 value = percentCommand.intValue();
263 action = BondDeviceAction.TURN_LIGHT_OFF;
266 action = BondDeviceAction.SET_BRIGHTNESS;
268 logger.trace("Fan light brightness command with value of {}", value);
269 api.executeDeviceAction(deviceId, action, value);
270 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
271 logger.trace("Fan light brightness increase/decrease command {}", command);
272 api.executeDeviceAction(deviceId,
273 (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE
274 ? BondDeviceAction.INCREASE_BRIGHTNESS
275 : BondDeviceAction.DECREASE_BRIGHTNESS),
277 } else if (command instanceof OnOffType) {
278 logger.trace("Fan light brightness command {}", command);
279 api.executeDeviceAction(deviceId,
280 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
283 logger.info("Unsupported command on fan light brightness channel");
287 case CHANNEL_UP_LIGHT_ENABLE:
288 api.executeDeviceAction(deviceId, command == OnOffType.ON ? BondDeviceAction.TURN_UP_LIGHT_ON
289 : BondDeviceAction.TURN_UP_LIGHT_OFF, null);
292 case CHANNEL_UP_LIGHT_POWER:
293 // To turn on the up light, we first have to enable it and then turn on the lights
295 api.executeDeviceAction(deviceId,
296 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
300 case CHANNEL_UP_LIGHT_BRIGHTNESS:
302 if (command instanceof PercentType percentCommand) {
303 value = percentCommand.intValue();
305 action = BondDeviceAction.TURN_LIGHT_OFF;
308 action = BondDeviceAction.SET_UP_LIGHT_BRIGHTNESS;
310 logger.trace("Fan up light brightness command with value of {}", value);
311 api.executeDeviceAction(deviceId, action, value);
312 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
313 logger.trace("Fan uplight brightness increase/decrease command {}", command);
314 api.executeDeviceAction(deviceId,
315 (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE
316 ? BondDeviceAction.INCREASE_UP_LIGHT_BRIGHTNESS
317 : BondDeviceAction.DECREASE_UP_LIGHT_BRIGHTNESS),
319 } else if (command instanceof OnOffType) {
320 logger.trace("Fan up light brightness command {}", command);
321 api.executeDeviceAction(deviceId,
322 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
325 logger.info("Unsupported command on fan up light brightness channel");
329 case CHANNEL_DOWN_LIGHT_ENABLE:
330 api.executeDeviceAction(deviceId, command == OnOffType.ON ? BondDeviceAction.TURN_DOWN_LIGHT_ON
331 : BondDeviceAction.TURN_DOWN_LIGHT_OFF, null);
334 case CHANNEL_DOWN_LIGHT_POWER:
335 // To turn on the down light, we first have to enable it and then turn on the lights
336 api.executeDeviceAction(deviceId, BondDeviceAction.TURN_DOWN_LIGHT_ON, null);
337 api.executeDeviceAction(deviceId,
338 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
342 case CHANNEL_DOWN_LIGHT_BRIGHTNESS:
344 if (command instanceof PercentType percentCommand) {
345 value = percentCommand.intValue();
347 action = BondDeviceAction.TURN_LIGHT_OFF;
350 action = BondDeviceAction.SET_DOWN_LIGHT_BRIGHTNESS;
352 logger.trace("Fan down light brightness command with value of {}", value);
353 api.executeDeviceAction(deviceId, action, value);
354 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
355 logger.trace("Fan down light brightness increase/decrease command");
356 api.executeDeviceAction(deviceId,
357 (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE
358 ? BondDeviceAction.INCREASE_DOWN_LIGHT_BRIGHTNESS
359 : BondDeviceAction.DECREASE_DOWN_LIGHT_BRIGHTNESS),
361 } else if (command instanceof OnOffType) {
362 logger.trace("Fan down light brightness command {}", command);
363 api.executeDeviceAction(deviceId,
364 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
367 logger.debug("Unsupported command on fan down light brightness channel");
372 if (command instanceof PercentType percentCommand) {
373 value = percentCommand.intValue();
375 action = BondDeviceAction.TURN_OFF;
378 action = BondDeviceAction.SET_FLAME;
380 logger.trace("Fireplace flame command with value of {}", value);
381 api.executeDeviceAction(deviceId, action, value);
382 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
383 logger.trace("Fireplace flame increase/decrease command");
384 api.executeDeviceAction(deviceId,
385 (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? BondDeviceAction.INCREASE_FLAME
386 : BondDeviceAction.DECREASE_FLAME),
388 } else if (command instanceof OnOffType) {
389 api.executeDeviceAction(deviceId,
390 command == OnOffType.ON ? BondDeviceAction.TURN_ON : BondDeviceAction.TURN_OFF, null);
392 logger.info("Unsupported command on flame channel");
396 case CHANNEL_ROLLERSHUTTER:
397 logger.trace("Rollershutter command {}", command);
398 if (command.equals(PercentType.ZERO)) {
399 command = UpDownType.UP;
400 } else if (command.equals(PercentType.HUNDRED)) {
401 command = UpDownType.DOWN;
403 if (command == UpDownType.UP) {
404 action = BondDeviceAction.OPEN;
405 } else if (command == UpDownType.DOWN) {
406 action = BondDeviceAction.CLOSE;
407 } else if (command == StopMoveType.STOP) {
408 action = BondDeviceAction.HOLD;
410 if (action != null) {
411 api.executeDeviceAction(deviceId, action, null);
416 logger.info("Command {} on unknown channel {}, {}", command.toFullString(), channelUID.getId(),
417 channelUID.toString());
422 private void enableUpLight() {
423 Objects.requireNonNull(api).executeDeviceAction(Objects.requireNonNull(config.deviceId),
424 BondDeviceAction.TURN_UP_LIGHT_ON, null);
427 private void enableDownLight() {
428 Objects.requireNonNull(api).executeDeviceAction(Objects.requireNonNull(config.deviceId),
429 BondDeviceAction.TURN_DOWN_LIGHT_ON, null);
433 public void initialize() {
434 config = getConfigAs(BondDeviceConfiguration.class);
435 logger.trace("Starting initialization for Bond device with device id {}.", config.deviceId);
436 fullyInitialized = false;
439 // set the thing status to UNKNOWN temporarily
440 updateStatus(ThingStatus.UNKNOWN);
442 scheduler.execute(this::initializeThing);
446 public synchronized void dispose() {
447 logger.debug("Disposing thing handler for {}.", this.getThing().getUID());
448 // Mark handler as disposed as soon as possible to halt updates
450 fullyInitialized = false;
452 final ScheduledFuture<?> pollingJob = this.pollingJob;
453 if (pollingJob != null && !pollingJob.isCancelled()) {
454 pollingJob.cancel(true);
456 this.pollingJob = null;
459 private void initializeThing() {
460 String deviceId = config.deviceId;
462 if (deviceId == null) {
463 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
464 "@text/offline.conf-error.no-device-id");
468 if (!getBridgeAndAPI()) {
471 BondHttpApi api = this.api;
473 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.comm-error.no-api");
478 logger.trace("Getting device information for {} ({})", config.deviceId, this.getThing().getLabel());
479 deviceInfo = api.getDevice(deviceId);
480 logger.trace("Getting device properties for {} ({})", config.deviceId, this.getThing().getLabel());
481 deviceProperties = api.getDeviceProperties(deviceId);
482 } catch (BondException e) {
483 if (!e.wasBridgeSetOffline()) {
484 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
489 final BondDevice devInfo = this.deviceInfo;
490 final BondDeviceProperties devProperties = this.deviceProperties;
491 BondDeviceType devType;
493 if (devInfo == null || devProperties == null || (devType = devInfo.type) == null
494 || (devHash = devInfo.hash) == null) {
495 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
496 "@text/offline.conf-error.no-device-properties");
500 // Anytime the configuration has changed or the binding has been updated,
501 // recreate the thing to make sure all possible channels are available
502 // NOTE: This will cause the thing to be disposed and re-initialized
503 if (wasThingUpdatedExternally(devInfo)) {
504 recreateAllChannels(devType, devHash);
508 updateDevicePropertiesFromBond(devInfo, devProperties);
510 deleteExtraChannels(devInfo.actions);
515 updateStatus(ThingStatus.ONLINE);
516 fullyInitialized = true;
517 logger.debug("Finished initializing device!");
520 private void updateProperty(Map<String, String> thingProperties, String key, @Nullable String value) {
524 thingProperties.put(key, value);
527 private void updateDevicePropertiesFromBond(BondDevice devInfo, BondDeviceProperties devProperties) {
528 // Update all the thing properties based on the result
529 Map<String, String> thingProperties = new HashMap<String, String>();
530 updateProperty(thingProperties, CONFIG_DEVICE_ID, config.deviceId);
531 logger.trace("Updating device name to {}", devInfo.name);
532 updateProperty(thingProperties, PROPERTIES_DEVICE_NAME, devInfo.name);
533 logger.trace("Updating other device properties for {} ({})", config.deviceId, this.getThing().getLabel());
534 updateProperty(thingProperties, PROPERTIES_TEMPLATE_NAME, devInfo.template);
535 thingProperties.put(PROPERTIES_MAX_SPEED, String.valueOf(devProperties.maxSpeed));
536 thingProperties.put(PROPERTIES_TRUST_STATE, String.valueOf(devProperties.trustState));
537 thingProperties.put(PROPERTIES_ADDRESS, devProperties.addr);
538 thingProperties.put(PROPERTIES_RF_FREQUENCY, String.valueOf(devProperties.freq));
539 logger.trace("Saving properties for {} ({})", config.deviceId, this.getThing().getLabel());
540 updateProperties(thingProperties);
543 private synchronized void recreateAllChannels(BondDeviceType currentType, String currentHash) {
544 if (hasConfigurationError()) {
545 logger.trace("Don't recreate channels, I've been disposed!");
549 logger.debug("Recreating all possible channels for a {} for {} ({})",
550 currentType.getThingTypeUID().getAsString(), config.deviceId, this.getThing().getLabel());
552 // Create a new configuration
553 final Map<String, Object> map = new HashMap<>();
554 map.put(CONFIG_DEVICE_ID, Objects.requireNonNull(config.deviceId));
555 map.put(CONFIG_LATEST_HASH, currentHash);
556 Configuration newConfiguration = new Configuration(map);
558 // Change the thing type back to itself to force all channels to be re-created from XML
559 changeThingType(currentType.getThingTypeUID(), newConfiguration);
562 private synchronized void deleteExtraChannels(List<BondDeviceAction> currentActions) {
563 logger.trace("Deleting channels based on the available actions");
564 // Get the thing to edit
565 ThingBuilder thingBuilder = editThing();
566 final BondDevice devInfo = this.deviceInfo;
568 // Now, look at the whole list of possible channels
569 List<Channel> possibleChannels = this.getThing().getChannels();
570 Set<String> availableChannelIds = new HashSet<>();
572 for (BondDeviceAction action : currentActions) {
573 String actionType = action.getChannelTypeId();
574 if (actionType != null) {
575 availableChannelIds.add(actionType);
576 logger.trace(" Action: {}, Relevant Channel Type Id: {}", action.getActionId(), actionType);
579 // Remove power channels if we have a dimmer channel for them;
580 // the dimmer channel already covers the power case.
581 // Add the raw channel for advanced users if we're a ceiling fan.
582 if (availableChannelIds.contains(CHANNEL_FAN_SPEED)) {
583 availableChannelIds.remove(CHANNEL_POWER);
584 availableChannelIds.remove(CHANNEL_FAN_POWER);
585 if (devInfo != null && devInfo.type == BondDeviceType.CEILING_FAN) {
586 availableChannelIds.add(CHANNEL_RAW_FAN_SPEED);
589 if (availableChannelIds.contains(CHANNEL_LIGHT_BRIGHTNESS)) {
590 availableChannelIds.remove(CHANNEL_LIGHT_POWER);
592 if (availableChannelIds.contains(CHANNEL_UP_LIGHT_BRIGHTNESS)) {
593 availableChannelIds.remove(CHANNEL_UP_LIGHT_POWER);
595 if (availableChannelIds.contains(CHANNEL_DOWN_LIGHT_BRIGHTNESS)) {
596 availableChannelIds.remove(CHANNEL_DOWN_LIGHT_POWER);
598 if (availableChannelIds.contains(CHANNEL_FLAME)) {
599 availableChannelIds.remove(CHANNEL_POWER);
602 for (Channel channel : possibleChannels) {
603 if (availableChannelIds.contains(channel.getUID().getId())) {
604 logger.trace(" ++++ Keeping: {}", channel.getUID().getId());
606 thingBuilder.withoutChannel(channel.getUID());
607 logger.trace(" ---- Dropping: {}", channel.getUID().getId());
611 // Add all the channels
612 logger.trace("Saving the thing with extra channels removed");
613 updateThing(thingBuilder.build());
616 public String getDeviceId() {
617 String deviceId = config.deviceId;
618 return deviceId == null ? "" : deviceId;
621 public synchronized void updateChannelsFromState(@Nullable BondDeviceState updateState) {
622 if (hasConfigurationError()) {
626 if (updateState == null) {
627 logger.debug("No state information provided to update channels with");
631 logger.debug("Updating channels from state for {} ({})", config.deviceId, this.getThing().getLabel());
633 updateStatus(ThingStatus.ONLINE);
635 updateState(CHANNEL_POWER, OnOffType.from(updateState.power != 0));
637 final BondDevice devInfo = this.deviceInfo;
638 if (devInfo != null && devInfo.actions.contains(BondDeviceAction.TURN_FP_FAN_OFF)) {
639 fanOn = updateState.fpfanPower != 0;
640 updateState(CHANNEL_FAN_POWER, OnOffType.from(!fanOn));
641 updateState(CHANNEL_FAN_SPEED, new PercentType(updateState.fpfanSpeed));
643 fanOn = updateState.power != 0;
645 BondDeviceProperties devProperties = this.deviceProperties;
646 if (devProperties != null) {
647 double maxSpeed = devProperties.maxSpeed;
648 value = (int) (((double) updateState.speed / maxSpeed) * 100);
649 logger.trace("Raw fan speed: {}, Percent: {}", updateState.speed, value);
650 } else if (updateState.speed != 0 && this.getThing().getThingTypeUID().equals(THING_TYPE_BOND_FAN)) {
651 logger.info("Unable to convert fan speed to a percent for {}!", this.getThing().getLabel());
653 updateState(CHANNEL_FAN_SPEED, formPercentType(fanOn, value));
654 updateState(CHANNEL_RAW_FAN_SPEED, fanOn ? new DecimalType(updateState.speed) : DecimalType.ZERO);
656 updateState(CHANNEL_FAN_BREEZE_STATE, OnOffType.from(updateState.breeze[0] != 0));
657 updateState(CHANNEL_FAN_BREEZE_MEAN, new PercentType(updateState.breeze[1]));
658 updateState(CHANNEL_FAN_BREEZE_VAR, new PercentType(updateState.breeze[2]));
659 updateState(CHANNEL_FAN_DIRECTION,
660 updateState.direction == 1 ? new StringType("summer") : new StringType("winter"));
661 updateState(CHANNEL_FAN_TIMER, new DecimalType(updateState.timer));
663 updateState(CHANNEL_LIGHT_POWER, OnOffType.from(updateState.light != 0));
664 updateState(CHANNEL_LIGHT_BRIGHTNESS, formPercentType(updateState.light != 0, updateState.brightness));
666 updateState(CHANNEL_UP_LIGHT_ENABLE, OnOffType.from(updateState.upLight != 0));
667 updateState(CHANNEL_UP_LIGHT_POWER, OnOffType.from(updateState.upLight == 1 && updateState.light == 1));
668 updateState(CHANNEL_UP_LIGHT_BRIGHTNESS,
669 formPercentType((updateState.upLight == 1 && updateState.light == 1), updateState.upLightBrightness));
671 updateState(CHANNEL_DOWN_LIGHT_ENABLE, OnOffType.from(updateState.downLight != 0));
672 updateState(CHANNEL_DOWN_LIGHT_POWER, OnOffType.from(updateState.downLight == 1 && updateState.light == 1));
673 updateState(CHANNEL_DOWN_LIGHT_BRIGHTNESS, formPercentType(
674 (updateState.downLight == 1 && updateState.light == 1), updateState.downLightBrightness));
676 updateState(CHANNEL_FLAME, formPercentType(updateState.power != 0, updateState.flame));
678 updateState(CHANNEL_ROLLERSHUTTER, formPercentType(updateState.open != 0, 100));
681 private PercentType formPercentType(boolean isOn, int value) {
683 return PercentType.ZERO;
685 return new PercentType(value);
689 private boolean hasConfigurationError() {
690 final ThingStatusInfo statusInfo = getThing().getStatusInfo();
691 return statusInfo.getStatus() == ThingStatus.OFFLINE
692 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR || disposed
693 || config.deviceId == null;
696 private synchronized boolean wasThingUpdatedExternally(BondDevice devInfo) {
697 // Check if the Bond hash tree has changed
698 final String lastDeviceConfigurationHash = config.lastDeviceConfigurationHash;
699 boolean updatedHashTree = !devInfo.hash.equals(lastDeviceConfigurationHash);
700 if (updatedHashTree) {
701 logger.debug("Hash tree of device has been updated by Bond.");
702 logger.debug("Current state is {}, prior state was {}.", devInfo.hash, lastDeviceConfigurationHash);
704 return updatedHashTree;
707 private boolean getBridgeAndAPI() {
708 Bridge myBridge = this.getBridge();
709 if (myBridge == null) {
710 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
711 "@text/offline.conf-error.no-bridge");
715 BondBridgeHandler myBridgeHandler = (BondBridgeHandler) myBridge.getHandler();
716 if (myBridgeHandler != null) {
717 this.api = myBridgeHandler.getBridgeAPI();
720 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
721 "@text/offline.conf-error.no-bridge");
727 // Start polling for state
728 private synchronized void startPollingJob() {
729 final ScheduledFuture<?> pollingJob = this.pollingJob;
730 if (pollingJob == null || pollingJob.isCancelled()) {
731 Runnable pollingCommand = () -> {
732 BondHttpApi api = this.api;
734 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
735 "@text/offline.comm-error.no-api");
739 String deviceId = Objects.requireNonNull(config.deviceId);
740 logger.trace("Polling for current state for {} ({})", deviceId, this.getThing().getLabel());
742 deviceState = api.getDeviceState(deviceId);
743 updateChannelsFromState(deviceState);
744 } catch (BondException e) {
745 if (!e.wasBridgeSetOffline()) {
746 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
750 this.pollingJob = scheduler.scheduleWithFixedDelay(pollingCommand, 60, 300, TimeUnit.SECONDS);
755 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
756 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
757 && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
758 if (!fullyInitialized) {
759 scheduler.execute(this::initializeThing);
761 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
762 // restart the polling job when the bridge goes back online
765 } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
766 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
767 // stop the polling job when the bridge goes offline
768 ScheduledFuture<?> pollingJob = this.pollingJob;
769 if (pollingJob != null) {
770 pollingJob.cancel(true);
771 this.pollingJob = null;