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) {
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_RAW_FAN_SPEED:
209 if (command instanceof DecimalType) {
210 value = ((DecimalType) command).intValue();
211 BondDeviceProperties devProperties = this.deviceProperties;
212 if (devProperties != null) {
214 // Interpret any 0 or less value as a request to turn off
215 action = BondDeviceAction.TURN_OFF;
218 action = BondDeviceAction.SET_SPEED;
219 value = Math.min(value, devProperties.maxSpeed);
221 logger.trace("Fan raw speed command with speed set as {}, action as {}", value, action);
222 api.executeDeviceAction(deviceId, action, value);
225 logger.info("Unsupported command on raw fan speed channel");
229 case CHANNEL_FAN_BREEZE_STATE:
230 logger.trace("Fan enable/disable breeze command");
231 api.executeDeviceAction(deviceId,
232 command == OnOffType.ON ? BondDeviceAction.BREEZE_ON : BondDeviceAction.BREEZE_OFF, null);
235 case CHANNEL_FAN_BREEZE_MEAN:
236 // TODO(SRGDamia1): write array command fxn
237 logger.trace("Support for fan breeze settings not yet available");
240 case CHANNEL_FAN_BREEZE_VAR:
241 // TODO(SRGDamia1): write array command fxn
242 logger.trace("Support for fan breeze settings not yet available");
245 case CHANNEL_FAN_DIRECTION:
246 logger.trace("Fan direction command {}", command.toString());
247 if (command instanceof StringType) {
248 api.executeDeviceAction(deviceId, BondDeviceAction.SET_DIRECTION,
249 command.toString().equals("winter") ? -1 : 1);
253 case CHANNEL_LIGHT_POWER:
254 logger.trace("Fan light state command");
255 api.executeDeviceAction(deviceId,
256 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
260 case CHANNEL_LIGHT_BRIGHTNESS:
261 if (command instanceof PercentType) {
262 PercentType pctCommand = (PercentType) command;
263 value = pctCommand.intValue();
265 action = BondDeviceAction.TURN_LIGHT_OFF;
268 action = BondDeviceAction.SET_BRIGHTNESS;
270 logger.trace("Fan light brightness command with value of {}", value);
271 api.executeDeviceAction(deviceId, action, value);
272 } else if (command instanceof IncreaseDecreaseType) {
273 logger.trace("Fan light brightness increase/decrease command {}", command);
274 api.executeDeviceAction(deviceId,
275 ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
276 ? BondDeviceAction.INCREASE_BRIGHTNESS
277 : BondDeviceAction.DECREASE_BRIGHTNESS),
279 } else if (command instanceof OnOffType) {
280 logger.trace("Fan light brightness command {}", command);
281 api.executeDeviceAction(deviceId,
282 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
285 logger.info("Unsupported command on fan light brightness channel");
289 case CHANNEL_UP_LIGHT_ENABLE:
290 api.executeDeviceAction(deviceId, command == OnOffType.ON ? BondDeviceAction.TURN_UP_LIGHT_ON
291 : BondDeviceAction.TURN_UP_LIGHT_OFF, null);
294 case CHANNEL_UP_LIGHT_POWER:
295 // To turn on the up light, we first have to enable it and then turn on the lights
297 api.executeDeviceAction(deviceId,
298 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
302 case CHANNEL_UP_LIGHT_BRIGHTNESS:
304 if (command instanceof PercentType) {
305 PercentType pctCommand = (PercentType) command;
306 value = pctCommand.intValue();
308 action = BondDeviceAction.TURN_LIGHT_OFF;
311 action = BondDeviceAction.SET_UP_LIGHT_BRIGHTNESS;
313 logger.trace("Fan up light brightness command with value of {}", value);
314 api.executeDeviceAction(deviceId, action, value);
315 } else if (command instanceof IncreaseDecreaseType) {
316 logger.trace("Fan uplight brightness increase/decrease command {}", command);
317 api.executeDeviceAction(deviceId,
318 ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
319 ? BondDeviceAction.INCREASE_UP_LIGHT_BRIGHTNESS
320 : BondDeviceAction.DECREASE_UP_LIGHT_BRIGHTNESS),
322 } else if (command instanceof OnOffType) {
323 logger.trace("Fan up light brightness command {}", command);
324 api.executeDeviceAction(deviceId,
325 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
328 logger.info("Unsupported command on fan up light brightness channel");
332 case CHANNEL_DOWN_LIGHT_ENABLE:
333 api.executeDeviceAction(deviceId, command == OnOffType.ON ? BondDeviceAction.TURN_DOWN_LIGHT_ON
334 : BondDeviceAction.TURN_DOWN_LIGHT_OFF, null);
337 case CHANNEL_DOWN_LIGHT_POWER:
338 // To turn on the down light, we first have to enable it and then turn on the lights
339 api.executeDeviceAction(deviceId, BondDeviceAction.TURN_DOWN_LIGHT_ON, null);
340 api.executeDeviceAction(deviceId,
341 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
345 case CHANNEL_DOWN_LIGHT_BRIGHTNESS:
347 if (command instanceof PercentType) {
348 PercentType pctCommand = (PercentType) command;
349 value = pctCommand.intValue();
351 action = BondDeviceAction.TURN_LIGHT_OFF;
354 action = BondDeviceAction.SET_DOWN_LIGHT_BRIGHTNESS;
356 logger.trace("Fan down light brightness command with value of {}", value);
357 api.executeDeviceAction(deviceId, action, value);
358 } else if (command instanceof IncreaseDecreaseType) {
359 logger.trace("Fan down light brightness increase/decrease command");
360 api.executeDeviceAction(deviceId,
361 ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
362 ? BondDeviceAction.INCREASE_DOWN_LIGHT_BRIGHTNESS
363 : BondDeviceAction.DECREASE_DOWN_LIGHT_BRIGHTNESS),
365 } else if (command instanceof OnOffType) {
366 logger.trace("Fan down light brightness command {}", command);
367 api.executeDeviceAction(deviceId,
368 command == OnOffType.ON ? BondDeviceAction.TURN_LIGHT_ON : BondDeviceAction.TURN_LIGHT_OFF,
371 logger.debug("Unsupported command on fan down light brightness channel");
376 if (command instanceof PercentType) {
377 PercentType pctCommand = (PercentType) command;
378 value = pctCommand.intValue();
380 action = BondDeviceAction.TURN_OFF;
383 action = BondDeviceAction.SET_FLAME;
385 logger.trace("Fireplace flame command with value of {}", value);
386 api.executeDeviceAction(deviceId, action, value);
387 } else if (command instanceof IncreaseDecreaseType) {
388 logger.trace("Fireplace flame increase/decrease command");
389 api.executeDeviceAction(deviceId,
390 ((IncreaseDecreaseType) command == IncreaseDecreaseType.INCREASE
391 ? BondDeviceAction.INCREASE_FLAME
392 : BondDeviceAction.DECREASE_FLAME),
394 } else if (command instanceof OnOffType) {
395 api.executeDeviceAction(deviceId,
396 command == OnOffType.ON ? BondDeviceAction.TURN_ON : BondDeviceAction.TURN_OFF, null);
398 logger.info("Unsupported command on flame channel");
402 case CHANNEL_ROLLERSHUTTER:
403 logger.trace("Rollershutter command {}", command);
404 if (command.equals(PercentType.ZERO)) {
405 command = UpDownType.UP;
406 } else if (command.equals(PercentType.HUNDRED)) {
407 command = UpDownType.DOWN;
409 if (command == UpDownType.UP) {
410 action = BondDeviceAction.OPEN;
411 } else if (command == UpDownType.DOWN) {
412 action = BondDeviceAction.CLOSE;
413 } else if (command == StopMoveType.STOP) {
414 action = BondDeviceAction.HOLD;
416 if (action != null) {
417 api.executeDeviceAction(deviceId, action, null);
422 logger.info("Command {} on unknown channel {}, {}", command.toFullString(), channelUID.getId(),
423 channelUID.toString());
428 private void enableUpLight() {
429 Objects.requireNonNull(api).executeDeviceAction(Objects.requireNonNull(config.deviceId),
430 BondDeviceAction.TURN_UP_LIGHT_ON, null);
433 private void enableDownLight() {
434 Objects.requireNonNull(api).executeDeviceAction(Objects.requireNonNull(config.deviceId),
435 BondDeviceAction.TURN_DOWN_LIGHT_ON, null);
439 public void initialize() {
440 config = getConfigAs(BondDeviceConfiguration.class);
441 logger.trace("Starting initialization for Bond device with device id {}.", config.deviceId);
442 fullyInitialized = false;
445 // set the thing status to UNKNOWN temporarily
446 updateStatus(ThingStatus.UNKNOWN);
448 scheduler.execute(this::initializeThing);
452 public synchronized void dispose() {
453 logger.debug("Disposing thing handler for {}.", this.getThing().getUID());
454 // Mark handler as disposed as soon as possible to halt updates
456 fullyInitialized = false;
458 final ScheduledFuture<?> pollingJob = this.pollingJob;
459 if (pollingJob != null && !pollingJob.isCancelled()) {
460 pollingJob.cancel(true);
462 this.pollingJob = null;
465 private void initializeThing() {
466 String deviceId = config.deviceId;
468 if (deviceId == null) {
469 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
470 "@text/offline.conf-error.no-device-id");
474 if (!getBridgeAndAPI()) {
477 BondHttpApi api = this.api;
479 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.comm-error.no-api");
484 logger.trace("Getting device information for {} ({})", config.deviceId, this.getThing().getLabel());
485 deviceInfo = api.getDevice(deviceId);
486 logger.trace("Getting device properties for {} ({})", config.deviceId, this.getThing().getLabel());
487 deviceProperties = api.getDeviceProperties(deviceId);
488 } catch (BondException e) {
489 if (!e.wasBridgeSetOffline()) {
490 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
495 final BondDevice devInfo = this.deviceInfo;
496 final BondDeviceProperties devProperties = this.deviceProperties;
497 BondDeviceType devType;
499 if (devInfo == null || devProperties == null || (devType = devInfo.type) == null
500 || (devHash = devInfo.hash) == null) {
501 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
502 "@text/offline.conf-error.no-device-properties");
506 // Anytime the configuration has changed or the binding has been updated,
507 // recreate the thing to make sure all possible channels are available
508 // NOTE: This will cause the thing to be disposed and re-initialized
509 if (wasThingUpdatedExternally(devInfo)) {
510 recreateAllChannels(devType, devHash);
514 updateDevicePropertiesFromBond(devInfo, devProperties);
516 deleteExtraChannels(devInfo.actions);
521 updateStatus(ThingStatus.ONLINE);
522 fullyInitialized = true;
523 logger.debug("Finished initializing device!");
526 private void updateProperty(Map<String, String> thingProperties, String key, @Nullable String value) {
530 thingProperties.put(key, value);
533 private void updateDevicePropertiesFromBond(BondDevice devInfo, BondDeviceProperties devProperties) {
534 // Update all the thing properties based on the result
535 Map<String, String> thingProperties = new HashMap<String, String>();
536 updateProperty(thingProperties, CONFIG_DEVICE_ID, config.deviceId);
537 logger.trace("Updating device name to {}", devInfo.name);
538 updateProperty(thingProperties, PROPERTIES_DEVICE_NAME, devInfo.name);
539 logger.trace("Updating other device properties for {} ({})", config.deviceId, this.getThing().getLabel());
540 updateProperty(thingProperties, PROPERTIES_TEMPLATE_NAME, devInfo.template);
541 thingProperties.put(PROPERTIES_MAX_SPEED, String.valueOf(devProperties.maxSpeed));
542 thingProperties.put(PROPERTIES_TRUST_STATE, String.valueOf(devProperties.trustState));
543 thingProperties.put(PROPERTIES_ADDRESS, String.valueOf(devProperties.addr));
544 thingProperties.put(PROPERTIES_RF_FREQUENCY, String.valueOf(devProperties.freq));
545 logger.trace("Saving properties for {} ({})", config.deviceId, this.getThing().getLabel());
546 updateProperties(thingProperties);
549 private synchronized void recreateAllChannels(BondDeviceType currentType, String currentHash) {
550 if (hasConfigurationError()) {
551 logger.trace("Don't recreate channels, I've been disposed!");
555 logger.debug("Recreating all possible channels for a {} for {} ({})",
556 currentType.getThingTypeUID().getAsString(), config.deviceId, this.getThing().getLabel());
558 // Create a new configuration
559 final Map<String, Object> map = new HashMap<>();
560 map.put(CONFIG_DEVICE_ID, Objects.requireNonNull(config.deviceId));
561 map.put(CONFIG_LATEST_HASH, currentHash);
562 Configuration newConfiguration = new Configuration(map);
564 // Change the thing type back to itself to force all channels to be re-created from XML
565 changeThingType(currentType.getThingTypeUID(), newConfiguration);
568 private synchronized void deleteExtraChannels(List<BondDeviceAction> currentActions) {
569 logger.trace("Deleting channels based on the available actions");
570 // Get the thing to edit
571 ThingBuilder thingBuilder = editThing();
572 final BondDevice devInfo = this.deviceInfo;
574 // Now, look at the whole list of possible channels
575 List<Channel> possibleChannels = this.getThing().getChannels();
576 Set<String> availableChannelIds = new HashSet<>();
578 for (BondDeviceAction action : currentActions) {
579 String actionType = action.getChannelTypeId();
580 if (actionType != null) {
581 availableChannelIds.add(actionType);
582 logger.trace(" Action: {}, Relevant Channel Type Id: {}", action.getActionId(), actionType);
585 // Remove power channels if we have a dimmer channel for them;
586 // the dimmer channel already covers the power case.
587 // Add the raw channel for advanced users if we're a ceiling fan.
588 if (availableChannelIds.contains(CHANNEL_FAN_SPEED)) {
589 availableChannelIds.remove(CHANNEL_POWER);
590 availableChannelIds.remove(CHANNEL_FAN_POWER);
591 if (devInfo != null && devInfo.type == BondDeviceType.CEILING_FAN) {
592 availableChannelIds.add(CHANNEL_RAW_FAN_SPEED);
595 if (availableChannelIds.contains(CHANNEL_LIGHT_BRIGHTNESS)) {
596 availableChannelIds.remove(CHANNEL_LIGHT_POWER);
598 if (availableChannelIds.contains(CHANNEL_UP_LIGHT_BRIGHTNESS)) {
599 availableChannelIds.remove(CHANNEL_UP_LIGHT_POWER);
601 if (availableChannelIds.contains(CHANNEL_DOWN_LIGHT_BRIGHTNESS)) {
602 availableChannelIds.remove(CHANNEL_DOWN_LIGHT_POWER);
604 if (availableChannelIds.contains(CHANNEL_FLAME)) {
605 availableChannelIds.remove(CHANNEL_POWER);
608 for (Channel channel : possibleChannels) {
609 if (availableChannelIds.contains(channel.getUID().getId())) {
610 logger.trace(" ++++ Keeping: {}", channel.getUID().getId());
612 thingBuilder.withoutChannel(channel.getUID());
613 logger.trace(" ---- Dropping: {}", channel.getUID().getId());
617 // Add all the channels
618 logger.trace("Saving the thing with extra channels removed");
619 updateThing(thingBuilder.build());
622 public String getDeviceId() {
623 String deviceId = config.deviceId;
624 return deviceId == null ? "" : deviceId;
627 public synchronized void updateChannelsFromState(@Nullable BondDeviceState updateState) {
628 if (hasConfigurationError()) {
632 if (updateState == null) {
633 logger.debug("No state information provided to update channels with");
637 logger.debug("Updating channels from state for {} ({})", config.deviceId, this.getThing().getLabel());
639 updateStatus(ThingStatus.ONLINE);
641 updateState(CHANNEL_POWER, updateState.power == 0 ? OnOffType.OFF : OnOffType.ON);
643 final BondDevice devInfo = this.deviceInfo;
644 if (devInfo != null && devInfo.actions.contains(BondDeviceAction.TURN_FP_FAN_OFF)) {
645 fanOn = updateState.fpfanPower != 0;
646 updateState(CHANNEL_FAN_POWER, fanOn ? OnOffType.OFF : OnOffType.ON);
647 updateState(CHANNEL_FAN_SPEED, new PercentType(updateState.fpfanSpeed));
649 fanOn = updateState.power != 0;
651 BondDeviceProperties devProperties = this.deviceProperties;
652 if (devProperties != null) {
653 double maxSpeed = devProperties.maxSpeed;
654 value = (int) (((double) updateState.speed / maxSpeed) * 100);
655 logger.trace("Raw fan speed: {}, Percent: {}", updateState.speed, value);
656 } else if (updateState.speed != 0 && this.getThing().getThingTypeUID().equals(THING_TYPE_BOND_FAN)) {
657 logger.info("Unable to convert fan speed to a percent for {}!", this.getThing().getLabel());
659 updateState(CHANNEL_FAN_SPEED, formPercentType(fanOn, value));
660 updateState(CHANNEL_RAW_FAN_SPEED, fanOn ? new DecimalType(updateState.speed) : DecimalType.ZERO);
662 updateState(CHANNEL_FAN_BREEZE_STATE, updateState.breeze[0] == 0 ? OnOffType.OFF : OnOffType.ON);
663 updateState(CHANNEL_FAN_BREEZE_MEAN, new PercentType(updateState.breeze[1]));
664 updateState(CHANNEL_FAN_BREEZE_VAR, new PercentType(updateState.breeze[2]));
665 updateState(CHANNEL_FAN_DIRECTION,
666 updateState.direction == 1 ? new StringType("summer") : new StringType("winter"));
667 updateState(CHANNEL_FAN_TIMER, new DecimalType(updateState.timer));
669 updateState(CHANNEL_LIGHT_POWER, updateState.light == 0 ? OnOffType.OFF : OnOffType.ON);
670 updateState(CHANNEL_LIGHT_BRIGHTNESS, formPercentType(updateState.light != 0, updateState.brightness));
672 updateState(CHANNEL_UP_LIGHT_ENABLE, updateState.upLight == 0 ? OnOffType.OFF : OnOffType.ON);
673 updateState(CHANNEL_UP_LIGHT_POWER,
674 (updateState.upLight == 1 && updateState.light == 1) ? OnOffType.ON : OnOffType.OFF);
675 updateState(CHANNEL_UP_LIGHT_BRIGHTNESS,
676 formPercentType((updateState.upLight == 1 && updateState.light == 1), updateState.upLightBrightness));
678 updateState(CHANNEL_DOWN_LIGHT_ENABLE, updateState.downLight == 0 ? OnOffType.OFF : OnOffType.ON);
679 updateState(CHANNEL_DOWN_LIGHT_POWER,
680 (updateState.downLight == 1 && updateState.light == 1) ? OnOffType.ON : OnOffType.OFF);
681 updateState(CHANNEL_DOWN_LIGHT_BRIGHTNESS, formPercentType(
682 (updateState.downLight == 1 && updateState.light == 1), updateState.downLightBrightness));
684 updateState(CHANNEL_FLAME, formPercentType(updateState.power != 0, updateState.flame));
686 updateState(CHANNEL_ROLLERSHUTTER, formPercentType(updateState.open != 0, 100));
689 private PercentType formPercentType(boolean isOn, int value) {
691 return PercentType.ZERO;
693 return new PercentType(value);
697 private boolean hasConfigurationError() {
698 final ThingStatusInfo statusInfo = getThing().getStatusInfo();
699 return statusInfo.getStatus() == ThingStatus.OFFLINE
700 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR || disposed
701 || config.deviceId == null;
704 private synchronized boolean wasThingUpdatedExternally(BondDevice devInfo) {
705 // Check if the Bond hash tree has changed
706 final String lastDeviceConfigurationHash = config.lastDeviceConfigurationHash;
707 boolean updatedHashTree = !devInfo.hash.equals(lastDeviceConfigurationHash);
708 if (updatedHashTree) {
709 logger.debug("Hash tree of device has been updated by Bond.");
710 logger.debug("Current state is {}, prior state was {}.", devInfo.hash, lastDeviceConfigurationHash);
712 return updatedHashTree;
715 private boolean getBridgeAndAPI() {
716 Bridge myBridge = this.getBridge();
717 if (myBridge == null) {
718 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
719 "@text/offline.conf-error.no-bridge");
723 BondBridgeHandler myBridgeHandler = (BondBridgeHandler) myBridge.getHandler();
724 if (myBridgeHandler != null) {
725 this.api = myBridgeHandler.getBridgeAPI();
728 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
729 "@text/offline.conf-error.no-bridge");
735 // Start polling for state
736 private synchronized void startPollingJob() {
737 final ScheduledFuture<?> pollingJob = this.pollingJob;
738 if (pollingJob == null || pollingJob.isCancelled()) {
739 Runnable pollingCommand = () -> {
740 BondHttpApi api = this.api;
742 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
743 "@text/offline.comm-error.no-api");
747 String deviceId = Objects.requireNonNull(config.deviceId);
748 logger.trace("Polling for current state for {} ({})", deviceId, this.getThing().getLabel());
750 deviceState = api.getDeviceState(deviceId);
751 updateChannelsFromState(deviceState);
752 } catch (BondException e) {
753 if (!e.wasBridgeSetOffline()) {
754 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
758 this.pollingJob = scheduler.scheduleWithFixedDelay(pollingCommand, 60, 300, TimeUnit.SECONDS);
763 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
764 if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
765 && getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
766 if (!fullyInitialized) {
767 scheduler.execute(this::initializeThing);
769 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
770 // restart the polling job when the bridge goes back online
773 } else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
774 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
775 // stop the polling job when the bridge goes offline
776 ScheduledFuture<?> pollingJob = this.pollingJob;
777 if (pollingJob != null) {
778 pollingJob.cancel(true);
779 this.pollingJob = null;