]> git.basschouten.com Git - openhab-addons.git/blob
98e6d856effb064249f69ec1a86b4d637d088591
[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.boschshc.internal.devices;
14
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;
22
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;
39
40 import com.google.gson.Gson;
41 import com.google.gson.JsonElement;
42
43 /**
44  * The {@link BoschSHCHandler} represents Bosch Things. Each type of device
45  * inherits from this abstract thing handler.
46  *
47  * @author Stefan Kästle - Initial contribution
48  * @author Christian Oeing - refactorings of e.g. server registration
49  */
50 @NonNullByDefault
51 public abstract class BoschSHCHandler extends BaseThingHandler {
52
53     /**
54      * Service State for a Bosch device.
55      */
56     class DeviceService<TState extends BoschSHCServiceState> {
57         /**
58          * Constructor.
59          *
60          * @param service Service which belongs to the device.
61          * @param affectedChannels Channels which are affected by the state of this service.
62          */
63         public DeviceService(BoschSHCService<TState> service, Collection<String> affectedChannels) {
64             this.service = service;
65             this.affectedChannels = affectedChannels;
66         }
67
68         /**
69          * Service which belongs to the device.
70          */
71         public final BoschSHCService<TState> service;
72
73         /**
74          * Channels which are affected by the state of this service.
75          */
76         public final Collection<String> affectedChannels;
77     }
78
79     /**
80      * Reusable gson instance to convert a class to json string and back in derived classes.
81      */
82     protected static final Gson GSON = new Gson();
83
84     protected final Logger logger = LoggerFactory.getLogger(getClass());
85
86     /**
87      * Bosch SHC configuration loaded from openHAB configuration.
88      */
89     private @Nullable BoschSHCConfiguration config;
90
91     /**
92      * Services of the device.
93      */
94     private List<DeviceService<? extends BoschSHCServiceState>> services = new ArrayList<>();
95
96     public BoschSHCHandler(Thing thing) {
97         super(thing);
98     }
99
100     /**
101      * Returns the unique id of the Bosch device.
102      *
103      * @return Unique id of the Bosch device.
104      */
105     public @Nullable String getBoschID() {
106         BoschSHCConfiguration config = this.config;
107         if (config != null) {
108             return config.id;
109         } else {
110             return null;
111         }
112     }
113
114     /**
115      * Initializes this handler. Use this method to register all services of the device with
116      * {@link #registerService(BoschSHCService)}.
117      */
118     @Override
119     public void initialize() {
120         this.config = getConfigAs(BoschSHCConfiguration.class);
121
122         try {
123             this.initializeServices();
124
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());
129         }
130     }
131
132     /**
133      * Handles the refresh command of all registered services. Override it to handle custom commands (e.g. to update
134      * states of services).
135      *
136      * @param channelUID {@link ChannelUID} of the channel to which the command was sent
137      * @param command {@link Command}
138      */
139     @Override
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())) {
145                     try {
146                         deviceService.service.refreshState();
147                     } catch (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()));
151                     } catch (InterruptedException e) {
152                         this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
153                                 String.format("Interrupted refresh state from service %s: %s",
154                                         deviceService.service.getServiceName(), e.getMessage()));
155                         Thread.currentThread().interrupt();
156                     }
157                 }
158             }
159         }
160     }
161
162     /**
163      * Processes an update which is received from the bridge.
164      *
165      * @param serviceName Name of service the update came from.
166      * @param stateData Current state of device service. Serialized as JSON.
167      */
168     public void processUpdate(String serviceName, JsonElement stateData) {
169         // Check services of device to correctly
170         for (DeviceService<? extends BoschSHCServiceState> deviceService : this.services) {
171             BoschSHCService<? extends BoschSHCServiceState> service = deviceService.service;
172             if (serviceName.equals(service.getServiceName())) {
173                 service.onStateUpdate(stateData);
174             }
175         }
176     }
177
178     /**
179      * Should be used by handlers to create their required services.
180      */
181     protected void initializeServices() throws BoschSHCException {
182     }
183
184     /**
185      * Returns the bridge handler for this thing handler.
186      *
187      * @return Bridge handler for this thing handler. Null if no or an invalid bridge was set in the configuration.
188      * @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set.
189      */
190     protected BoschSHCBridgeHandler getBridgeHandler() throws BoschSHCException {
191         Bridge bridge = this.getBridge();
192         if (bridge == null) {
193             throw new BoschSHCException(String.format("No valid bridge set for %s", this.getThing()));
194         }
195         BoschSHCBridgeHandler bridgeHandler = (BoschSHCBridgeHandler) bridge.getHandler();
196         if (bridgeHandler == null) {
197             throw new BoschSHCException(String.format("Bridge of %s has no valid bridge handler", this.getThing()));
198         }
199         return bridgeHandler;
200     }
201
202     /**
203      * Query the Bosch Smart Home Controller for the state of the service with the specified name.
204      *
205      * @note Use services instead of directly requesting a state.
206      *
207      * @param stateName Name of the service to query
208      * @param classOfT Class to convert the resulting JSON to
209      */
210     protected <T extends BoschSHCServiceState> @Nullable T getState(String stateName, Class<T> classOfT) {
211         String deviceId = this.getBoschID();
212         if (deviceId == null) {
213             return null;
214         }
215         try {
216             BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler();
217             return bridgeHandler.getState(deviceId, stateName, classOfT);
218         } catch (TimeoutException | ExecutionException | BoschSHCException e) {
219             this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
220                     String.format("Error when trying to refresh state from service %s: %s", stateName, e.getMessage()));
221             return null;
222         } catch (InterruptedException e) {
223             this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
224                     String.format("Interrupted refresh state from service %s: %s", stateName, e.getMessage()));
225             Thread.currentThread().interrupt();
226             return null;
227         }
228     }
229
230     /**
231      * Creates and registers a new service for this device.
232      *
233      * @param <TService> Type of service.
234      * @param <TState> Type of service state.
235      * @param newService Supplier function to create a new instance of the service.
236      * @param stateUpdateListener Function to call when a state update was received
237      *            from the device.
238      * @param affectedChannels Channels which are affected by the state of this
239      *            service.
240      * @return Instance of registered service.
241      * @throws BoschSHCException
242      */
243     protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> TService createService(
244             Supplier<TService> newService, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels)
245             throws BoschSHCException {
246         TService service = newService.get();
247         this.registerService(service, stateUpdateListener, affectedChannels);
248         return service;
249     }
250
251     /**
252      * Registers a service for this device.
253      *
254      * @param <TService> Type of service.
255      * @param <TState> Type of service state.
256      * @param service Service to register.
257      * @param stateUpdateListener Function to call when a state update was received
258      *            from the device.
259      * @param affectedChannels Channels which are affected by the state of this
260      *            service.
261      * @throws BoschSHCException If bridge for handler is not set or an invalid bridge is set.
262      * @throws BoschSHCException If no device id is set.
263      */
264     protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void registerService(
265             TService service, Consumer<TState> stateUpdateListener, Collection<String> affectedChannels)
266             throws BoschSHCException {
267         BoschSHCBridgeHandler bridgeHandler = this.getBridgeHandler();
268
269         String deviceId = this.getBoschID();
270         if (deviceId == null) {
271             throw new BoschSHCException(
272                     String.format("Could not register service for %s, no device id set", this.getThing()));
273         }
274
275         service.initialize(bridgeHandler, deviceId, stateUpdateListener);
276         this.registerService(service, affectedChannels);
277     }
278
279     /**
280      * Updates the state of a device service.
281      * Sets the status of the device to offline if setting the state fails.
282      *
283      * @param <TService> Type of service.
284      * @param <TState> Type of service state.
285      * @param service Service to set state for.
286      * @param state State to set.
287      */
288     protected <TService extends BoschSHCService<TState>, TState extends BoschSHCServiceState> void updateServiceState(
289             TService service, TState state) {
290         try {
291             service.setState(state);
292         } catch (TimeoutException | ExecutionException e) {
293             this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, String.format(
294                     "Error when trying to update state for service %s: %s", service.getServiceName(), e.getMessage()));
295         } catch (InterruptedException e) {
296             this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, String
297                     .format("Interrupted update state for service %s: %s", service.getServiceName(), e.getMessage()));
298             Thread.currentThread().interrupt();
299         }
300     }
301
302     /**
303      * Registers a service of this device.
304      *
305      * @param service Service which belongs to this device
306      * @param affectedChannels Channels which are affected by the state of this
307      *            service
308      */
309     private <TState extends BoschSHCServiceState> void registerService(BoschSHCService<TState> service,
310             Collection<String> affectedChannels) {
311         this.services.add(new DeviceService<TState>(service, affectedChannels));
312     }
313 }