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