]> git.basschouten.com Git - openhab-addons.git/blob
7a5d0e9f838d3066e4a55a7e4c9be2d6c3d981df
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.HarmonyHubHandlerFactory;
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 HarmonyHubHandlerFactory factory;
71
72     private @NonNullByDefault({}) HarmonyDeviceConfig config;
73
74     public HarmonyDeviceHandler(Thing thing, HarmonyHubHandlerFactory factory) {
75         super(thing);
76         this.factory = factory;
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 dispose() {
146         factory.removeChannelTypesForThing(getThing().getUID());
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         factory.addChannelType(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 }