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.hdpowerview.internal.handler;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.List;
19 import java.util.Map.Entry;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.CopyOnWriteArrayList;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
25 import javax.ws.rs.ProcessingException;
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.api.Firmware;
34 import org.openhab.binding.hdpowerview.internal.api.responses.FirmwareVersions;
35 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections;
36 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection;
37 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
38 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
39 import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents;
40 import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents.ScheduledEvent;
41 import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
42 import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
43 import org.openhab.binding.hdpowerview.internal.builders.AutomationChannelBuilder;
44 import org.openhab.binding.hdpowerview.internal.builders.SceneChannelBuilder;
45 import org.openhab.binding.hdpowerview.internal.builders.SceneGroupChannelBuilder;
46 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewHubConfiguration;
47 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
48 import org.openhab.binding.hdpowerview.internal.exceptions.HubException;
49 import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
50 import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
51 import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
52 import org.openhab.core.library.CoreItemFactory;
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.ThingUID;
62 import org.openhab.core.thing.binding.BaseBridgeHandler;
63 import org.openhab.core.thing.binding.ThingHandler;
64 import org.openhab.core.thing.binding.builder.ChannelBuilder;
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<>();
87 private long refreshInterval;
88 private long hardRefreshPositionInterval;
89 private long hardRefreshBatteryLevelInterval;
91 private @Nullable HDPowerViewWebTargets webTargets;
92 private @Nullable ScheduledFuture<?> pollFuture;
93 private @Nullable ScheduledFuture<?> hardRefreshPositionFuture;
94 private @Nullable ScheduledFuture<?> hardRefreshBatteryLevelFuture;
96 private List<Scene> sceneCache = new CopyOnWriteArrayList<>();
97 private List<SceneCollection> sceneCollectionCache = new CopyOnWriteArrayList<>();
98 private List<ScheduledEvent> scheduledEventCache = new CopyOnWriteArrayList<>();
99 private @Nullable FirmwareVersions firmwareVersions;
100 private Boolean deprecatedChannelsCreated = false;
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 HDPowerViewWebTargets webTargets = this.webTargets;
132 if (webTargets == null) {
133 throw new ProcessingException("Web targets not initialized");
135 int id = Integer.parseInt(channelUID.getIdWithoutGroup());
136 if (sceneChannelTypeUID.equals(channel.getChannelTypeUID()) && OnOffType.ON == command) {
137 webTargets.activateScene(id);
138 // Reschedule soft poll for immediate shade position update.
140 } else if (sceneGroupChannelTypeUID.equals(channel.getChannelTypeUID()) && OnOffType.ON == command) {
141 webTargets.activateSceneCollection(id);
142 // Reschedule soft poll for immediate shade position update.
144 } else if (automationChannelTypeUID.equals(channel.getChannelTypeUID())) {
145 webTargets.enableScheduledEvent(id, OnOffType.ON == command);
147 } catch (HubMaintenanceException e) {
148 // exceptions are logged in HDPowerViewWebTargets
149 } catch (NumberFormatException | HubException e) {
150 logger.debug("Unexpected error {}", e.getMessage());
155 public void initialize() {
156 logger.debug("Initializing hub");
157 HDPowerViewHubConfiguration config = getConfigAs(HDPowerViewHubConfiguration.class);
158 String host = config.host;
160 if (host == null || host.isEmpty()) {
161 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
162 "@text/offline.conf-error.no-host-address");
166 pendingShadeInitializations.clear();
167 webTargets = new HDPowerViewWebTargets(httpClient, host);
168 refreshInterval = config.refresh;
169 hardRefreshPositionInterval = config.hardRefresh;
170 hardRefreshBatteryLevelInterval = config.hardRefreshBatteryLevel;
171 initializeChannels();
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();
181 deprecatedChannelsCreated = false;
184 public @Nullable HDPowerViewWebTargets getWebTargets() {
189 public void handleRemoval() {
190 super.handleRemoval();
195 public void dispose() {
198 pendingShadeInitializations.clear();
202 public void childHandlerInitialized(final ThingHandler childHandler, final Thing childThing) {
203 logger.debug("Child handler initialized: {}", childThing.getUID());
204 if (childHandler instanceof HDPowerViewShadeHandler) {
205 ShadeData shadeData = pendingShadeInitializations.remove(childThing.getUID());
206 if (shadeData != null) {
207 updateShadeThing(shadeData.id, childThing, shadeData);
210 super.childHandlerInitialized(childHandler, childThing);
214 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
215 logger.debug("Child handler disposed: {}", childThing.getUID());
216 if (childHandler instanceof HDPowerViewShadeHandler) {
217 pendingShadeInitializations.remove(childThing.getUID());
219 super.childHandlerDisposed(childHandler, childThing);
222 private void schedulePoll() {
227 private void scheduleSoftPoll() {
228 ScheduledFuture<?> future = this.pollFuture;
229 if (future != null) {
230 future.cancel(false);
232 logger.debug("Scheduling poll every {} ms", refreshInterval);
233 this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshInterval, TimeUnit.MILLISECONDS);
236 private void scheduleHardPoll() {
237 ScheduledFuture<?> future = this.hardRefreshPositionFuture;
238 if (future != null) {
239 future.cancel(false);
241 if (hardRefreshPositionInterval > 0) {
242 logger.debug("Scheduling hard position refresh every {} minutes", hardRefreshPositionInterval);
243 this.hardRefreshPositionFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShadePositions, 1,
244 hardRefreshPositionInterval, TimeUnit.MINUTES);
247 future = this.hardRefreshBatteryLevelFuture;
248 if (future != null) {
249 future.cancel(false);
251 if (hardRefreshBatteryLevelInterval > 0) {
252 logger.debug("Scheduling hard battery level refresh every {} hours", hardRefreshBatteryLevelInterval);
253 this.hardRefreshBatteryLevelFuture = scheduler.scheduleWithFixedDelay(
254 this::requestRefreshShadeBatteryLevels, 1, hardRefreshBatteryLevelInterval, TimeUnit.HOURS);
258 private synchronized void stopPoll() {
259 ScheduledFuture<?> future = this.pollFuture;
260 if (future != null) {
263 this.pollFuture = null;
265 future = this.hardRefreshPositionFuture;
266 if (future != null) {
269 this.hardRefreshPositionFuture = null;
271 future = this.hardRefreshBatteryLevelFuture;
272 if (future != null) {
275 this.hardRefreshBatteryLevelFuture = null;
278 private synchronized void poll() {
280 logger.debug("Polling for state");
281 updateFirmwareProperties();
284 List<Scene> scenes = updateSceneChannels();
285 List<SceneCollection> sceneCollections = updateSceneGroupChannels();
286 List<ScheduledEvent> scheduledEvents = updateAutomationChannels(scenes, sceneCollections);
288 // Scheduled events should also have their current state updated if event has been
289 // enabled or disabled through app or other integration.
290 updateAutomationStates(scheduledEvents);
291 } catch (HubInvalidResponseException e) {
292 Throwable cause = e.getCause();
294 logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
296 logger.warn("Bridge returned a bad JSON response: {} -> {}", e.getMessage(), cause.getMessage());
298 } catch (HubMaintenanceException e) {
299 // exceptions are logged in HDPowerViewWebTargets
300 } catch (HubException e) {
301 logger.warn("Error connecting to bridge: {}", e.getMessage());
302 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, e.getMessage());
306 private void updateFirmwareProperties()
307 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
308 if (firmwareVersions != null) {
311 HDPowerViewWebTargets webTargets = this.webTargets;
312 if (webTargets == null) {
313 throw new ProcessingException("Web targets not initialized");
315 FirmwareVersions firmwareVersions = webTargets.getFirmwareVersions();
316 Firmware mainProcessor = firmwareVersions.mainProcessor;
317 if (mainProcessor == null) {
318 logger.warn("Main processor firmware version missing in response.");
321 logger.debug("Main processor firmware version received: {}, {}", mainProcessor.name, mainProcessor.toString());
322 Map<String, String> properties = editProperties();
323 String mainProcessorName = mainProcessor.name;
324 if (mainProcessorName != null) {
325 properties.put(HDPowerViewBindingConstants.PROPERTY_FIRMWARE_NAME, mainProcessorName);
327 properties.put(Thing.PROPERTY_FIRMWARE_VERSION, mainProcessor.toString());
328 Firmware radio = firmwareVersions.radio;
330 logger.debug("Radio firmware version received: {}", radio.toString());
331 properties.put(HDPowerViewBindingConstants.PROPERTY_RADIO_FIRMWARE_VERSION, radio.toString());
333 updateProperties(properties);
336 private void pollShades() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
337 HDPowerViewWebTargets webTargets = this.webTargets;
338 if (webTargets == null) {
339 throw new ProcessingException("Web targets not initialized");
342 Shades shades = webTargets.getShades();
343 List<ShadeData> shadesData = shades.shadeData;
344 if (shadesData == null) {
345 throw new HubInvalidResponseException("Missing 'shades.shadeData' element");
348 updateStatus(ThingStatus.ONLINE);
349 logger.debug("Received data for {} shades", shadesData.size());
351 Map<Integer, ShadeData> idShadeDataMap = getIdShadeDataMap(shadesData);
352 Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
353 for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
354 Thing thing = item.getKey();
355 int shadeId = item.getValue();
356 ShadeData shadeData = idShadeDataMap.get(shadeId);
357 updateShadeThing(shadeId, thing, shadeData);
361 private void updateShadeThing(int shadeId, Thing thing, @Nullable ShadeData shadeData) {
362 if (shadeData == null) {
363 logger.debug("Shade '{}' has no data in hub", shadeId);
366 HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
367 if (thingHandler == null) {
368 logger.debug("Shade '{}' handler not initialized", shadeId);
369 pendingShadeInitializations.put(thing.getUID(), shadeData);
372 ThingStatus thingStatus = thingHandler.getThing().getStatus();
373 switch (thingStatus) {
377 logger.debug("Updating shade '{}'", shadeId);
378 thingHandler.onReceiveUpdate(shadeData);
382 logger.debug("Shade '{}' handler not yet ready; status: {}", shadeId, thingStatus);
383 pendingShadeInitializations.put(thing.getUID(), shadeData);
388 logger.debug("Ignoring shade update for shade '{}' in status {}", shadeId, thingStatus);
393 private List<Scene> fetchScenes()
394 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
395 HDPowerViewWebTargets webTargets = this.webTargets;
396 if (webTargets == null) {
397 throw new ProcessingException("Web targets not initialized");
400 Scenes scenes = webTargets.getScenes();
401 List<Scene> sceneData = scenes.sceneData;
402 if (sceneData == null) {
403 throw new HubInvalidResponseException("Missing 'scenes.sceneData' element");
405 logger.debug("Received data for {} scenes", sceneData.size());
410 private List<Scene> updateSceneChannels()
411 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
412 List<Scene> scenes = fetchScenes();
414 if (scenes.size() == sceneCache.size() && sceneCache.containsAll(scenes)) {
415 // Duplicates are not allowed. Reordering is not supported.
416 logger.debug("Preserving scene channels, no changes detected");
420 logger.debug("Updating all scene channels, changes detected");
421 sceneCache = new CopyOnWriteArrayList<Scene>(scenes);
423 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
424 allChannels.removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES.equals(c.getUID().getGroupId()));
426 SceneChannelBuilder channelBuilder = SceneChannelBuilder
427 .create(this.translationProvider,
428 new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES))
429 .withScenes(scenes).withChannels(allChannels);
431 updateThing(editThing().withChannels(channelBuilder.build()).build());
433 createDeprecatedSceneChannels(scenes);
439 * Create backwards compatible scene channels if any items configured before release 3.2
440 * are still linked. Users should have a reasonable amount of time to migrate to the new
441 * scene channels that are connected to a channel group.
443 private void createDeprecatedSceneChannels(List<Scene> scenes) {
444 if (deprecatedChannelsCreated) {
445 // Only do this once.
448 ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(),
449 HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES);
450 for (Scene scene : scenes) {
451 String channelId = Integer.toString(scene.id);
452 ChannelUID newChannelUid = new ChannelUID(channelGroupUid, channelId);
453 ChannelUID deprecatedChannelUid = new ChannelUID(getThing().getUID(), channelId);
454 String description = translationProvider.getText("dynamic-channel.scene-activate.deprecated.description",
456 Channel channel = ChannelBuilder.create(deprecatedChannelUid, CoreItemFactory.SWITCH)
457 .withType(sceneChannelTypeUID).withLabel(scene.getName()).withDescription(description).build();
458 logger.debug("Creating deprecated channel '{}' ('{}') to probe for linked items", deprecatedChannelUid,
460 updateThing(editThing().withChannel(channel).build());
461 if (this.isLinked(deprecatedChannelUid) && !this.isLinked(newChannelUid)) {
462 logger.warn("Created deprecated channel '{}' ('{}'), please link items to '{}' instead",
463 deprecatedChannelUid, scene.getName(), newChannelUid);
465 if (this.isLinked(newChannelUid)) {
466 logger.debug("Removing deprecated channel '{}' ('{}') since new channel '{}' is linked",
467 deprecatedChannelUid, scene.getName(), newChannelUid);
470 logger.debug("Removing deprecated channel '{}' ('{}') since it has no linked items",
471 deprecatedChannelUid, scene.getName());
473 updateThing(editThing().withoutChannel(deprecatedChannelUid).build());
476 deprecatedChannelsCreated = true;
479 private List<SceneCollection> fetchSceneCollections()
480 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
481 HDPowerViewWebTargets webTargets = this.webTargets;
482 if (webTargets == null) {
483 throw new ProcessingException("Web targets not initialized");
486 SceneCollections sceneCollections = webTargets.getSceneCollections();
487 List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
488 if (sceneCollectionData == null) {
489 throw new HubInvalidResponseException("Missing 'sceneCollections.sceneCollectionData' element");
491 logger.debug("Received data for {} sceneCollections", sceneCollectionData.size());
493 return sceneCollectionData;
496 private List<SceneCollection> updateSceneGroupChannels()
497 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
498 List<SceneCollection> sceneCollections = fetchSceneCollections();
500 if (sceneCollections.size() == sceneCollectionCache.size()
501 && sceneCollectionCache.containsAll(sceneCollections)) {
502 // Duplicates are not allowed. Reordering is not supported.
503 logger.debug("Preserving scene group channels, no changes detected");
504 return sceneCollections;
507 logger.debug("Updating all scene group channels, changes detected");
508 sceneCollectionCache = new CopyOnWriteArrayList<SceneCollection>(sceneCollections);
510 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
512 .removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_SCENE_GROUPS.equals(c.getUID().getGroupId()));
514 SceneGroupChannelBuilder channelBuilder = SceneGroupChannelBuilder
515 .create(this.translationProvider,
516 new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_SCENE_GROUPS))
517 .withSceneCollections(sceneCollections).withChannels(allChannels);
519 updateThing(editThing().withChannels(channelBuilder.build()).build());
521 return sceneCollections;
524 private List<ScheduledEvent> fetchScheduledEvents()
525 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
526 HDPowerViewWebTargets webTargets = this.webTargets;
527 if (webTargets == null) {
528 throw new ProcessingException("Web targets not initialized");
531 ScheduledEvents scheduledEvents = webTargets.getScheduledEvents();
532 List<ScheduledEvent> scheduledEventData = scheduledEvents.scheduledEventData;
533 if (scheduledEventData == null) {
534 throw new HubInvalidResponseException("Missing 'scheduledEvents.scheduledEventData' element");
536 logger.debug("Received data for {} scheduledEvents", scheduledEventData.size());
538 return scheduledEventData;
541 private List<ScheduledEvent> updateAutomationChannels(List<Scene> scenes, List<SceneCollection> sceneCollections)
542 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
543 List<ScheduledEvent> scheduledEvents = fetchScheduledEvents();
545 if (scheduledEvents.size() == scheduledEventCache.size() && scheduledEventCache.containsAll(scheduledEvents)) {
546 // Duplicates are not allowed. Reordering is not supported.
547 logger.debug("Preserving automation channels, no changes detected");
548 return scheduledEvents;
551 logger.debug("Updating all automation channels, changes detected");
552 scheduledEventCache = new CopyOnWriteArrayList<ScheduledEvent>(scheduledEvents);
554 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
556 .removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS.equals(c.getUID().getGroupId()));
557 AutomationChannelBuilder channelBuilder = AutomationChannelBuilder
558 .create(this.translationProvider,
559 new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS))
560 .withScenes(scenes).withSceneCollections(sceneCollections).withScheduledEvents(scheduledEvents)
561 .withChannels(allChannels);
562 updateThing(editThing().withChannels(channelBuilder.build()).build());
564 return scheduledEvents;
567 private void updateAutomationStates(List<ScheduledEvent> scheduledEvents) {
568 ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(),
569 HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS);
570 for (ScheduledEvent scheduledEvent : scheduledEvents) {
571 String scheduledEventId = Integer.toString(scheduledEvent.id);
572 ChannelUID channelUid = new ChannelUID(channelGroupUid, scheduledEventId);
573 updateState(channelUid, scheduledEvent.enabled ? OnOffType.ON : OnOffType.OFF);
577 private Map<Thing, Integer> getShadeThingIdMap() {
578 Map<Thing, Integer> ret = new HashMap<>();
579 getThing().getThings().stream()
580 .filter(thing -> HDPowerViewBindingConstants.THING_TYPE_SHADE.equals(thing.getThingTypeUID()))
582 int id = thing.getConfiguration().as(HDPowerViewShadeConfiguration.class).id;
590 private Map<Integer, ShadeData> getIdShadeDataMap(List<ShadeData> shadeData) {
591 Map<Integer, ShadeData> ret = new HashMap<>();
592 for (ShadeData shade : shadeData) {
594 ret.put(shade.id, shade);
600 private void requestRefreshShadePositions() {
601 Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
602 for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
603 Thing thing = item.getKey();
604 ThingHandler handler = thing.getHandler();
605 if (handler instanceof HDPowerViewShadeHandler) {
606 ((HDPowerViewShadeHandler) handler).requestRefreshShadePosition();
608 int shadeId = item.getValue();
609 logger.debug("Shade '{}' handler not initialized", shadeId);
614 private void requestRefreshShadeBatteryLevels() {
615 Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
616 for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
617 Thing thing = item.getKey();
618 ThingHandler handler = thing.getHandler();
619 if (handler instanceof HDPowerViewShadeHandler) {
620 ((HDPowerViewShadeHandler) handler).requestRefreshShadeBatteryLevel();
622 int shadeId = item.getValue();
623 logger.debug("Shade '{}' handler not initialized", shadeId);