2 * Copyright (c) 2010-2024 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.hdpowerview.internal.handler;
15 import java.time.Duration;
16 import java.time.Instant;
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.List;
21 import java.util.Map.Entry;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.CopyOnWriteArrayList;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.eclipse.jetty.client.HttpClient;
30 import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants;
31 import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider;
32 import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets;
33 import org.openhab.binding.hdpowerview.internal.builders.AutomationChannelBuilder;
34 import org.openhab.binding.hdpowerview.internal.builders.SceneChannelBuilder;
35 import org.openhab.binding.hdpowerview.internal.builders.SceneGroupChannelBuilder;
36 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewHubConfiguration;
37 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
38 import org.openhab.binding.hdpowerview.internal.dto.Firmware;
39 import org.openhab.binding.hdpowerview.internal.dto.HubFirmware;
40 import org.openhab.binding.hdpowerview.internal.dto.Scene;
41 import org.openhab.binding.hdpowerview.internal.dto.SceneCollection;
42 import org.openhab.binding.hdpowerview.internal.dto.ScheduledEvent;
43 import org.openhab.binding.hdpowerview.internal.dto.ShadeData;
44 import org.openhab.binding.hdpowerview.internal.dto.UserData;
45 import org.openhab.binding.hdpowerview.internal.dto.responses.SceneCollections;
46 import org.openhab.binding.hdpowerview.internal.dto.responses.Scenes;
47 import org.openhab.binding.hdpowerview.internal.dto.responses.ScheduledEvents;
48 import org.openhab.binding.hdpowerview.internal.dto.responses.Shades;
49 import org.openhab.binding.hdpowerview.internal.exceptions.HubException;
50 import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
51 import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
52 import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
53 import org.openhab.core.library.types.OnOffType;
54 import org.openhab.core.thing.Bridge;
55 import org.openhab.core.thing.Channel;
56 import org.openhab.core.thing.ChannelGroupUID;
57 import org.openhab.core.thing.ChannelUID;
58 import org.openhab.core.thing.Thing;
59 import org.openhab.core.thing.ThingStatus;
60 import org.openhab.core.thing.ThingStatusDetail;
61 import org.openhab.core.thing.ThingStatusInfo;
62 import org.openhab.core.thing.ThingUID;
63 import org.openhab.core.thing.binding.BaseBridgeHandler;
64 import org.openhab.core.thing.binding.ThingHandler;
65 import org.openhab.core.thing.type.ChannelTypeUID;
66 import org.openhab.core.types.Command;
67 import org.openhab.core.types.RefreshType;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
72 * The {@link HDPowerViewHubHandler} is responsible for handling commands, which
73 * are sent to one of the channels.
75 * @author Andy Lintner - Initial contribution
76 * @author Andrew Fiddian-Green - Added support for secondary rail positions
77 * @author Jacob Laursen - Added support for scene groups and automations
80 public class HDPowerViewHubHandler extends BaseBridgeHandler {
82 private final Logger logger = LoggerFactory.getLogger(HDPowerViewHubHandler.class);
83 private final HttpClient httpClient;
84 private final HDPowerViewTranslationProvider translationProvider;
85 private final ConcurrentHashMap<ThingUID, ShadeData> pendingShadeInitializations = new ConcurrentHashMap<>();
86 private final Duration firmwareVersionValidityPeriod = Duration.ofDays(1);
88 private long refreshInterval;
89 private long hardRefreshPositionInterval;
90 private long hardRefreshBatteryLevelInterval;
92 private @NonNullByDefault({}) HDPowerViewWebTargets webTargets;
93 private @Nullable ScheduledFuture<?> pollFuture;
94 private @Nullable ScheduledFuture<?> hardRefreshPositionFuture;
95 private @Nullable ScheduledFuture<?> hardRefreshBatteryLevelFuture;
97 private List<Scene> sceneCache = new CopyOnWriteArrayList<>();
98 private List<SceneCollection> sceneCollectionCache = new CopyOnWriteArrayList<>();
99 private List<ScheduledEvent> scheduledEventCache = new CopyOnWriteArrayList<>();
100 private Instant userDataUpdated = Instant.MIN;
102 private final ChannelTypeUID sceneChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
103 HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE);
105 private final ChannelTypeUID sceneGroupChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
106 HDPowerViewBindingConstants.CHANNELTYPE_SCENE_GROUP_ACTIVATE);
108 private final ChannelTypeUID automationChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
109 HDPowerViewBindingConstants.CHANNELTYPE_AUTOMATION_ENABLED);
111 public HDPowerViewHubHandler(Bridge bridge, HttpClient httpClient,
112 HDPowerViewTranslationProvider translationProvider) {
114 this.httpClient = httpClient;
115 this.translationProvider = translationProvider;
119 public void handleCommand(ChannelUID channelUID, Command command) {
120 if (RefreshType.REFRESH == command) {
121 requestRefreshShadePositions();
125 Channel channel = getThing().getChannel(channelUID.getId());
126 if (channel == null) {
131 int id = Integer.parseInt(channelUID.getIdWithoutGroup());
132 if (sceneChannelTypeUID.equals(channel.getChannelTypeUID()) && OnOffType.ON == command) {
133 webTargets.activateScene(id);
134 // Reschedule soft poll for immediate shade position update.
136 } else if (sceneGroupChannelTypeUID.equals(channel.getChannelTypeUID()) && OnOffType.ON == command) {
137 webTargets.activateSceneCollection(id);
138 // Reschedule soft poll for immediate shade position update.
140 } else if (automationChannelTypeUID.equals(channel.getChannelTypeUID())) {
141 webTargets.enableScheduledEvent(id, OnOffType.ON == command);
143 } catch (HubMaintenanceException e) {
144 // exceptions are logged in HDPowerViewWebTargets
145 userDataUpdated = Instant.MIN;
146 } catch (NumberFormatException | HubException e) {
147 logger.debug("Unexpected error {}", e.getMessage());
152 public void initialize() {
153 logger.debug("Initializing hub");
154 HDPowerViewHubConfiguration config = getConfigAs(HDPowerViewHubConfiguration.class);
155 String host = config.host;
157 if (host == null || host.isEmpty()) {
158 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
159 "@text/offline.conf-error.no-host-address");
163 pendingShadeInitializations.clear();
164 webTargets = new HDPowerViewWebTargets(httpClient, host);
165 refreshInterval = config.refresh;
166 hardRefreshPositionInterval = config.hardRefresh;
167 hardRefreshBatteryLevelInterval = config.hardRefreshBatteryLevel;
168 initializeChannels();
169 userDataUpdated = Instant.MIN;
171 updateStatus(ThingStatus.UNKNOWN);
175 private void initializeChannels() {
176 // Rebuild dynamic channels and synchronize with cache.
177 updateThing(editThing().withChannels(new ArrayList<Channel>()).build());
179 sceneCollectionCache.clear();
180 scheduledEventCache.clear();
183 public HDPowerViewWebTargets getWebTargets() {
188 public void handleRemoval() {
189 super.handleRemoval();
194 public void dispose() {
197 pendingShadeInitializations.clear();
201 public void childHandlerInitialized(final ThingHandler childHandler, final Thing childThing) {
202 logger.debug("Child handler initialized: {}", childThing.getUID());
203 if (childHandler instanceof HDPowerViewShadeHandler) {
204 ShadeData shadeData = pendingShadeInitializations.remove(childThing.getUID());
205 if (shadeData != null) {
206 if (shadeData.id > 0) {
207 updateShadeThing(shadeData.id, childThing, shadeData);
209 updateUnknownShadeThing(childThing);
213 super.childHandlerInitialized(childHandler, childThing);
217 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
218 logger.debug("Child handler disposed: {}", childThing.getUID());
219 if (childHandler instanceof HDPowerViewShadeHandler) {
220 pendingShadeInitializations.remove(childThing.getUID());
222 super.childHandlerDisposed(childHandler, childThing);
225 private void schedulePoll() {
230 private void scheduleSoftPoll() {
231 ScheduledFuture<?> future = this.pollFuture;
232 if (future != null) {
233 future.cancel(false);
235 logger.debug("Scheduling poll every {} ms", refreshInterval);
236 this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshInterval, TimeUnit.MILLISECONDS);
239 private void scheduleHardPoll() {
240 ScheduledFuture<?> future = this.hardRefreshPositionFuture;
241 if (future != null) {
242 future.cancel(false);
244 if (hardRefreshPositionInterval > 0) {
245 logger.debug("Scheduling hard position refresh every {} minutes", hardRefreshPositionInterval);
246 this.hardRefreshPositionFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShadePositions, 1,
247 hardRefreshPositionInterval, TimeUnit.MINUTES);
250 future = this.hardRefreshBatteryLevelFuture;
251 if (future != null) {
252 future.cancel(false);
254 if (hardRefreshBatteryLevelInterval > 0) {
255 logger.debug("Scheduling hard battery level refresh every {} hours", hardRefreshBatteryLevelInterval);
256 this.hardRefreshBatteryLevelFuture = scheduler.scheduleWithFixedDelay(
257 this::requestRefreshShadeBatteryLevels, 1, hardRefreshBatteryLevelInterval, TimeUnit.HOURS);
261 private synchronized void stopPoll() {
262 ScheduledFuture<?> future = this.pollFuture;
263 if (future != null) {
266 this.pollFuture = null;
268 future = this.hardRefreshPositionFuture;
269 if (future != null) {
272 this.hardRefreshPositionFuture = null;
274 future = this.hardRefreshBatteryLevelFuture;
275 if (future != null) {
278 this.hardRefreshBatteryLevelFuture = null;
281 private synchronized void poll() {
283 updateUserDataProperties();
284 } catch (HubException e) {
285 logger.warn("Failed to update firmware properties: {}", e.getMessage());
289 logger.debug("Polling for state");
292 List<Scene> scenes = updateSceneChannels();
293 List<SceneCollection> sceneCollections = updateSceneGroupChannels();
294 List<ScheduledEvent> scheduledEvents = updateAutomationChannels(scenes, sceneCollections);
296 // Scheduled events should also have their current state updated if event has been
297 // enabled or disabled through app or other integration.
298 updateAutomationStates(scheduledEvents);
299 } catch (HubInvalidResponseException e) {
300 Throwable cause = e.getCause();
302 logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
304 logger.warn("Bridge returned a bad JSON response: {} -> {}", e.getMessage(), cause.getMessage());
306 } catch (HubMaintenanceException e) {
307 // exceptions are logged in HDPowerViewWebTargets
308 userDataUpdated = Instant.MIN;
309 } catch (HubException e) {
310 logger.warn("Error connecting to bridge: {}", e.getMessage());
311 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
312 userDataUpdated = Instant.MIN;
316 private void updateUserDataProperties()
317 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
318 if (userDataUpdated.isAfter(Instant.now().minus(firmwareVersionValidityPeriod))) {
322 UserData userData = webTargets.getUserData();
323 Map<String, String> properties = editProperties();
324 HubFirmware firmwareVersions = userData.firmware;
325 if (firmwareVersions != null) {
326 updateFirmwareProperties(properties, firmwareVersions);
328 String serialNumber = userData.serialNumber;
329 if (serialNumber != null) {
330 properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
332 String macAddress = userData.macAddress;
333 if (macAddress != null) {
334 properties.put(Thing.PROPERTY_MAC_ADDRESS, macAddress);
336 String hubName = userData.getHubName();
337 if (!hubName.isEmpty()) {
338 properties.put(HDPowerViewBindingConstants.PROPERTY_HUB_NAME, hubName);
340 updateProperties(properties);
341 userDataUpdated = Instant.now();
344 private void updateFirmwareProperties(Map<String, String> properties, HubFirmware firmwareVersions) {
345 Firmware mainProcessor = firmwareVersions.mainProcessor;
346 if (mainProcessor == null) {
347 logger.warn("Main processor firmware version missing in response.");
350 logger.debug("Main processor firmware version received: {}, {}", mainProcessor.name, mainProcessor.toString());
351 String mainProcessorName = mainProcessor.name;
352 if (mainProcessorName != null) {
353 properties.put(HDPowerViewBindingConstants.PROPERTY_FIRMWARE_NAME, mainProcessorName);
355 properties.put(Thing.PROPERTY_FIRMWARE_VERSION, mainProcessor.toString());
356 Firmware radio = firmwareVersions.radio;
358 logger.debug("Radio firmware version received: {}", radio.toString());
359 properties.put(HDPowerViewBindingConstants.PROPERTY_RADIO_FIRMWARE_VERSION, radio.toString());
363 private void pollShades() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
364 Shades shades = webTargets.getShades();
365 List<ShadeData> shadesData = shades.shadeData;
366 if (shadesData == null) {
367 throw new HubInvalidResponseException("Missing 'shades.shadeData' element");
370 updateStatus(ThingStatus.ONLINE);
371 logger.debug("Received data for {} shades", shadesData.size());
373 Map<Integer, ShadeData> idShadeDataMap = getIdShadeDataMap(shadesData);
374 Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
375 for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
376 Thing thing = item.getKey();
377 int shadeId = item.getValue();
378 ShadeData shadeData = idShadeDataMap.get(shadeId);
379 if (shadeData != null) {
380 updateShadeThing(shadeId, thing, shadeData);
382 updateUnknownShadeThing(thing);
387 private void updateShadeThing(int shadeId, Thing thing, ShadeData shadeData) {
388 HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
389 if (thingHandler == null) {
390 logger.debug("Shade '{}' handler not initialized", shadeId);
391 pendingShadeInitializations.put(thing.getUID(), shadeData);
394 ThingStatus thingStatus = thingHandler.getThing().getStatus();
395 switch (thingStatus) {
399 logger.debug("Updating shade '{}'", shadeId);
400 thingHandler.onReceiveUpdate(shadeData);
404 logger.debug("Shade '{}' handler not yet ready; status: {}", shadeId, thingStatus);
405 pendingShadeInitializations.put(thing.getUID(), shadeData);
410 logger.debug("Ignoring shade update for shade '{}' in status {}", shadeId, thingStatus);
415 private void updateUnknownShadeThing(Thing thing) {
416 String shadeId = thing.getUID().getId();
417 logger.debug("Shade '{}' has no data in hub", shadeId);
418 HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
419 if (thingHandler == null) {
420 logger.debug("Shade '{}' handler not initialized", shadeId);
421 pendingShadeInitializations.put(thing.getUID(), new ShadeData());
424 ThingStatus thingStatus = thingHandler.getThing().getStatus();
425 switch (thingStatus) {
429 thing.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
430 "@text/offline.gone.shade-unknown-to-hub"));
434 logger.debug("Shade '{}' handler not yet ready; status: {}", shadeId, thingStatus);
435 pendingShadeInitializations.put(thing.getUID(), new ShadeData());
440 logger.debug("Ignoring shade status update for shade '{}' in status {}", shadeId, thingStatus);
445 private List<Scene> fetchScenes()
446 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
447 Scenes scenes = webTargets.getScenes();
448 List<Scene> sceneData = scenes.sceneData;
449 if (sceneData == null) {
450 throw new HubInvalidResponseException("Missing 'scenes.sceneData' element");
452 logger.debug("Received data for {} scenes", sceneData.size());
457 private List<Scene> updateSceneChannels()
458 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
459 List<Scene> scenes = fetchScenes();
461 if (scenes.size() == sceneCache.size() && sceneCache.containsAll(scenes)) {
462 // Duplicates are not allowed. Reordering is not supported.
463 logger.debug("Preserving scene channels, no changes detected");
467 logger.debug("Updating all scene channels, changes detected");
468 sceneCache = new CopyOnWriteArrayList<Scene>(scenes);
470 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
471 allChannels.removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES.equals(c.getUID().getGroupId()));
473 SceneChannelBuilder channelBuilder = SceneChannelBuilder
474 .create(this.translationProvider,
475 new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES))
476 .withScenes(scenes).withChannels(allChannels);
478 updateThing(editThing().withChannels(channelBuilder.build()).build());
483 private List<SceneCollection> fetchSceneCollections()
484 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
485 SceneCollections sceneCollections = webTargets.getSceneCollections();
486 List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
487 if (sceneCollectionData == null) {
488 throw new HubInvalidResponseException("Missing 'sceneCollections.sceneCollectionData' element");
490 logger.debug("Received data for {} sceneCollections", sceneCollectionData.size());
492 return sceneCollectionData;
495 private List<SceneCollection> updateSceneGroupChannels()
496 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
497 List<SceneCollection> sceneCollections = fetchSceneCollections();
499 if (sceneCollections.size() == sceneCollectionCache.size()
500 && sceneCollectionCache.containsAll(sceneCollections)) {
501 // Duplicates are not allowed. Reordering is not supported.
502 logger.debug("Preserving scene group channels, no changes detected");
503 return sceneCollections;
506 logger.debug("Updating all scene group channels, changes detected");
507 sceneCollectionCache = new CopyOnWriteArrayList<SceneCollection>(sceneCollections);
509 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
511 .removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_SCENE_GROUPS.equals(c.getUID().getGroupId()));
513 SceneGroupChannelBuilder channelBuilder = SceneGroupChannelBuilder
514 .create(this.translationProvider,
515 new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_SCENE_GROUPS))
516 .withSceneCollections(sceneCollections).withChannels(allChannels);
518 updateThing(editThing().withChannels(channelBuilder.build()).build());
520 return sceneCollections;
523 private List<ScheduledEvent> fetchScheduledEvents()
524 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
525 ScheduledEvents scheduledEvents = webTargets.getScheduledEvents();
526 List<ScheduledEvent> scheduledEventData = scheduledEvents.scheduledEventData;
527 if (scheduledEventData == null) {
528 throw new HubInvalidResponseException("Missing 'scheduledEvents.scheduledEventData' element");
530 logger.debug("Received data for {} scheduledEvents", scheduledEventData.size());
532 return scheduledEventData;
535 private List<ScheduledEvent> updateAutomationChannels(List<Scene> scenes, List<SceneCollection> sceneCollections)
536 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
537 List<ScheduledEvent> scheduledEvents = fetchScheduledEvents();
539 if (scheduledEvents.size() == scheduledEventCache.size() && scheduledEventCache.containsAll(scheduledEvents)) {
540 // Duplicates are not allowed. Reordering is not supported.
541 logger.debug("Preserving automation channels, no changes detected");
542 return scheduledEvents;
545 logger.debug("Updating all automation channels, changes detected");
546 scheduledEventCache = new CopyOnWriteArrayList<ScheduledEvent>(scheduledEvents);
548 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
550 .removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS.equals(c.getUID().getGroupId()));
551 AutomationChannelBuilder channelBuilder = AutomationChannelBuilder
552 .create(this.translationProvider,
553 new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS))
554 .withScenes(scenes).withSceneCollections(sceneCollections).withScheduledEvents(scheduledEvents)
555 .withChannels(allChannels);
556 updateThing(editThing().withChannels(channelBuilder.build()).build());
558 return scheduledEvents;
561 private void updateAutomationStates(List<ScheduledEvent> scheduledEvents) {
562 ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(),
563 HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS);
564 for (ScheduledEvent scheduledEvent : scheduledEvents) {
565 String scheduledEventId = Integer.toString(scheduledEvent.id);
566 ChannelUID channelUid = new ChannelUID(channelGroupUid, scheduledEventId);
567 updateState(channelUid, OnOffType.from(scheduledEvent.enabled));
571 private Map<Thing, Integer> getShadeThingIdMap() {
572 Map<Thing, Integer> ret = new HashMap<>();
573 getThing().getThings().stream()
574 .filter(thing -> HDPowerViewBindingConstants.THING_TYPE_SHADE.equals(thing.getThingTypeUID()))
576 int id = thing.getConfiguration().as(HDPowerViewShadeConfiguration.class).id;
584 private Map<Integer, ShadeData> getIdShadeDataMap(List<ShadeData> shadeData) {
585 Map<Integer, ShadeData> ret = new HashMap<>();
586 for (ShadeData shade : shadeData) {
588 ret.put(shade.id, shade);
594 private void requestRefreshShadePositions() {
595 Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
596 for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
597 Thing thing = item.getKey();
598 if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
599 // Skip shades unknown to the Hub.
600 logger.debug("Shade '{}' is unknown, skipping position refresh", item.getValue());
603 ThingHandler handler = thing.getHandler();
604 if (handler instanceof HDPowerViewShadeHandler shadeHandler) {
605 shadeHandler.requestRefreshShadePosition();
607 int shadeId = item.getValue();
608 logger.debug("Shade '{}' handler not initialized", shadeId);
613 private void requestRefreshShadeBatteryLevels() {
614 Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
615 for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
616 Thing thing = item.getKey();
617 if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
618 // Skip shades unknown to the Hub.
619 logger.debug("Shade '{}' is unknown, skipping battery level refresh", item.getValue());
622 ThingHandler handler = thing.getHandler();
623 if (handler instanceof HDPowerViewShadeHandler shadeHandler) {
624 shadeHandler.requestRefreshShadeBatteryLevel();
626 int shadeId = item.getValue();
627 logger.debug("Shade '{}' handler not initialized", shadeId);