]> git.basschouten.com Git - openhab-addons.git/blob
94174ba16348a42fec93a2f9767b332cf6b95320
[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.hdpowerview.internal;
14
15 import java.time.Instant;
16
17 import javax.ws.rs.ProcessingException;
18 import javax.ws.rs.client.Client;
19 import javax.ws.rs.client.Entity;
20 import javax.ws.rs.client.Invocation;
21 import javax.ws.rs.client.WebTarget;
22 import javax.ws.rs.core.MediaType;
23 import javax.ws.rs.core.Response;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.hdpowerview.internal.api.ShadePosition;
28 import org.openhab.binding.hdpowerview.internal.api.requests.ShadeMove;
29 import org.openhab.binding.hdpowerview.internal.api.requests.ShadeStop;
30 import org.openhab.binding.hdpowerview.internal.api.responses.Scenes;
31 import org.openhab.binding.hdpowerview.internal.api.responses.Shade;
32 import org.openhab.binding.hdpowerview.internal.api.responses.Shades;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import com.google.gson.Gson;
37 import com.google.gson.JsonParseException;
38
39 /**
40  * JAX-RS targets for communicating with an HD PowerView hub
41  *
42  * @author Andy Lintner - Initial contribution
43  * @author Andrew Fiddian-Green - Added support for secondary rail positions
44  */
45 @NonNullByDefault
46 public class HDPowerViewWebTargets {
47
48     private static final String PUT = "PUT";
49     private static final String GET = "GET";
50     private static final String SCENE_ID = "sceneId";
51     private static final String ID = "id";
52     private static final String REFRESH = "refresh";
53     private static final String CONN_HDR = "Connection";
54     private static final String CONN_VAL = "close"; // versus "keep-alive"
55
56     private final Logger logger = LoggerFactory.getLogger(HDPowerViewWebTargets.class);
57
58     /*
59      * the hub returns a 423 error (resource locked) daily just after midnight;
60      * which means it is temporarily undergoing maintenance; so we use "soft"
61      * exception handling during the five minute maintenance period after a 423
62      * error is received
63      */
64     private final int maintenancePeriod = 300;
65     private Instant maintenanceScheduledEnd = Instant.now().minusSeconds(2 * maintenancePeriod);
66
67     private WebTarget base;
68     private WebTarget shades;
69     private WebTarget shade;
70     private WebTarget sceneActivate;
71     private WebTarget scenes;
72
73     private final Gson gson = new Gson();
74
75     /**
76      * Initialize the web targets
77      * 
78      * @param client the Javax RS client (the binding)
79      * @param ipAddress the IP address of the server (the hub)
80      */
81     public HDPowerViewWebTargets(Client client, String ipAddress) {
82         base = client.target("http://" + ipAddress + "/api");
83         shades = base.path("shades/");
84         shade = base.path("shades/{id}");
85         sceneActivate = base.path("scenes");
86         scenes = base.path("scenes/");
87     }
88
89     /**
90      * Fetches a JSON package that describes all shades in the hub, and wraps it in
91      * a Shades class instance
92      * 
93      * @return Shades class instance
94      * @throws JsonParseException if there is a JSON parsing error
95      * @throws ProcessingException if there is any processing error
96      * @throws HubMaintenanceException if the hub is down for maintenance
97      */
98     public @Nullable Shades getShades() throws JsonParseException, ProcessingException, HubMaintenanceException {
99         String json = invoke(shades.request().header(CONN_HDR, CONN_VAL).buildGet(), shades, null);
100         return gson.fromJson(json, Shades.class);
101     }
102
103     /**
104      * Instructs the hub to move a specific shade
105      * 
106      * @param shadeId id of the shade to be moved
107      * @param position instance of ShadePosition containing the new position
108      * @throws ProcessingException if there is any processing error
109      * @throws HubMaintenanceException if the hub is down for maintenance
110      */
111     public void moveShade(int shadeId, ShadePosition position) throws ProcessingException, HubMaintenanceException {
112         WebTarget target = shade.resolveTemplate(ID, shadeId);
113         String json = gson.toJson(new ShadeMove(shadeId, position));
114         invoke(target.request().header(CONN_HDR, CONN_VAL)
115                 .buildPut(Entity.entity(json, MediaType.APPLICATION_JSON_TYPE)), target, json);
116         return;
117     }
118
119     /**
120      * Fetches a JSON package that describes all scenes in the hub, and wraps it in
121      * a Scenes class instance
122      * 
123      * @return Scenes class instance
124      * @throws JsonParseException if there is a JSON parsing error
125      * @throws ProcessingException if there is any processing error
126      * @throws HubMaintenanceException if the hub is down for maintenance
127      */
128     public @Nullable Scenes getScenes() throws JsonParseException, ProcessingException, HubMaintenanceException {
129         String json = invoke(scenes.request().header(CONN_HDR, CONN_VAL).buildGet(), scenes, null);
130         return gson.fromJson(json, Scenes.class);
131     }
132
133     /**
134      * Instructs the hub to execute a specific scene
135      * 
136      * @param sceneId id of the scene to be executed
137      * @throws ProcessingException if there is any processing error
138      * @throws HubMaintenanceException if the hub is down for maintenance
139      */
140     public void activateScene(int sceneId) throws ProcessingException, HubMaintenanceException {
141         WebTarget target = sceneActivate.queryParam(SCENE_ID, sceneId);
142         invoke(target.request().header(CONN_HDR, CONN_VAL).buildGet(), target, null);
143     }
144
145     private synchronized String invoke(Invocation invocation, WebTarget target, @Nullable String jsonCommand)
146             throws ProcessingException, HubMaintenanceException {
147         if (logger.isTraceEnabled()) {
148             logger.trace("API command {} {}", jsonCommand == null ? GET : PUT, target.getUri());
149             if (jsonCommand != null) {
150                 logger.trace("JSON command = {}", jsonCommand);
151             }
152         }
153         Response response;
154         try {
155             response = invocation.invoke();
156         } catch (ProcessingException e) {
157             if (Instant.now().isBefore(maintenanceScheduledEnd)) {
158                 // throw "softer" exception during maintenance window
159                 logger.debug("Hub still undergoing maintenance");
160                 throw new HubMaintenanceException("Hub still undergoing maintenance");
161             }
162             throw e;
163         }
164         int statusCode = response.getStatus();
165         if (statusCode == 423) {
166             // set end of maintenance window, and throw a "softer" exception
167             maintenanceScheduledEnd = Instant.now().plusSeconds(maintenancePeriod);
168             logger.debug("Hub undergoing maintenance");
169             if (response.hasEntity()) {
170                 response.readEntity(String.class);
171             }
172             response.close();
173             throw new HubMaintenanceException("Hub undergoing maintenance");
174         }
175         if (statusCode != 200) {
176             logger.warn("Hub returned HTTP error '{}'", statusCode);
177             if (response.hasEntity()) {
178                 response.readEntity(String.class);
179             }
180             response.close();
181             throw new ProcessingException(String.format("HTTP %d error", statusCode));
182         }
183         if (!response.hasEntity()) {
184             logger.warn("Hub returned no content");
185             response.close();
186             throw new ProcessingException("Missing response entity");
187         }
188         String jsonResponse = response.readEntity(String.class);
189         if (logger.isTraceEnabled()) {
190             logger.trace("JSON response = {}", jsonResponse);
191         }
192         return jsonResponse;
193     }
194
195     /**
196      * Fetches a JSON package that describes a specific shade in the hub, and wraps it
197      * in a Shade class instance
198      * 
199      * @param shadeId id of the shade to be fetched
200      * @return Shade class instance
201      * @throws ProcessingException if there is any processing error
202      * @throws HubMaintenanceException if the hub is down for maintenance
203      */
204     public @Nullable Shade getShade(int shadeId) throws ProcessingException, HubMaintenanceException {
205         WebTarget target = shade.resolveTemplate(ID, shadeId);
206         String json = invoke(target.request().header(CONN_HDR, CONN_VAL).buildGet(), target, null);
207         return gson.fromJson(json, Shade.class);
208     }
209
210     /**
211      * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on
212      * a specific shade; fetches a JSON package that describes that shade, and wraps
213      * it in a Shade class instance
214      * 
215      * @param shadeId id of the shade to be refreshed
216      * @return Shade class instance
217      * @throws ProcessingException if there is any processing error
218      * @throws HubMaintenanceException if the hub is down for maintenance
219      */
220     public @Nullable Shade refreshShade(int shadeId) throws ProcessingException, HubMaintenanceException {
221         WebTarget target = shade.resolveTemplate(ID, shadeId).queryParam(REFRESH, true);
222         String json = invoke(target.request().header(CONN_HDR, CONN_VAL).buildGet(), target, null);
223         return gson.fromJson(json, Shade.class);
224     }
225
226     /**
227      * Tells the hub to stop movement of a specific shade
228      * 
229      * @param shadeId id of the shade to be stopped
230      * @throws ProcessingException if there is any processing error
231      * @throws HubMaintenanceException if the hub is down for maintenance
232      */
233     public void stopShade(int shadeId) throws ProcessingException, HubMaintenanceException {
234         WebTarget target = shade.resolveTemplate(ID, shadeId);
235         String json = gson.toJson(new ShadeStop(shadeId));
236         invoke(target.request().header(CONN_HDR, CONN_VAL)
237                 .buildPut(Entity.entity(json, MediaType.APPLICATION_JSON_TYPE)), target, json);
238         return;
239     }
240 }