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.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.api.Firmware;
34 import org.openhab.binding.hdpowerview.internal.api.HubFirmware;
35 import org.openhab.binding.hdpowerview.internal.api.UserData;
36 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections;
37 import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection;
38 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
39 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene;
40 import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents;
41 import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents.ScheduledEvent;
42 import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
43 import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData;
44 import org.openhab.binding.hdpowerview.internal.builders.AutomationChannelBuilder;
45 import org.openhab.binding.hdpowerview.internal.builders.SceneChannelBuilder;
46 import org.openhab.binding.hdpowerview.internal.builders.SceneGroupChannelBuilder;
47 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewHubConfiguration;
48 import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration;
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.CoreItemFactory;
54 import org.openhab.core.library.types.OnOffType;
55 import org.openhab.core.thing.Bridge;
56 import org.openhab.core.thing.Channel;
57 import org.openhab.core.thing.ChannelGroupUID;
58 import org.openhab.core.thing.ChannelUID;
59 import org.openhab.core.thing.Thing;
60 import org.openhab.core.thing.ThingStatus;
61 import org.openhab.core.thing.ThingStatusDetail;
62 import org.openhab.core.thing.ThingStatusInfo;
63 import org.openhab.core.thing.ThingUID;
64 import org.openhab.core.thing.binding.BaseBridgeHandler;
65 import org.openhab.core.thing.binding.ThingHandler;
66 import org.openhab.core.thing.binding.builder.ChannelBuilder;
67 import org.openhab.core.thing.type.ChannelTypeUID;
68 import org.openhab.core.types.Command;
69 import org.openhab.core.types.RefreshType;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
74 * The {@link HDPowerViewHubHandler} is responsible for handling commands, which
75 * are sent to one of the channels.
77 * @author Andy Lintner - Initial contribution
78 * @author Andrew Fiddian-Green - Added support for secondary rail positions
79 * @author Jacob Laursen - Added support for scene groups and automations
82 public class HDPowerViewHubHandler extends BaseBridgeHandler {
84 private final Logger logger = LoggerFactory.getLogger(HDPowerViewHubHandler.class);
85 private final HttpClient httpClient;
86 private final HDPowerViewTranslationProvider translationProvider;
87 private final ConcurrentHashMap<ThingUID, ShadeData> pendingShadeInitializations = new ConcurrentHashMap<>();
88 private final Duration firmwareVersionValidityPeriod = Duration.ofDays(1);
90 private long refreshInterval;
91 private long hardRefreshPositionInterval;
92 private long hardRefreshBatteryLevelInterval;
94 private @NonNullByDefault({}) HDPowerViewWebTargets webTargets;
95 private @Nullable ScheduledFuture<?> pollFuture;
96 private @Nullable ScheduledFuture<?> hardRefreshPositionFuture;
97 private @Nullable ScheduledFuture<?> hardRefreshBatteryLevelFuture;
99 private List<Scene> sceneCache = new CopyOnWriteArrayList<>();
100 private List<SceneCollection> sceneCollectionCache = new CopyOnWriteArrayList<>();
101 private List<ScheduledEvent> scheduledEventCache = new CopyOnWriteArrayList<>();
102 private Instant UserDataUpdated = Instant.MIN;
103 private Boolean deprecatedChannelsCreated = false;
105 private final ChannelTypeUID sceneChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
106 HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE);
108 private final ChannelTypeUID sceneGroupChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
109 HDPowerViewBindingConstants.CHANNELTYPE_SCENE_GROUP_ACTIVATE);
111 private final ChannelTypeUID automationChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID,
112 HDPowerViewBindingConstants.CHANNELTYPE_AUTOMATION_ENABLED);
114 public HDPowerViewHubHandler(Bridge bridge, HttpClient httpClient,
115 HDPowerViewTranslationProvider translationProvider) {
117 this.httpClient = httpClient;
118 this.translationProvider = translationProvider;
122 public void handleCommand(ChannelUID channelUID, Command command) {
123 if (RefreshType.REFRESH == command) {
124 requestRefreshShadePositions();
128 Channel channel = getThing().getChannel(channelUID.getId());
129 if (channel == null) {
134 int id = Integer.parseInt(channelUID.getIdWithoutGroup());
135 if (sceneChannelTypeUID.equals(channel.getChannelTypeUID()) && OnOffType.ON == command) {
136 webTargets.activateScene(id);
137 // Reschedule soft poll for immediate shade position update.
139 } else if (sceneGroupChannelTypeUID.equals(channel.getChannelTypeUID()) && OnOffType.ON == command) {
140 webTargets.activateSceneCollection(id);
141 // Reschedule soft poll for immediate shade position update.
143 } else if (automationChannelTypeUID.equals(channel.getChannelTypeUID())) {
144 webTargets.enableScheduledEvent(id, OnOffType.ON == command);
146 } catch (HubMaintenanceException e) {
147 // exceptions are logged in HDPowerViewWebTargets
148 UserDataUpdated = Instant.MIN;
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();
172 UserDataUpdated = Instant.MIN;
174 updateStatus(ThingStatus.UNKNOWN);
178 private void initializeChannels() {
179 // Rebuild dynamic channels and synchronize with cache.
180 updateThing(editThing().withChannels(new ArrayList<Channel>()).build());
182 sceneCollectionCache.clear();
183 scheduledEventCache.clear();
184 deprecatedChannelsCreated = false;
187 public HDPowerViewWebTargets getWebTargets() {
192 public void handleRemoval() {
193 super.handleRemoval();
198 public void dispose() {
201 pendingShadeInitializations.clear();
205 public void childHandlerInitialized(final ThingHandler childHandler, final Thing childThing) {
206 logger.debug("Child handler initialized: {}", childThing.getUID());
207 if (childHandler instanceof HDPowerViewShadeHandler) {
208 ShadeData shadeData = pendingShadeInitializations.remove(childThing.getUID());
209 if (shadeData != null) {
210 if (shadeData.id > 0) {
211 updateShadeThing(shadeData.id, childThing, shadeData);
213 updateUnknownShadeThing(childThing);
217 super.childHandlerInitialized(childHandler, childThing);
221 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
222 logger.debug("Child handler disposed: {}", childThing.getUID());
223 if (childHandler instanceof HDPowerViewShadeHandler) {
224 pendingShadeInitializations.remove(childThing.getUID());
226 super.childHandlerDisposed(childHandler, childThing);
229 private void schedulePoll() {
234 private void scheduleSoftPoll() {
235 ScheduledFuture<?> future = this.pollFuture;
236 if (future != null) {
237 future.cancel(false);
239 logger.debug("Scheduling poll every {} ms", refreshInterval);
240 this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshInterval, TimeUnit.MILLISECONDS);
243 private void scheduleHardPoll() {
244 ScheduledFuture<?> future = this.hardRefreshPositionFuture;
245 if (future != null) {
246 future.cancel(false);
248 if (hardRefreshPositionInterval > 0) {
249 logger.debug("Scheduling hard position refresh every {} minutes", hardRefreshPositionInterval);
250 this.hardRefreshPositionFuture = scheduler.scheduleWithFixedDelay(this::requestRefreshShadePositions, 1,
251 hardRefreshPositionInterval, TimeUnit.MINUTES);
254 future = this.hardRefreshBatteryLevelFuture;
255 if (future != null) {
256 future.cancel(false);
258 if (hardRefreshBatteryLevelInterval > 0) {
259 logger.debug("Scheduling hard battery level refresh every {} hours", hardRefreshBatteryLevelInterval);
260 this.hardRefreshBatteryLevelFuture = scheduler.scheduleWithFixedDelay(
261 this::requestRefreshShadeBatteryLevels, 1, hardRefreshBatteryLevelInterval, TimeUnit.HOURS);
265 private synchronized void stopPoll() {
266 ScheduledFuture<?> future = this.pollFuture;
267 if (future != null) {
270 this.pollFuture = null;
272 future = this.hardRefreshPositionFuture;
273 if (future != null) {
276 this.hardRefreshPositionFuture = null;
278 future = this.hardRefreshBatteryLevelFuture;
279 if (future != null) {
282 this.hardRefreshBatteryLevelFuture = null;
285 private synchronized void poll() {
287 updateUserDataProperties();
288 } catch (HubException e) {
289 logger.warn("Failed to update firmware properties: {}", e.getMessage());
293 logger.debug("Polling for state");
296 List<Scene> scenes = updateSceneChannels();
297 List<SceneCollection> sceneCollections = updateSceneGroupChannels();
298 List<ScheduledEvent> scheduledEvents = updateAutomationChannels(scenes, sceneCollections);
300 // Scheduled events should also have their current state updated if event has been
301 // enabled or disabled through app or other integration.
302 updateAutomationStates(scheduledEvents);
303 } catch (HubInvalidResponseException e) {
304 Throwable cause = e.getCause();
306 logger.warn("Bridge returned a bad JSON response: {}", e.getMessage());
308 logger.warn("Bridge returned a bad JSON response: {} -> {}", e.getMessage(), cause.getMessage());
310 } catch (HubMaintenanceException e) {
311 // exceptions are logged in HDPowerViewWebTargets
312 UserDataUpdated = Instant.MIN;
313 } catch (HubException e) {
314 logger.warn("Error connecting to bridge: {}", e.getMessage());
315 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
316 UserDataUpdated = Instant.MIN;
320 private void updateUserDataProperties()
321 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
322 if (UserDataUpdated.isAfter(Instant.now().minus(firmwareVersionValidityPeriod))) {
326 UserData userData = webTargets.getUserData();
327 Map<String, String> properties = editProperties();
328 HubFirmware firmwareVersions = userData.firmware;
329 if (firmwareVersions != null) {
330 updateFirmwareProperties(properties, firmwareVersions);
332 String serialNumber = userData.serialNumber;
333 if (serialNumber != null) {
334 properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
336 String macAddress = userData.macAddress;
337 if (macAddress != null) {
338 properties.put(Thing.PROPERTY_MAC_ADDRESS, macAddress);
340 String hubName = userData.getHubName();
341 if (!hubName.isEmpty()) {
342 properties.put(HDPowerViewBindingConstants.PROPERTY_HUB_NAME, hubName);
344 updateProperties(properties);
345 UserDataUpdated = Instant.now();
348 private void updateFirmwareProperties(Map<String, String> properties, HubFirmware firmwareVersions) {
349 Firmware mainProcessor = firmwareVersions.mainProcessor;
350 if (mainProcessor == null) {
351 logger.warn("Main processor firmware version missing in response.");
354 logger.debug("Main processor firmware version received: {}, {}", mainProcessor.name, mainProcessor.toString());
355 String mainProcessorName = mainProcessor.name;
356 if (mainProcessorName != null) {
357 properties.put(HDPowerViewBindingConstants.PROPERTY_FIRMWARE_NAME, mainProcessorName);
359 properties.put(Thing.PROPERTY_FIRMWARE_VERSION, mainProcessor.toString());
360 Firmware radio = firmwareVersions.radio;
362 logger.debug("Radio firmware version received: {}", radio.toString());
363 properties.put(HDPowerViewBindingConstants.PROPERTY_RADIO_FIRMWARE_VERSION, radio.toString());
367 private void pollShades() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
368 Shades shades = webTargets.getShades();
369 List<ShadeData> shadesData = shades.shadeData;
370 if (shadesData == null) {
371 throw new HubInvalidResponseException("Missing 'shades.shadeData' element");
374 updateStatus(ThingStatus.ONLINE);
375 logger.debug("Received data for {} shades", shadesData.size());
377 Map<Integer, ShadeData> idShadeDataMap = getIdShadeDataMap(shadesData);
378 Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
379 for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
380 Thing thing = item.getKey();
381 int shadeId = item.getValue();
382 ShadeData shadeData = idShadeDataMap.get(shadeId);
383 if (shadeData != null) {
384 updateShadeThing(shadeId, thing, shadeData);
386 updateUnknownShadeThing(thing);
391 private void updateShadeThing(int shadeId, Thing thing, ShadeData shadeData) {
392 HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
393 if (thingHandler == null) {
394 logger.debug("Shade '{}' handler not initialized", shadeId);
395 pendingShadeInitializations.put(thing.getUID(), shadeData);
398 ThingStatus thingStatus = thingHandler.getThing().getStatus();
399 switch (thingStatus) {
403 logger.debug("Updating shade '{}'", shadeId);
404 thingHandler.onReceiveUpdate(shadeData);
408 logger.debug("Shade '{}' handler not yet ready; status: {}", shadeId, thingStatus);
409 pendingShadeInitializations.put(thing.getUID(), shadeData);
414 logger.debug("Ignoring shade update for shade '{}' in status {}", shadeId, thingStatus);
419 private void updateUnknownShadeThing(Thing thing) {
420 String shadeId = thing.getUID().getId();
421 logger.debug("Shade '{}' has no data in hub", shadeId);
422 HDPowerViewShadeHandler thingHandler = ((HDPowerViewShadeHandler) thing.getHandler());
423 if (thingHandler == null) {
424 logger.debug("Shade '{}' handler not initialized", shadeId);
425 pendingShadeInitializations.put(thing.getUID(), new ShadeData());
428 ThingStatus thingStatus = thingHandler.getThing().getStatus();
429 switch (thingStatus) {
433 thing.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
434 "@text/offline.gone.shade-unknown-to-hub"));
438 logger.debug("Shade '{}' handler not yet ready; status: {}", shadeId, thingStatus);
439 pendingShadeInitializations.put(thing.getUID(), new ShadeData());
444 logger.debug("Ignoring shade status update for shade '{}' in status {}", shadeId, thingStatus);
449 private List<Scene> fetchScenes()
450 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
451 Scenes scenes = webTargets.getScenes();
452 List<Scene> sceneData = scenes.sceneData;
453 if (sceneData == null) {
454 throw new HubInvalidResponseException("Missing 'scenes.sceneData' element");
456 logger.debug("Received data for {} scenes", sceneData.size());
461 private List<Scene> updateSceneChannels()
462 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
463 List<Scene> scenes = fetchScenes();
465 if (scenes.size() == sceneCache.size() && sceneCache.containsAll(scenes)) {
466 // Duplicates are not allowed. Reordering is not supported.
467 logger.debug("Preserving scene channels, no changes detected");
471 logger.debug("Updating all scene channels, changes detected");
472 sceneCache = new CopyOnWriteArrayList<Scene>(scenes);
474 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
475 allChannels.removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES.equals(c.getUID().getGroupId()));
477 SceneChannelBuilder channelBuilder = SceneChannelBuilder
478 .create(this.translationProvider,
479 new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES))
480 .withScenes(scenes).withChannels(allChannels);
482 updateThing(editThing().withChannels(channelBuilder.build()).build());
484 createDeprecatedSceneChannels(scenes);
490 * Create backwards compatible scene channels if any items configured before release 3.2
491 * are still linked. Users should have a reasonable amount of time to migrate to the new
492 * scene channels that are connected to a channel group.
494 private void createDeprecatedSceneChannels(List<Scene> scenes) {
495 if (deprecatedChannelsCreated) {
496 // Only do this once.
499 ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(),
500 HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES);
501 for (Scene scene : scenes) {
502 String channelId = Integer.toString(scene.id);
503 ChannelUID newChannelUid = new ChannelUID(channelGroupUid, channelId);
504 ChannelUID deprecatedChannelUid = new ChannelUID(getThing().getUID(), channelId);
505 String description = translationProvider.getText("dynamic-channel.scene-activate.deprecated.description",
507 Channel channel = ChannelBuilder.create(deprecatedChannelUid, CoreItemFactory.SWITCH)
508 .withType(sceneChannelTypeUID).withLabel(scene.getName()).withDescription(description).build();
509 logger.debug("Creating deprecated channel '{}' ('{}') to probe for linked items", deprecatedChannelUid,
511 updateThing(editThing().withChannel(channel).build());
512 if (this.isLinked(deprecatedChannelUid) && !this.isLinked(newChannelUid)) {
513 logger.warn("Created deprecated channel '{}' ('{}'), please link items to '{}' instead",
514 deprecatedChannelUid, scene.getName(), newChannelUid);
516 if (this.isLinked(newChannelUid)) {
517 logger.debug("Removing deprecated channel '{}' ('{}') since new channel '{}' is linked",
518 deprecatedChannelUid, scene.getName(), newChannelUid);
521 logger.debug("Removing deprecated channel '{}' ('{}') since it has no linked items",
522 deprecatedChannelUid, scene.getName());
524 updateThing(editThing().withoutChannel(deprecatedChannelUid).build());
527 deprecatedChannelsCreated = true;
530 private List<SceneCollection> fetchSceneCollections()
531 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
532 SceneCollections sceneCollections = webTargets.getSceneCollections();
533 List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
534 if (sceneCollectionData == null) {
535 throw new HubInvalidResponseException("Missing 'sceneCollections.sceneCollectionData' element");
537 logger.debug("Received data for {} sceneCollections", sceneCollectionData.size());
539 return sceneCollectionData;
542 private List<SceneCollection> updateSceneGroupChannels()
543 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
544 List<SceneCollection> sceneCollections = fetchSceneCollections();
546 if (sceneCollections.size() == sceneCollectionCache.size()
547 && sceneCollectionCache.containsAll(sceneCollections)) {
548 // Duplicates are not allowed. Reordering is not supported.
549 logger.debug("Preserving scene group channels, no changes detected");
550 return sceneCollections;
553 logger.debug("Updating all scene group channels, changes detected");
554 sceneCollectionCache = new CopyOnWriteArrayList<SceneCollection>(sceneCollections);
556 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
558 .removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_SCENE_GROUPS.equals(c.getUID().getGroupId()));
560 SceneGroupChannelBuilder channelBuilder = SceneGroupChannelBuilder
561 .create(this.translationProvider,
562 new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_SCENE_GROUPS))
563 .withSceneCollections(sceneCollections).withChannels(allChannels);
565 updateThing(editThing().withChannels(channelBuilder.build()).build());
567 return sceneCollections;
570 private List<ScheduledEvent> fetchScheduledEvents()
571 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
572 ScheduledEvents scheduledEvents = webTargets.getScheduledEvents();
573 List<ScheduledEvent> scheduledEventData = scheduledEvents.scheduledEventData;
574 if (scheduledEventData == null) {
575 throw new HubInvalidResponseException("Missing 'scheduledEvents.scheduledEventData' element");
577 logger.debug("Received data for {} scheduledEvents", scheduledEventData.size());
579 return scheduledEventData;
582 private List<ScheduledEvent> updateAutomationChannels(List<Scene> scenes, List<SceneCollection> sceneCollections)
583 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
584 List<ScheduledEvent> scheduledEvents = fetchScheduledEvents();
586 if (scheduledEvents.size() == scheduledEventCache.size() && scheduledEventCache.containsAll(scheduledEvents)) {
587 // Duplicates are not allowed. Reordering is not supported.
588 logger.debug("Preserving automation channels, no changes detected");
589 return scheduledEvents;
592 logger.debug("Updating all automation channels, changes detected");
593 scheduledEventCache = new CopyOnWriteArrayList<ScheduledEvent>(scheduledEvents);
595 List<Channel> allChannels = new ArrayList<>(getThing().getChannels());
597 .removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS.equals(c.getUID().getGroupId()));
598 AutomationChannelBuilder channelBuilder = AutomationChannelBuilder
599 .create(this.translationProvider,
600 new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS))
601 .withScenes(scenes).withSceneCollections(sceneCollections).withScheduledEvents(scheduledEvents)
602 .withChannels(allChannels);
603 updateThing(editThing().withChannels(channelBuilder.build()).build());
605 return scheduledEvents;
608 private void updateAutomationStates(List<ScheduledEvent> scheduledEvents) {
609 ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(),
610 HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS);
611 for (ScheduledEvent scheduledEvent : scheduledEvents) {
612 String scheduledEventId = Integer.toString(scheduledEvent.id);
613 ChannelUID channelUid = new ChannelUID(channelGroupUid, scheduledEventId);
614 updateState(channelUid, scheduledEvent.enabled ? OnOffType.ON : OnOffType.OFF);
618 private Map<Thing, Integer> getShadeThingIdMap() {
619 Map<Thing, Integer> ret = new HashMap<>();
620 getThing().getThings().stream()
621 .filter(thing -> HDPowerViewBindingConstants.THING_TYPE_SHADE.equals(thing.getThingTypeUID()))
623 int id = thing.getConfiguration().as(HDPowerViewShadeConfiguration.class).id;
631 private Map<Integer, ShadeData> getIdShadeDataMap(List<ShadeData> shadeData) {
632 Map<Integer, ShadeData> ret = new HashMap<>();
633 for (ShadeData shade : shadeData) {
635 ret.put(shade.id, shade);
641 private void requestRefreshShadePositions() {
642 Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
643 for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
644 Thing thing = item.getKey();
645 if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
646 // Skip shades unknown to the Hub.
647 logger.debug("Shade '{}' is unknown, skipping position refresh", item.getValue());
650 ThingHandler handler = thing.getHandler();
651 if (handler instanceof HDPowerViewShadeHandler) {
652 ((HDPowerViewShadeHandler) handler).requestRefreshShadePosition();
654 int shadeId = item.getValue();
655 logger.debug("Shade '{}' handler not initialized", shadeId);
660 private void requestRefreshShadeBatteryLevels() {
661 Map<Thing, Integer> thingIdMap = getShadeThingIdMap();
662 for (Entry<Thing, Integer> item : thingIdMap.entrySet()) {
663 Thing thing = item.getKey();
664 if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.GONE) {
665 // Skip shades unknown to the Hub.
666 logger.debug("Shade '{}' is unknown, skipping battery level refresh", item.getValue());
669 ThingHandler handler = thing.getHandler();
670 if (handler instanceof HDPowerViewShadeHandler) {
671 ((HDPowerViewShadeHandler) handler).requestRefreshShadeBatteryLevel();
673 int shadeId = item.getValue();
674 logger.debug("Shade '{}' handler not initialized", shadeId);