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.boschshc.internal.devices;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.List;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.TimeoutException;
20 import java.util.function.Consumer;
21 import java.util.function.Supplier;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.boschshc.internal.devices.bridge.BoschSHCBridgeHandler;
26 import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
27 import org.openhab.binding.boschshc.internal.services.BoschSHCService;
28 import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.binding.BaseThingHandler;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.RefreshType;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 import com.google.gson.Gson;
41 import com.google.gson.JsonElement;
44 * The {@link BoschSHCHandler} represents Bosch Things. Each type of device
45 * inherits from this abstract thing handler.
47 * @author Stefan Kästle - Initial contribution
48 * @author Christian Oeing - refactorings of e.g. server registration
51 public abstract class BoschSHCHandler extends BaseThingHandler {
54 * Service State for a Bosch device.
56 class DeviceService<TState extends BoschSHCServiceState> {
60 * @param service Service which belongs to the device.
61 * @param affectedChannels Channels which are affected by the state of this service.
63 public DeviceService(BoschSHCService<TState> service, Collection<String> affectedChannels) {
64 this.service = service;
65 this.affectedChannels = affectedChannels;
69 * Service which belongs to the device.
71 public final BoschSHCService<TState> service;
74 * Channels which are affected by the state of this service.
76 public final Collection<String> affectedChannels;
80 * Reusable gson instance to convert a class to json string and back in derived classes.
82 protected static final Gson GSON = new Gson();
84 protected final Logger logger = LoggerFactory.getLogger(getClass());
87 * Bosch SHC configuration loaded from openHAB configuration.
89 private @Nullable BoschSHCConfiguration config;
92 * Services of the device.
94 private List<DeviceService<? extends BoschSHCServiceState>> services = new ArrayList<>();
96 public BoschSHCHandler(Thing thing) {
101 * Returns the unique id of the Bosch device.
103 * @return Unique id of the Bosch device.
105 public @Nullable String getBoschID() {
106 BoschSHCConfiguration config = this.config;
107 if (config != null) {
115 * Initializes this handler. Use this method to register all services of the device with
116 * {@link #registerService(BoschSHCService)}.
119 public void initialize() {
120 this.config = getConfigAs(BoschSHCConfiguration.class);
123 this.initializeServices();
125 // Mark immediately as online - if the bridge is online, the thing is too.
126 this.updateStatus(ThingStatus.ONLINE);
127 } catch (BoschSHCException e) {
128 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
133 * Handles the refresh command of all registered services. Override it to handle custom commands (e.g. to update
134 * states of services).
136 * @param channelUID {@link ChannelUID} of the channel to which the command was sent
137 * @param command {@link Command}
140 public void handleCommand(ChannelUID channelUID, Command command) {
141 if (command instanceof RefreshType) {
142 // Refresh state of services that affect the channel
143 for (DeviceService<? extends BoschSHCServiceState> deviceService : this.services) {
144 if (deviceService.affectedChannels.contains(channelUID.getIdWithoutGroup())) {
146 deviceService.service.refreshState();
147 } catch (InterruptedException | TimeoutException | ExecutionException | BoschSHCException e) {
148 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
149 String.format("Error when trying to refresh state from service %s: %s",
150 deviceService.service.getServiceName(), e.getMessage()));
158 * Processes an update which is received from the bridge.
160 * @param serviceName Name of service the update came from.
161 * @param stateData Current state of device service. Serialized as JSON.
163 public void processUpdate(String serviceName, JsonElement stateData) {
164 // Check services of device to correctly
165 for (DeviceService<? extends BoschSHCServiceState> deviceService : this.services) {
166 BoschSHCService<? extends BoschSHCServiceState> service = deviceService.service;
167 if (serviceName.equals(service.getServiceName())) {
168 service.onStateUpdate(stateData);
174 * Should be used by handlers to create their required services.
176 protected void initializeServices() throws BoschSHCException {
180 * Returns the bridge handler for this thing handler.
182 * @return Bridge handler for this thing handler. Null if no or an invalid bridge was set in the configuration.
183 * @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set.
185 protected BoschSHCBridgeHandler getBridgeHandler() throws BoschSHCException {
186 Bridge bridge = this.getBridge();
187 if (bridge == null) {
188 throw new BoschSHCException(String.format("No valid bridge set for %s", this.getThing()));
190 BoschSHCBridgeHandler bridgeHandler = (BoschSHCBridgeHandler) bridge.getHandler();
191 if (bridgeHandler == null) {
192 throw new BoschSHCException(String.format("Bridge of %s has no valid bridge handler", this.getThing()));
194 return bridgeHandler;
198 * Query the Bosch Smart Home Controller for the state of the service with the specified name.
200 * @note Use services instead of directly requesting a state.
202 * @param stateName Name of the service to query
203 * @param classOfT Class to convert the resulting JSON to
205 protected <T extends BoschSHCServiceState> @Nullable T getState(String stateName, Class<T> classOfT) {
206 String deviceId = this.getBoschID();
207 if (deviceId == null) {
211 BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler();
212 return bridgeHandler.getState(deviceId, stateName, classOfT);
213 } catch (InterruptedException | TimeoutException | ExecutionException | BoschSHCException e) {
214 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
215 String.format("Error when trying to refresh state from service %s: %s", stateName, e.getMessage()));
221 * Creates and registers a new service for this device.
223 * @param <TService> Type of service.
224 * @param <TState> Type of service state.
225 * @param newService Supplier function to create a new instance of the service.
226 * @param stateUpdateListener Function to call when a state update was received
228 * @param affectedChannels Channels which are affected by the state of this
230 * @return Instance of registered service.
231 * @throws BoschSHCException
233 protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> TService createService(
234 Supplier<TService> newService, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels)
235 throws BoschSHCException {
236 TService service = newService.get();
237 this.registerService(service, stateUpdateListener, affectedChannels);
242 * Registers a service for this device.
244 * @param <TService> Type of service.
245 * @param <TState> Type of service state.
246 * @param service Service to register.
247 * @param stateUpdateListener Function to call when a state update was received
249 * @param affectedChannels Channels which are affected by the state of this
251 * @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set.
252 * @throws BoschSHCException If no device id is set.
254 protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void registerService(
255 TService service, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels)
256 throws BoschSHCException {
257 BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler();
259 String deviceId = this.getBoschID();
260 if (deviceId == null) {
261 throw new BoschSHCException(
262 String.format("Could not register service for %s, no device id set", this.getThing()));
265 service.initialize(bridgeHandler, deviceId, stateUpdateListener);
266 this.registerService(service, affectedChannels);
270 * Updates the state of a device service.
271 * Sets the status of the device to offline if setting the state fails.
273 * @param <TService> Type of service.
274 * @param <TState> Type of service state.
275 * @param service Service to set state for.
276 * @param state State to set.
278 protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void updateServiceState(
279 TService service, TState state) {
281 service.setState(state);
282 } catch (InterruptedException | TimeoutException | ExecutionException e) {
283 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, String.format(
284 "Error when trying to update state for service %s: %s", service.getServiceName(), e.getMessage()));
289 * Registers a service of this device.
291 * @param service Service which belongs to this device
292 * @param affectedChannels Channels which are affected by the state of this
295 private <TState extends BoschSHCServiceState> void registerService(BoschSHCService<TState> service,
296 Collection<String> affectedChannels) {
297 this.services.add(new DeviceService<TState>(service, affectedChannels));