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