2 * Copyright (c) 2010-2023 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.mielecloud.internal.handler;
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.*;
20 import java.util.Optional;
21 import java.util.function.Consumer;
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;
54 * Abstract base class for all Miele thing handlers.
56 * @author Roland Edelhoff - Initial contribution
57 * @author Björn Lange - Add channel state wrappers
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);
65 private final Logger logger = LoggerFactory.getLogger(this.getClass());
68 * Creates a new {@link AbstractMieleThingHandler}.
70 * @param thing The thing to handle.
72 public AbstractMieleThingHandler(Thing thing) {
76 private Optional<MieleBridgeHandler> getMieleBridgeHandler() {
77 Bridge bridge = getBridge();
79 return Optional.empty();
82 BridgeHandler handler = bridge.getHandler();
83 if (!(handler instanceof MieleBridgeHandler)) {
84 return Optional.empty();
87 return Optional.of((MieleBridgeHandler) handler);
90 protected MieleWebservice getWebservice() {
91 return getMieleBridgeHandler().map(MieleBridgeHandler::getWebservice)
92 .orElse(UnavailableMieleWebservice.INSTANCE);
96 public void initialize() {
97 getWebservice().dispatchDeviceState(getDeviceId());
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);
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));
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);
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);
133 if (command instanceof OnOffType) {
134 triggerLight(OnOffType.ON.equals(command));
139 if (POWER_ON.matches(command.toString()) || POWER_OFF.matches(command.toString())) {
140 triggerPowerState(OnOffType.ON.equals(OnOffType.from(command.toString())));
147 public void dispose() {
151 * Invoked when an update of the available actions for the device managed by this handler is received from the Miele
154 public final void onProcessActionUpdated(ActionsState actionState) {
155 latestActionsState = actionState;
156 updateActionState(new ActionsChannelState(latestActionsState));
160 * Invoked when the device managed by this handler was removed from the Miele cloud.
162 public final void onDeviceRemoved() {
163 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, I18NKeys.THING_STATUS_DESCRIPTION_REMOVED);
167 * Invoked when a device state update for the device managed by this handler is received from the Miele cloud.
169 public final void onDeviceStateUpdated(DeviceState deviceState) {
170 latestTransitionState = new TransitionState(latestTransitionState, deviceState);
171 latestDeviceState = deviceState;
173 updateThingProperties(deviceState);
174 updateDeviceState(new DeviceChannelState(latestDeviceState));
175 updateTransitionState(new TransitionChannelState(latestTransitionState));
176 updateThingStatus(latestDeviceState);
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));
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));
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));
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));
199 private void performPutAction(Runnable action, Consumer<Exception> onError) {
200 scheduler.execute(() -> {
203 } catch (TooManyRequestsException e) {
204 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
205 I18NKeys.THING_STATUS_DESCRIPTION_RATELIMIT);
207 } catch (Exception e) {
213 protected final String getDeviceId() {
214 return getConfig().get(MieleCloudBindingConstants.CONFIG_PARAM_DEVICE_IDENTIFIER).toString();
218 * Creates a {@link ChannelUID} from the given name.
220 * @param name channel name
221 * @return {@link ChannelUID}
223 protected ChannelUID channel(String name) {
224 return new ChannelUID(getThing().getUID(), name);
228 * Updates the thing status depending on whether the managed device is connected and reachable.
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);
235 updateStatus(ThingStatus.ONLINE);
240 * Determines the status of the currently selected program.
242 protected ProgramStatus getProgramStatus(StateType rawStatus) {
243 if (rawStatus.equals(StateType.RUNNING)) {
244 return PROGRAM_STARTED;
246 return PROGRAM_STOPPED;
250 * Determines the power status of the managed device.
252 protected PowerStatus getPowerStatus(StateType rawStatus) {
253 if (rawStatus.equals(StateType.OFF) || rawStatus.equals(StateType.NOT_CONNECTED)) {
260 * Updates the thing properties. This is necessary if properties have not been set during discovery.
262 private void updateThingProperties(DeviceState deviceState) {
263 var properties = editProperties();
264 properties.putAll(ThingInformationExtractor.extractProperties(getThing().getThingTypeUID(), deviceState));
265 updateProperties(properties);
269 * Updates the device state channels.
271 * @param device The {@link DeviceChannelState} information to update the device channel states with.
273 protected abstract void updateDeviceState(DeviceChannelState device);
276 * Updates the transition state channels.
278 * @param transition The {@link TransitionChannelState} information to update the transition channel states with.
280 protected abstract void updateTransitionState(TransitionChannelState transition);
283 * Updates the device action state channels.
285 * @param actions The {@link ActionsChannelState} information to update the action channel states with.
287 protected abstract void updateActionState(ActionsChannelState actions);