2 * Copyright (c) 2010-2021 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.homeconnect.internal.handler;
15 import static java.lang.String.format;
16 import static java.util.Collections.emptyList;
17 import static org.openhab.binding.homeconnect.internal.HomeConnectBindingConstants.*;
19 import java.util.ArrayList;
21 import java.util.Optional;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.homeconnect.internal.client.HomeConnectApiClient;
25 import org.openhab.binding.homeconnect.internal.client.exception.ApplianceOfflineException;
26 import org.openhab.binding.homeconnect.internal.client.exception.AuthorizationException;
27 import org.openhab.binding.homeconnect.internal.client.exception.CommunicationException;
28 import org.openhab.binding.homeconnect.internal.client.model.Data;
29 import org.openhab.binding.homeconnect.internal.type.HomeConnectDynamicStateDescriptionProvider;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.StateOption;
37 import org.openhab.core.types.UnDefType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * The {@link HomeConnectHoodHandler} is responsible for handling commands, which are
43 * sent to one of the channels of a hood.
45 * @author Jonas BrĂ¼stel - Initial contribution
48 public class HomeConnectHoodHandler extends AbstractHomeConnectThingHandler {
50 private static final String START_VENTING_INTENSIVE_STAGE_PAYLOAD_TEMPLATE = "\n" + "{\n" + " \"data\": {\n"
51 + " \"key\": \"Cooking.Common.Program.Hood.Venting\",\n" + " \"options\": [\n"
52 + " {\n" + " \"key\": \"Cooking.Common.Option.Hood.IntensiveLevel\",\n"
53 + " \"value\": \"%s\"\n" + " }\n" + " ]\n" + " }\n" + "}";
55 private static final String START_VENTING_STAGE_PAYLOAD_TEMPLATE = "\n" + "{\n" + " \"data\": {\n"
56 + " \"key\": \"Cooking.Common.Program.Hood.Venting\",\n" + " \"options\": [\n"
57 + " {\n" + " \"key\": \"Cooking.Common.Option.Hood.VentingLevel\",\n"
58 + " \"value\": \"%s\"\n" + " }\n" + " ]\n" + " }\n" + "}";
60 private final Logger logger = LoggerFactory.getLogger(HomeConnectHoodHandler.class);
62 public HomeConnectHoodHandler(Thing thing,
63 HomeConnectDynamicStateDescriptionProvider dynamicStateDescriptionProvider) {
64 super(thing, dynamicStateDescriptionProvider);
68 protected void configureChannelUpdateHandlers(Map<String, ChannelUpdateHandler> handlers) {
69 // register default update handlers
70 handlers.put(CHANNEL_OPERATION_STATE, defaultOperationStateChannelUpdateHandler());
71 handlers.put(CHANNEL_POWER_STATE, defaultPowerStateChannelUpdateHandler());
72 handlers.put(CHANNEL_REMOTE_START_ALLOWANCE_STATE, defaultRemoteStartAllowanceChannelUpdateHandler());
73 handlers.put(CHANNEL_REMOTE_CONTROL_ACTIVE_STATE, defaultRemoteControlActiveStateChannelUpdateHandler());
74 handlers.put(CHANNEL_LOCAL_CONTROL_ACTIVE_STATE, defaultLocalControlActiveStateChannelUpdateHandler());
75 handlers.put(CHANNEL_ACTIVE_PROGRAM_STATE, defaultActiveProgramStateUpdateHandler());
76 handlers.put(CHANNEL_AMBIENT_LIGHT_STATE, defaultAmbientLightChannelUpdateHandler());
77 handlers.put(CHANNEL_FUNCTIONAL_LIGHT_STATE,
78 (channelUID, cache) -> updateState(channelUID, cache.putIfAbsentAndGet(channelUID, () -> {
79 Optional<HomeConnectApiClient> apiClient = getApiClient();
80 if (apiClient.isPresent()) {
81 Data data = apiClient.get().getFunctionalLightState(getThingHaId());
82 if (data.getValue() != null) {
83 boolean enabled = data.getValueAsBoolean();
85 Data brightnessData = apiClient.get().getFunctionalLightBrightnessState(getThingHaId());
86 getThingChannel(CHANNEL_FUNCTIONAL_LIGHT_BRIGHTNESS_STATE)
87 .ifPresent(channel -> updateState(channel.getUID(),
88 new PercentType(brightnessData.getValueAsInt())));
90 return OnOffType.from(enabled);
92 return UnDefType.UNDEF;
95 return UnDefType.UNDEF;
101 protected void configureEventHandlers(Map<String, EventHandler> handlers) {
102 // register default SSE event handlers
103 handlers.put(EVENT_REMOTE_CONTROL_START_ALLOWED,
104 defaultBooleanEventHandler(CHANNEL_REMOTE_START_ALLOWANCE_STATE));
105 handlers.put(EVENT_REMOTE_CONTROL_ACTIVE, defaultBooleanEventHandler(CHANNEL_REMOTE_CONTROL_ACTIVE_STATE));
106 handlers.put(EVENT_LOCAL_CONTROL_ACTIVE, defaultBooleanEventHandler(CHANNEL_LOCAL_CONTROL_ACTIVE_STATE));
107 handlers.put(EVENT_OPERATION_STATE, defaultOperationStateEventHandler());
108 handlers.put(EVENT_ACTIVE_PROGRAM, defaultActiveProgramEventHandler());
109 handlers.put(EVENT_POWER_STATE, defaultPowerStateEventHandler());
110 handlers.put(EVENT_FUNCTIONAL_LIGHT_STATE, defaultBooleanEventHandler(CHANNEL_FUNCTIONAL_LIGHT_STATE));
111 handlers.put(EVENT_FUNCTIONAL_LIGHT_BRIGHTNESS_STATE,
112 defaultPercentHandler(CHANNEL_FUNCTIONAL_LIGHT_BRIGHTNESS_STATE));
113 handlers.put(EVENT_AMBIENT_LIGHT_STATE, defaultBooleanEventHandler(CHANNEL_AMBIENT_LIGHT_STATE));
114 handlers.put(EVENT_AMBIENT_LIGHT_BRIGHTNESS_STATE,
115 defaultPercentHandler(CHANNEL_AMBIENT_LIGHT_BRIGHTNESS_STATE));
116 handlers.put(EVENT_AMBIENT_LIGHT_COLOR_STATE, defaultAmbientLightColorStateEventHandler());
117 handlers.put(EVENT_AMBIENT_LIGHT_CUSTOM_COLOR_STATE, defaultAmbientLightCustomColorStateEventHandler());
119 // register hood specific SSE event handlers
120 handlers.put(EVENT_HOOD_INTENSIVE_LEVEL,
121 event -> getThingChannel(CHANNEL_HOOD_INTENSIVE_LEVEL).ifPresent(channel -> {
122 String hoodIntensiveLevel = event.getValue();
123 if (hoodIntensiveLevel != null) {
124 updateState(channel.getUID(), new StringType(mapStageStringType(hoodIntensiveLevel)));
126 updateState(channel.getUID(), UnDefType.UNDEF);
129 handlers.put(EVENT_HOOD_VENTING_LEVEL,
130 event -> getThingChannel(CHANNEL_HOOD_VENTING_LEVEL).ifPresent(channel -> {
131 String hoodVentingLevel = event.getValue();
132 if (hoodVentingLevel != null) {
133 updateState(channel.getUID(), new StringType(mapStageStringType(hoodVentingLevel)));
135 updateState(channel.getUID(), UnDefType.UNDEF);
141 protected void handleCommand(final ChannelUID channelUID, final Command command,
142 final HomeConnectApiClient apiClient)
143 throws CommunicationException, AuthorizationException, ApplianceOfflineException {
144 super.handleCommand(channelUID, command, apiClient);
146 if (command instanceof OnOffType) {
147 if (CHANNEL_POWER_STATE.equals(channelUID.getId())) {
148 apiClient.setPowerState(getThingHaId(),
149 OnOffType.ON.equals(command) ? STATE_POWER_ON : STATE_POWER_OFF);
154 handleLightCommands(channelUID, command, apiClient);
157 if (command instanceof StringType && CHANNEL_HOOD_ACTIONS_STATE.equals(channelUID.getId())) {
158 String operationState = getOperationState();
159 if (OPERATION_STATE_INACTIVE.equals(operationState) || OPERATION_STATE_RUN.equals(operationState)) {
160 if (COMMAND_STOP.equalsIgnoreCase(command.toFullString())) {
161 apiClient.stopProgram(getThingHaId());
164 logger.debug("Device can not handle command {} in current operation state ({}). thing={}, haId={}",
165 command, operationState, getThingLabel(), getThingHaId());
168 // These command always start the hood - even if appliance is turned off
169 if (COMMAND_AUTOMATIC.equalsIgnoreCase(command.toFullString())) {
170 apiClient.startProgram(getThingHaId(), PROGRAM_HOOD_AUTOMATIC);
171 } else if (COMMAND_DELAYED_SHUT_OFF.equalsIgnoreCase(command.toFullString())) {
172 apiClient.startProgram(getThingHaId(), PROGRAM_HOOD_DELAYED_SHUT_OFF);
173 } else if (COMMAND_VENTING_1.equalsIgnoreCase(command.toFullString())) {
174 apiClient.startCustomProgram(getThingHaId(),
175 format(START_VENTING_STAGE_PAYLOAD_TEMPLATE, STAGE_FAN_STAGE_01));
176 } else if (COMMAND_VENTING_2.equalsIgnoreCase(command.toFullString())) {
177 apiClient.startCustomProgram(getThingHaId(),
178 format(START_VENTING_STAGE_PAYLOAD_TEMPLATE, STAGE_FAN_STAGE_02));
179 } else if (COMMAND_VENTING_3.equalsIgnoreCase(command.toFullString())) {
180 apiClient.startCustomProgram(getThingHaId(),
181 format(START_VENTING_STAGE_PAYLOAD_TEMPLATE, STAGE_FAN_STAGE_03));
182 } else if (COMMAND_VENTING_4.equalsIgnoreCase(command.toFullString())) {
183 apiClient.startCustomProgram(getThingHaId(),
184 format(START_VENTING_STAGE_PAYLOAD_TEMPLATE, STAGE_FAN_STAGE_04));
185 } else if (COMMAND_VENTING_5.equalsIgnoreCase(command.toFullString())) {
186 apiClient.startCustomProgram(getThingHaId(),
187 format(START_VENTING_STAGE_PAYLOAD_TEMPLATE, STAGE_FAN_STAGE_05));
188 } else if (COMMAND_VENTING_INTENSIVE_1.equalsIgnoreCase(command.toFullString())) {
189 apiClient.startCustomProgram(getThingHaId(),
190 format(START_VENTING_INTENSIVE_STAGE_PAYLOAD_TEMPLATE, STAGE_INTENSIVE_STAGE_1));
191 } else if (COMMAND_VENTING_INTENSIVE_2.equalsIgnoreCase(command.toFullString())) {
192 apiClient.startCustomProgram(getThingHaId(),
193 format(START_VENTING_INTENSIVE_STAGE_PAYLOAD_TEMPLATE, STAGE_INTENSIVE_STAGE_2));
195 logger.info("Start custom program. command={} haId={}", command.toFullString(), getThingHaId());
196 apiClient.startCustomProgram(getThingHaId(), command.toFullString());
202 protected void updateSelectedProgramStateDescription() {
203 // update hood program actions
204 if (isBridgeOffline() || !isThingAccessibleViaServerSentEvents()) {
208 Optional<HomeConnectApiClient> apiClient = getApiClient();
209 if (apiClient.isPresent()) {
211 ArrayList<StateOption> stateOptions = new ArrayList<>();
212 apiClient.get().getPrograms(getThingHaId()).forEach(availableProgram -> {
213 if (PROGRAM_HOOD_AUTOMATIC.equals(availableProgram.getKey())) {
214 stateOptions.add(new StateOption(COMMAND_AUTOMATIC, mapStringType(availableProgram.getKey())));
215 } else if (PROGRAM_HOOD_DELAYED_SHUT_OFF.equals(availableProgram.getKey())) {
217 new StateOption(COMMAND_DELAYED_SHUT_OFF, mapStringType(availableProgram.getKey())));
218 } else if (PROGRAM_HOOD_VENTING.equals(availableProgram.getKey())) {
220 apiClient.get().getProgramOptions(getThingHaId(), PROGRAM_HOOD_VENTING).forEach(option -> {
221 if (OPTION_HOOD_VENTING_LEVEL.equalsIgnoreCase(option.getKey())) {
222 option.getAllowedValues().stream().filter(s -> !STAGE_FAN_OFF.equalsIgnoreCase(s))
223 .forEach(s -> stateOptions.add(createVentingStateOption(s)));
224 } else if (OPTION_HOOD_INTENSIVE_LEVEL.equalsIgnoreCase(option.getKey())) {
225 option.getAllowedValues().stream()
226 .filter(s -> !STAGE_INTENSIVE_STAGE_OFF.equalsIgnoreCase(s))
227 .forEach(s -> stateOptions.add(createVentingStateOption(s)));
230 } catch (CommunicationException | ApplianceOfflineException | AuthorizationException e) {
231 logger.warn("Could not fetch hood program options. error={}", e.getMessage());
232 stateOptions.add(createVentingStateOption(STAGE_FAN_STAGE_01));
233 stateOptions.add(createVentingStateOption(STAGE_FAN_STAGE_02));
234 stateOptions.add(createVentingStateOption(STAGE_FAN_STAGE_03));
235 stateOptions.add(createVentingStateOption(STAGE_FAN_STAGE_04));
236 stateOptions.add(createVentingStateOption(STAGE_FAN_STAGE_05));
237 stateOptions.add(createVentingStateOption(STAGE_INTENSIVE_STAGE_1));
238 stateOptions.add(createVentingStateOption(STAGE_INTENSIVE_STAGE_2));
242 stateOptions.add(new StateOption(COMMAND_STOP, "Stop"));
244 getThingChannel(CHANNEL_HOOD_ACTIONS_STATE).ifPresent(channel -> getDynamicStateDescriptionProvider()
245 .setStateOptions(channel.getUID(), stateOptions));
246 } catch (CommunicationException | ApplianceOfflineException | AuthorizationException e) {
247 logger.debug("Could not fetch available programs. thing={}, haId={}, error={}", getThingLabel(),
248 getThingHaId(), e.getMessage());
249 removeSelectedProgramStateDescription();
252 removeSelectedProgramStateDescription();
257 protected void removeSelectedProgramStateDescription() {
258 getThingChannel(CHANNEL_HOOD_ACTIONS_STATE).ifPresent(
259 channel -> getDynamicStateDescriptionProvider().setStateOptions(channel.getUID(), emptyList()));
263 public String toString() {
264 return "HomeConnectHoodHandler [haId: " + getThingHaId() + "]";
268 protected void resetProgramStateChannels() {
269 super.resetProgramStateChannels();
270 getThingChannel(CHANNEL_ACTIVE_PROGRAM_STATE).ifPresent(c -> updateState(c.getUID(), UnDefType.UNDEF));
271 getThingChannel(CHANNEL_HOOD_INTENSIVE_LEVEL).ifPresent(c -> updateState(c.getUID(), UnDefType.UNDEF));
272 getThingChannel(CHANNEL_HOOD_VENTING_LEVEL).ifPresent(c -> updateState(c.getUID(), UnDefType.UNDEF));
275 private StateOption createVentingStateOption(String optionKey) {
276 String label = mapStringType(PROGRAM_HOOD_VENTING);
278 if (STAGE_FAN_STAGE_01.equalsIgnoreCase(optionKey)) {
279 return new StateOption(COMMAND_VENTING_1,
280 format("%s (Level %s)", label, mapStageStringType(STAGE_FAN_STAGE_01)));
281 } else if (STAGE_FAN_STAGE_02.equalsIgnoreCase(optionKey)) {
282 return new StateOption(COMMAND_VENTING_2,
283 format("%s (Level %s)", label, mapStageStringType(STAGE_FAN_STAGE_02)));
284 } else if (STAGE_FAN_STAGE_03.equalsIgnoreCase(optionKey)) {
285 return new StateOption(COMMAND_VENTING_3,
286 format("%s (Level %s)", label, mapStageStringType(STAGE_FAN_STAGE_03)));
287 } else if (STAGE_FAN_STAGE_04.equalsIgnoreCase(optionKey)) {
288 return new StateOption(COMMAND_VENTING_4,
289 format("%s (Level %s)", label, mapStageStringType(STAGE_FAN_STAGE_04)));
290 } else if (STAGE_FAN_STAGE_05.equalsIgnoreCase(optionKey)) {
291 return new StateOption(COMMAND_VENTING_5,
292 format("%s (Level %s)", label, mapStageStringType(STAGE_FAN_STAGE_05)));
293 } else if (STAGE_INTENSIVE_STAGE_1.equalsIgnoreCase(optionKey)) {
294 return new StateOption(COMMAND_VENTING_INTENSIVE_1,
295 format("%s (Intensive level %s)", label, mapStageStringType(STAGE_INTENSIVE_STAGE_1)));
297 return new StateOption(COMMAND_VENTING_INTENSIVE_2,
298 format("%s (Intensive level %s)", label, mapStageStringType(STAGE_INTENSIVE_STAGE_2)));