]> git.basschouten.com Git - openhab-addons.git/blob
eef8e5eb63e61adaac8df3a00fef72480ca17099
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.homeconnect.internal.handler;
14
15 import static java.lang.String.format;
16 import static java.util.Collections.emptyList;
17 import static org.openhab.binding.homeconnect.internal.HomeConnectBindingConstants.*;
18
19 import java.util.ArrayList;
20 import java.util.Map;
21 import java.util.Optional;
22
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;
40
41 /**
42  * The {@link HomeConnectHoodHandler} is responsible for handling commands, which are
43  * sent to one of the channels of a hood.
44  *
45  * @author Jonas BrĂ¼stel - Initial contribution
46  */
47 @NonNullByDefault
48 public class HomeConnectHoodHandler extends AbstractHomeConnectThingHandler {
49
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" + "}";
54
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" + "}";
59
60     private final Logger logger = LoggerFactory.getLogger(HomeConnectHoodHandler.class);
61
62     public HomeConnectHoodHandler(Thing thing,
63             HomeConnectDynamicStateDescriptionProvider dynamicStateDescriptionProvider) {
64         super(thing, dynamicStateDescriptionProvider);
65     }
66
67     @Override
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();
84                             if (enabled) {
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())));
89                             }
90                             return OnOffType.from(enabled);
91                         } else {
92                             return UnDefType.UNDEF;
93                         }
94                     } else {
95                         return UnDefType.UNDEF;
96                     }
97                 })));
98     }
99
100     @Override
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());
118
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)));
125                     } else {
126                         updateState(channel.getUID(), UnDefType.UNDEF);
127                     }
128                 }));
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)));
134                     } else {
135                         updateState(channel.getUID(), UnDefType.UNDEF);
136                     }
137                 }));
138     }
139
140     @Override
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);
145
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);
150             }
151         }
152
153         // light commands
154         handleLightCommands(channelUID, command, apiClient);
155
156         // program options
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());
162                 }
163             } else {
164                 logger.debug("Device can not handle command {} in current operation state ({}). thing={}, haId={}",
165                         command, operationState, getThingLabel(), getThingHaId());
166             }
167
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));
194             } else {
195                 logger.info("Start custom program. command={} haId={}", command.toFullString(), getThingHaId());
196                 apiClient.startCustomProgram(getThingHaId(), command.toFullString());
197             }
198         }
199     }
200
201     @Override
202     protected void updateSelectedProgramStateDescription() {
203         // update hood program actions
204         if (isBridgeOffline() || !isThingAccessibleViaServerSentEvents()) {
205             return;
206         }
207
208         Optional<HomeConnectApiClient> apiClient = getApiClient();
209         if (apiClient.isPresent()) {
210             try {
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())) {
216                         stateOptions.add(
217                                 new StateOption(COMMAND_DELAYED_SHUT_OFF, mapStringType(availableProgram.getKey())));
218                     } else if (PROGRAM_HOOD_VENTING.equals(availableProgram.getKey())) {
219                         try {
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)));
228                                 }
229                             });
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));
239                         }
240                     }
241                 });
242                 stateOptions.add(new StateOption(COMMAND_STOP, "Stop"));
243
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();
250             }
251         } else {
252             removeSelectedProgramStateDescription();
253         }
254     }
255
256     @Override
257     protected void removeSelectedProgramStateDescription() {
258         getThingChannel(CHANNEL_HOOD_ACTIONS_STATE).ifPresent(
259                 channel -> getDynamicStateDescriptionProvider().setStateOptions(channel.getUID(), emptyList()));
260     }
261
262     @Override
263     public String toString() {
264         return "HomeConnectHoodHandler [haId: " + getThingHaId() + "]";
265     }
266
267     @Override
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));
273     }
274
275     private StateOption createVentingStateOption(String optionKey) {
276         String label = mapStringType(PROGRAM_HOOD_VENTING);
277
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)));
296         } else {
297             return new StateOption(COMMAND_VENTING_INTENSIVE_2,
298                     format("%s (Intensive level %s)", label, mapStageStringType(STAGE_INTENSIVE_STAGE_2)));
299         }
300     }
301 }