]> git.basschouten.com Git - openhab-addons.git/blob
a13ce2afbc1cab00f0567a6c88808048da8c9cfd
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.harmonyhub.internal.handler;
14
15 import static org.openhab.binding.harmonyhub.internal.HarmonyHubBindingConstants.*;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.LinkedList;
20 import java.util.List;
21 import java.util.Set;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.harmonyhub.internal.HarmonyHubDynamicTypeProvider;
26 import org.openhab.binding.harmonyhub.internal.config.HarmonyDeviceConfig;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.Channel;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.ThingStatusInfo;
35 import org.openhab.core.thing.ThingTypeUID;
36 import org.openhab.core.thing.binding.BaseThingHandler;
37 import org.openhab.core.thing.binding.builder.ChannelBuilder;
38 import org.openhab.core.thing.binding.builder.ThingBuilder;
39 import org.openhab.core.thing.type.ChannelType;
40 import org.openhab.core.thing.type.ChannelTypeBuilder;
41 import org.openhab.core.thing.type.ChannelTypeUID;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44 import org.openhab.core.types.StateDescriptionFragmentBuilder;
45 import org.openhab.core.types.StateOption;
46 import org.openhab.core.types.UnDefType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 import com.digitaldan.harmony.config.ControlGroup;
51 import com.digitaldan.harmony.config.Device;
52 import com.digitaldan.harmony.config.Function;
53 import com.digitaldan.harmony.config.HarmonyConfig;
54
55 /**
56  * The {@link HarmonyDeviceHandler} is responsible for handling commands for Harmony Devices, which are
57  * sent to one of the channels. It also is responsible for dynamically creating the button press channel
58  * based on the device's available button press functions.
59  *
60  * @author Dan Cunningham - Initial contribution
61  * @author Wouter Born - Add null annotations
62  */
63 @NonNullByDefault
64 public class HarmonyDeviceHandler extends BaseThingHandler {
65
66     private final Logger logger = LoggerFactory.getLogger(HarmonyDeviceHandler.class);
67
68     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(HARMONY_DEVICE_THING_TYPE);
69
70     private final HarmonyHubDynamicTypeProvider typeProvider;
71
72     private @NonNullByDefault({}) HarmonyDeviceConfig config;
73
74     public HarmonyDeviceHandler(Thing thing, HarmonyHubDynamicTypeProvider typeProvider) {
75         super(thing);
76         this.typeProvider = typeProvider;
77     }
78
79     protected @Nullable HarmonyHubHandler getHarmonyHubHandler() {
80         Bridge bridge = getBridge();
81         return bridge != null ? (HarmonyHubHandler) bridge.getHandler() : null;
82     }
83
84     @Override
85     public void handleCommand(ChannelUID channelUID, Command command) {
86         logger.trace("Handling command '{}' for {}", command, channelUID);
87
88         if (command instanceof RefreshType) {
89             // nothing to refresh
90             return;
91         }
92
93         if (getThing().getStatus() != ThingStatus.ONLINE) {
94             logger.debug("Hub is offline, ignoring command {} for channel {}", command, channelUID);
95             return;
96         }
97
98         if (!(command instanceof StringType)) {
99             logger.warn("Command '{}' is not a String type for channel {}", command, channelUID);
100             return;
101         }
102
103         HarmonyHubHandler hubHandler = getHarmonyHubHandler();
104         if (hubHandler == null) {
105             logger.warn("Command '{}' cannot be handled because {} has no bridge", command, getThing().getUID());
106             return;
107         }
108
109         int id = config.id;
110         String name = config.name;
111         String message = "Pressing button '{}' on {}";
112         if (id > 0) {
113             logger.debug(message, command, id);
114             hubHandler.pressButton(id, command.toString());
115         } else if (name != null) {
116             logger.debug(message, command, name);
117             hubHandler.pressButton(name, command.toString());
118         } else {
119             logger.warn("Command '{}' cannot be handled because {} has no valid id or name configured", command,
120                     getThing().getUID());
121         }
122         // may need to ask the list if this can be set here?
123         updateState(channelUID, UnDefType.UNDEF);
124     }
125
126     @Override
127     public void initialize() {
128         config = getConfigAs(HarmonyDeviceConfig.class);
129         boolean validConfiguration = config.name != null || config.id >= 0;
130         if (validConfiguration) {
131             updateStatus(ThingStatus.UNKNOWN);
132             updateBridgeStatus();
133         } else {
134             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
135                     "A harmony device thing must be configured with a device name OR a postive device id");
136         }
137     }
138
139     @Override
140     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
141         updateBridgeStatus();
142     }
143
144     @Override
145     public void handleRemoval() {
146         typeProvider.removeChannelTypesForThing(getThing().getUID());
147         super.handleRemoval();
148     }
149
150     /**
151      * Updates our state based on the bridge/hub
152      */
153     private void updateBridgeStatus() {
154         Bridge bridge = getBridge();
155         ThingStatus bridgeStatus = bridge != null ? bridge.getStatus() : null;
156         HarmonyHubHandler hubHandler = getHarmonyHubHandler();
157
158         boolean bridgeOnline = bridgeStatus == ThingStatus.ONLINE;
159         boolean thingOnline = getThing().getStatus() == ThingStatus.ONLINE;
160
161         if (bridgeOnline && hubHandler != null && !thingOnline) {
162             updateStatus(ThingStatus.ONLINE);
163             hubHandler.getConfigFuture().thenAcceptAsync(this::updateButtonPressChannel, scheduler).exceptionally(e -> {
164                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
165                         "Getting config failed: " + e.getMessage());
166                 return null;
167             });
168         } else if (!bridgeOnline || hubHandler == null) {
169             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
170         }
171     }
172
173     /**
174      * Updates the buttonPress channel with the available buttons as option states.
175      */
176     private void updateButtonPressChannel(@Nullable HarmonyConfig harmonyConfig) {
177         ChannelTypeUID channelTypeUID = new ChannelTypeUID(
178                 getThing().getUID().getAsString() + ":" + CHANNEL_BUTTON_PRESS);
179
180         if (harmonyConfig == null) {
181             logger.debug("Cannot update {} when HarmonyConfig is null", channelTypeUID);
182             return;
183         }
184
185         logger.debug("Updating {}", channelTypeUID);
186
187         List<StateOption> states = getButtonStateOptions(harmonyConfig);
188
189         ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID, "Send Button Press", "String")
190                 .withDescription("Send a button press to device " + getThing().getLabel())
191                 .withStateDescriptionFragment(StateDescriptionFragmentBuilder.create().withOptions(states).build())
192                 .build();
193
194         typeProvider.putChannelType(channelType);
195
196         Channel channel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), CHANNEL_BUTTON_PRESS), "String")
197                 .withType(channelTypeUID).build();
198
199         // replace existing buttonPress with updated one
200         List<Channel> newChannels = new ArrayList<>();
201         for (Channel c : getThing().getChannels()) {
202             if (!c.getUID().equals(channel.getUID())) {
203                 newChannels.add(c);
204             }
205         }
206         newChannels.add(channel);
207
208         ThingBuilder thingBuilder = editThing();
209         thingBuilder.withChannels(newChannels);
210         updateThing(thingBuilder.build());
211     }
212
213     private List<StateOption> getButtonStateOptions(HarmonyConfig harmonyConfig) {
214         int id = config.id;
215         String name = config.name;
216         List<StateOption> states = new LinkedList<>();
217
218         // Iterate through button function commands and add them to our state list
219         for (Device device : harmonyConfig.getDevices()) {
220             boolean sameId = name == null && device.getId() == id;
221             boolean sameName = name != null && name.equals(device.getLabel());
222
223             if (sameId || sameName) {
224                 for (ControlGroup controlGroup : device.getControlGroup()) {
225                     for (Function function : controlGroup.getFunction()) {
226                         states.add(new StateOption(function.getName(), function.getLabel()));
227                     }
228                 }
229                 break;
230             }
231         }
232         return states;
233     }
234 }