]> git.basschouten.com Git - openhab-addons.git/blob
937d6b069a06dbe2732acc9b6b1d02a16d7a026b
[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.mielecloud.internal.handler;
14
15 import static org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants.Channels.*;
16 import static org.openhab.binding.mielecloud.internal.webservice.api.PowerStatus.*;
17 import static org.openhab.binding.mielecloud.internal.webservice.api.ProgramStatus.*;
18 import static org.openhab.binding.mielecloud.internal.webservice.api.json.ProcessAction.*;
19
20 import java.util.Optional;
21 import java.util.function.Consumer;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants;
25 import org.openhab.binding.mielecloud.internal.MieleCloudBindingConstants.I18NKeys;
26 import org.openhab.binding.mielecloud.internal.discovery.ThingInformationExtractor;
27 import org.openhab.binding.mielecloud.internal.handler.channel.ActionsChannelState;
28 import org.openhab.binding.mielecloud.internal.handler.channel.DeviceChannelState;
29 import org.openhab.binding.mielecloud.internal.handler.channel.TransitionChannelState;
30 import org.openhab.binding.mielecloud.internal.webservice.ActionStateFetcher;
31 import org.openhab.binding.mielecloud.internal.webservice.MieleWebservice;
32 import org.openhab.binding.mielecloud.internal.webservice.UnavailableMieleWebservice;
33 import org.openhab.binding.mielecloud.internal.webservice.api.ActionsState;
34 import org.openhab.binding.mielecloud.internal.webservice.api.DeviceState;
35 import org.openhab.binding.mielecloud.internal.webservice.api.PowerStatus;
36 import org.openhab.binding.mielecloud.internal.webservice.api.ProgramStatus;
37 import org.openhab.binding.mielecloud.internal.webservice.api.TransitionState;
38 import org.openhab.binding.mielecloud.internal.webservice.api.json.ProcessAction;
39 import org.openhab.binding.mielecloud.internal.webservice.api.json.StateType;
40 import org.openhab.binding.mielecloud.internal.webservice.exception.TooManyRequestsException;
41 import org.openhab.core.library.types.OnOffType;
42 import org.openhab.core.thing.Bridge;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.binding.BaseThingHandler;
48 import org.openhab.core.thing.binding.BridgeHandler;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.types.RefreshType;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * Abstract base class for all Miele thing handlers.
56  *
57  * @author Roland Edelhoff - Initial contribution
58  * @author Björn Lange - Add channel state wrappers
59  */
60 @NonNullByDefault
61 public abstract class AbstractMieleThingHandler extends BaseThingHandler {
62     protected final ActionStateFetcher actionFetcher;
63     protected DeviceState latestDeviceState = new DeviceState(getDeviceId(), null);
64     protected TransitionState latestTransitionState = new TransitionState(null, latestDeviceState);
65     protected ActionsState latestActionsState = new ActionsState(getDeviceId(), null);
66
67     private final Logger logger = LoggerFactory.getLogger(this.getClass());
68
69     /**
70      * Creates a new {@link AbstractMieleThingHandler}.
71      *
72      * @param thing The thing to handle.
73      */
74     public AbstractMieleThingHandler(Thing thing) {
75         super(thing);
76         this.actionFetcher = new ActionStateFetcher(this::getWebservice, scheduler);
77     }
78
79     private Optional<MieleBridgeHandler> getMieleBridgeHandler() {
80         Bridge bridge = getBridge();
81         if (bridge == null) {
82             return Optional.empty();
83         }
84
85         BridgeHandler handler = bridge.getHandler();
86         if (handler == null || !(handler instanceof MieleBridgeHandler)) {
87             return Optional.empty();
88         }
89
90         return Optional.of((MieleBridgeHandler) handler);
91     }
92
93     protected MieleWebservice getWebservice() {
94         return getMieleBridgeHandler().map(MieleBridgeHandler::getWebservice)
95                 .orElse(UnavailableMieleWebservice.INSTANCE);
96     }
97
98     @Override
99     public void initialize() {
100         getWebservice().dispatchDeviceState(getDeviceId());
101
102         // If no device state update was received so far, set the device to OFFLINE.
103         if (getThing().getStatus() == ThingStatus.INITIALIZING) {
104             updateStatus(ThingStatus.OFFLINE);
105         }
106     }
107
108     @Override
109     public void handleCommand(ChannelUID channelUID, Command command) {
110         if (RefreshType.REFRESH.equals(command)) {
111             updateDeviceState(new DeviceChannelState(latestDeviceState));
112             updateTransitionState(new TransitionChannelState(latestTransitionState));
113             updateActionState(new ActionsChannelState(latestActionsState));
114         }
115
116         switch (channelUID.getId()) {
117             case PROGRAM_START_STOP:
118                 if (PROGRAM_STARTED.matches(command.toString())) {
119                     triggerProcessAction(START);
120                 } else if (PROGRAM_STOPPED.matches(command.toString())) {
121                     triggerProcessAction(STOP);
122                 }
123                 break;
124
125             case PROGRAM_START_STOP_PAUSE:
126                 if (PROGRAM_STARTED.matches(command.toString())) {
127                     triggerProcessAction(START);
128                 } else if (PROGRAM_STOPPED.matches(command.toString())) {
129                     triggerProcessAction(STOP);
130                 } else if (PROGRAM_PAUSED.matches(command.toString())) {
131                     triggerProcessAction(PAUSE);
132                 }
133                 break;
134
135             case LIGHT_SWITCH:
136                 if (command instanceof OnOffType) {
137                     triggerLight(OnOffType.ON.equals(command));
138                 }
139                 break;
140
141             case POWER_ON_OFF:
142                 if (POWER_ON.matches(command.toString()) || POWER_OFF.matches(command.toString())) {
143                     triggerPowerState(OnOffType.ON.equals(OnOffType.from(command.toString())));
144                 }
145                 break;
146         }
147     }
148
149     @Override
150     public void dispose() {
151     }
152
153     /**
154      * Invoked when an update of the available actions for the device managed by this handler is received from the Miele
155      * cloud.
156      */
157     public final void onProcessActionUpdated(ActionsState actionState) {
158         latestActionsState = actionState;
159         updateActionState(new ActionsChannelState(latestActionsState));
160     }
161
162     /**
163      * Invoked when the device managed by this handler was removed from the Miele cloud.
164      */
165     public final void onDeviceRemoved() {
166         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, I18NKeys.THING_STATUS_DESCRIPTION_REMOVED);
167     }
168
169     /**
170      * Invoked when a device state update for the device managed by this handler is received from the Miele cloud.
171      */
172     public final void onDeviceStateUpdated(DeviceState deviceState) {
173         actionFetcher.onDeviceStateUpdated(deviceState);
174
175         latestTransitionState = new TransitionState(latestTransitionState, deviceState);
176         latestDeviceState = deviceState;
177
178         updateThingProperties(deviceState);
179         updateDeviceState(new DeviceChannelState(latestDeviceState));
180         updateTransitionState(new TransitionChannelState(latestTransitionState));
181         updateThingStatus(latestDeviceState);
182     }
183
184     protected void triggerProcessAction(final ProcessAction processAction) {
185         performPutAction(() -> getWebservice().putProcessAction(getDeviceId(), processAction),
186                 t -> logger.warn("Failed to perform '{}' operation for device '{}'.", processAction, getDeviceId(), t));
187     }
188
189     protected void triggerLight(final boolean on) {
190         performPutAction(() -> getWebservice().putLight(getDeviceId(), on),
191                 t -> logger.warn("Failed to set light state to '{}' for device '{}'.", on, getDeviceId(), t));
192     }
193
194     protected void triggerPowerState(final boolean on) {
195         performPutAction(() -> getWebservice().putPowerState(getDeviceId(), on),
196                 t -> logger.warn("Failed to set the power state to '{}' for device '{}'.", on, getDeviceId(), t));
197     }
198
199     protected void triggerProgram(final long programId) {
200         performPutAction(() -> getWebservice().putProgram(getDeviceId(), programId), t -> logger
201                 .warn("Failed to activate program with ID '{}' for device '{}'.", programId, getDeviceId(), t));
202     }
203
204     private void performPutAction(Runnable action, Consumer<Exception> onError) {
205         scheduler.execute(() -> {
206             try {
207                 action.run();
208             } catch (TooManyRequestsException e) {
209                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
210                         I18NKeys.THING_STATUS_DESCRIPTION_RATELIMIT);
211                 onError.accept(e);
212             } catch (Exception e) {
213                 onError.accept(e);
214             }
215         });
216     }
217
218     protected final String getDeviceId() {
219         return getConfig().get(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER).toString();
220     }
221
222     /**
223      * Creates a {@link ChannelUID} from the given name.
224      *
225      * @param name channel name
226      * @return {@link ChannelUID}
227      */
228     protected ChannelUID channel(String name) {
229         return new ChannelUID(getThing().getUID(), name);
230     }
231
232     /**
233      * Updates the thing status depending on whether the managed device is connected and reachable.
234      */
235     private void updateThingStatus(DeviceState deviceState) {
236         if (deviceState.isInState(StateType.NOT_CONNECTED)) {
237             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
238                     I18NKeys.THING_STATUS_DESCRIPTION_DISCONNECTED);
239         } else {
240             updateStatus(ThingStatus.ONLINE);
241         }
242     }
243
244     /**
245      * Determines the status of the currently selected program.
246      */
247     protected ProgramStatus getProgramStatus(StateType rawStatus) {
248         if (rawStatus.equals(StateType.RUNNING)) {
249             return PROGRAM_STARTED;
250         }
251         return PROGRAM_STOPPED;
252     }
253
254     /**
255      * Determines the power status of the managed device.
256      */
257     protected PowerStatus getPowerStatus(StateType rawStatus) {
258         if (rawStatus.equals(StateType.OFF) || rawStatus.equals(StateType.NOT_CONNECTED)) {
259             return POWER_OFF;
260         }
261         return POWER_ON;
262     }
263
264     /**
265      * Updates the thing properties. This is necessary if properties have not been set during discovery.
266      */
267     private void updateThingProperties(DeviceState deviceState) {
268         var properties = editProperties();
269         properties.putAll(ThingInformationExtractor.extractProperties(getThing().getThingTypeUID(), deviceState));
270         updateProperties(properties);
271     }
272
273     /**
274      * Updates the device state channels.
275      *
276      * @param device The {@link DeviceChannelState} information to update the device channel states with.
277      */
278     protected abstract void updateDeviceState(DeviceChannelState device);
279
280     /**
281      * Updates the transition state channels.
282      *
283      * @param transition The {@link TransitionChannelState} information to update the transition channel states with.
284      */
285     protected abstract void updateTransitionState(TransitionChannelState transition);
286
287     /**
288      * Updates the device action state channels.
289      *
290      * @param action The {@link ActionsChannelState} information to update the action channel states with.
291      */
292     protected abstract void updateActionState(ActionsChannelState actions);
293 }