]> git.basschouten.com Git - openhab-addons.git/blob
8f25872a3d98362098155b02aeab372ef75b912e
[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.siemensrds.internal;
14
15 import static org.openhab.binding.siemensrds.internal.RdsBindingConstants.*;
16
17 import java.io.BufferedReader;
18 import java.io.DataOutputStream;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.InputStreamReader;
22 import java.io.OutputStream;
23 import java.io.UnsupportedEncodingException;
24 import java.net.HttpURLConnection;
25 import java.net.MalformedURLException;
26 import java.net.ProtocolException;
27 import java.net.URL;
28 import java.nio.charset.StandardCharsets;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Set;
34
35 import javax.net.ssl.HttpsURLConnection;
36
37 import org.eclipse.jdt.annotation.NonNullByDefault;
38 import org.eclipse.jdt.annotation.Nullable;
39 import org.openhab.binding.siemensrds.points.BasePoint;
40 import org.openhab.binding.siemensrds.points.PointDeserializer;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import com.google.gson.Gson;
45 import com.google.gson.GsonBuilder;
46 import com.google.gson.JsonParseException;
47 import com.google.gson.annotations.SerializedName;
48
49 /**
50  *
51  * Interface to the Data Points of a particular Plant
52  *
53  * @author Andrew Fiddian-Green - Initial contribution
54  *
55  */
56 @NonNullByDefault
57 public class RdsDataPoints {
58
59     /*
60      * NOTE: requires a static logger because the class has static methods
61      */
62     protected final Logger logger = LoggerFactory.getLogger(RdsDataPoints.class);
63
64     private static final Gson GSON = new GsonBuilder().registerTypeAdapter(BasePoint.class, new PointDeserializer())
65             .create();
66
67     /*
68      * this is a second index into to the JSON "values" points Map below; the
69      * purpose is to allow point lookups by a) pointId (which we do directly from
70      * the Map, and b) by pointClass (which we do indirectly "double dereferenced"
71      * via this index
72      */
73     private final Map<String, String> indexClassToId = new HashMap<>();
74
75     @SerializedName("totalCount")
76     private @Nullable String totalCount;
77     @SerializedName("values")
78     public @Nullable Map<String, @Nullable BasePoint> points;
79
80     private String valueFilter = "";
81
82     /*
83      * protected static method: can be used by this class and by other classes to
84      * execute an HTTP GET command on the remote cloud server to retrieve the JSON
85      * response from the given urlString
86      */
87     protected static String httpGenericGetJson(String apiKey, String token, String urlString)
88             throws RdsCloudException, IOException {
89         /*
90          * NOTE: this class uses JAVAX HttpsURLConnection library instead of the
91          * preferred JETTY library; the reason is that JETTY does not allow sending the
92          * square brackets characters "[]" verbatim over HTTP connections
93          */
94         URL url = new URL(urlString);
95         HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
96
97         https.setRequestMethod(HTTP_GET);
98
99         https.setRequestProperty(USER_AGENT, MOZILLA);
100         https.setRequestProperty(ACCEPT, APPLICATION_JSON);
101         https.setRequestProperty(SUBSCRIPTION_KEY, apiKey);
102         https.setRequestProperty(AUTHORIZATION, String.format(BEARER, token));
103
104         if (https.getResponseCode() != HttpURLConnection.HTTP_OK) {
105             throw new IOException(https.getResponseMessage());
106         }
107
108         try (InputStream inputStream = https.getInputStream();
109                 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
110                 BufferedReader reader = new BufferedReader(inputStreamReader)) {
111             String inputString;
112             StringBuffer response = new StringBuffer();
113             while ((inputString = reader.readLine()) != null) {
114                 response.append(inputString);
115             }
116             return response.toString();
117         }
118     }
119
120     /*
121      * public static method: parse the JSON, and create a real instance of this
122      * class that encapsulates the data data point values
123      */
124     public static @Nullable RdsDataPoints createFromJson(String json) {
125         return GSON.fromJson(json, RdsDataPoints.class);
126     }
127
128     /*
129      * private method: execute an HTTP PUT on the server to set a data point value
130      */
131     private void httpSetPointValueJson(String apiKey, String token, String pointUrl, String json)
132             throws RdsCloudException, UnsupportedEncodingException, ProtocolException, MalformedURLException,
133             IOException {
134         /*
135          * NOTE: this class uses JAVAX HttpsURLConnection library instead of the
136          * preferred JETTY library; the reason is that JETTY does not allow sending the
137          * square brackets characters "[]" verbatim over HTTP connections
138          */
139         URL url = new URL(pointUrl);
140
141         HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
142
143         https.setRequestMethod(HTTP_PUT);
144
145         https.setRequestProperty(USER_AGENT, MOZILLA);
146         https.setRequestProperty(CONTENT_TYPE, APPLICATION_JSON);
147         https.setRequestProperty(SUBSCRIPTION_KEY, apiKey);
148         https.setRequestProperty(AUTHORIZATION, String.format(BEARER, token));
149
150         https.setDoOutput(true);
151
152         try (OutputStream outputStream = https.getOutputStream();
153                 DataOutputStream writer = new DataOutputStream(outputStream)) {
154             writer.writeBytes(json);
155         }
156
157         if (https.getResponseCode() != HttpURLConnection.HTTP_OK) {
158             throw new IOException(https.getResponseMessage());
159         }
160     }
161
162     /*
163      * public method: retrieve the data point with the given pointClass
164      */
165     public BasePoint getPointByClass(String pointClass) throws RdsCloudException {
166         if (indexClassToId.isEmpty()) {
167             initClassToIdNameIndex();
168         }
169         @Nullable
170         String pointId = indexClassToId.get(pointClass);
171         if (pointId != null) {
172             return getPointById(pointId);
173         }
174         throw new RdsCloudException(String.format("pointClass \"%s\" not found", pointClass));
175     }
176
177     /*
178      * public method: retrieve the data point with the given pointId
179      */
180     public BasePoint getPointById(String pointId) throws RdsCloudException {
181         Map<String, @Nullable BasePoint> points = this.points;
182         if (points != null) {
183             @Nullable
184             BasePoint point = points.get(pointId);
185             if (point != null) {
186                 return point;
187             }
188         }
189         throw new RdsCloudException(String.format("pointId \"%s\" not found", pointId));
190     }
191
192     /*
193      * private method: retrieve Id of data point with the given pointClass
194      */
195     public String pointClassToId(String pointClass) throws RdsCloudException {
196         if (indexClassToId.isEmpty()) {
197             initClassToIdNameIndex();
198         }
199         @Nullable
200         String pointId = indexClassToId.get(pointClass);
201         if (pointId != null) {
202             return pointId;
203         }
204         throw new RdsCloudException(String.format("no pointId to match pointClass \"%s\"", pointClass));
205     }
206
207     /*
208      * public method: return the state of the "Online" data point
209      */
210     public boolean isOnline() throws RdsCloudException {
211         BasePoint point = getPointByClass(HIE_ONLINE);
212         return "Online".equals(point.getEnum().toString());
213     }
214
215     /*
216      * public method: set a new data point value on the server
217      */
218     public void setValue(String apiKey, String token, String pointClass, String value) {
219         try {
220             String pointId = pointClassToId(pointClass);
221             BasePoint point = getPointByClass(pointClass);
222
223             String url = String.format(URL_SETVAL, pointId);
224             String payload = point.commandJson(value);
225
226             if (logger.isTraceEnabled()) {
227                 logger.trace(LOG_HTTP_COMMAND, HTTP_PUT, url.length());
228                 logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
229                 logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, payload);
230             } else if (logger.isDebugEnabled()) {
231                 logger.debug(LOG_HTTP_COMMAND_ABR, HTTP_PUT, url.length());
232                 logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK, url.substring(0, Math.min(url.length(), 30)));
233                 logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK,
234                         payload.substring(0, Math.min(payload.length(), 30)));
235             }
236
237             httpSetPointValueJson(apiKey, token, url, payload);
238         } catch (RdsCloudException e) {
239             logger.warn(LOG_SYSTEM_EXCEPTION, "setValue()", e.getClass().getName(), e.getMessage());
240         } catch (JsonParseException | IOException e) {
241             logger.warn(LOG_RUNTIME_EXCEPTION, "setValue()", e.getClass().getName(), e.getMessage());
242         }
243     }
244
245     /*
246      * public method: refresh the data point value from the server
247      */
248     public boolean refresh(String apiKey, String token) {
249         try {
250             // initialize the value filter
251             if (valueFilter.isEmpty()) {
252                 Set<String> set = new HashSet<>();
253                 String pointId;
254
255                 for (ChannelMap chan : CHAN_MAP) {
256                     pointId = pointClassToId(chan.clazz);
257                     if (!pointId.isEmpty()) {
258                         set.add(String.format("\"%s\"", pointId));
259                     }
260                 }
261
262                 Map<String, @Nullable BasePoint> points = this.points;
263                 if (points != null) {
264                     for (Map.Entry<String, @Nullable BasePoint> entry : points.entrySet()) {
265                         @Nullable
266                         BasePoint point = entry.getValue();
267                         if (point != null) {
268                             if ("Online".equals(point.getMemberName())) {
269                                 set.add(String.format("\"%s\"", entry.getKey()));
270                                 break;
271                             }
272                         }
273                     }
274                 }
275
276                 valueFilter = String.join(",", set);
277             }
278
279             String url = String.format(URL_VALUES, valueFilter);
280
281             if (logger.isTraceEnabled()) {
282                 logger.trace(LOG_HTTP_COMMAND, HTTP_GET, url.length());
283                 logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
284             } else if (logger.isDebugEnabled()) {
285                 logger.debug(LOG_HTTP_COMMAND_ABR, HTTP_GET, url.length());
286                 logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK, url.substring(0, Math.min(url.length(), 30)));
287             }
288
289             String json = httpGenericGetJson(apiKey, token, url);
290
291             if (logger.isTraceEnabled()) {
292                 logger.trace(LOG_CONTENT_LENGTH, LOG_RECEIVED_MSG, json.length());
293                 logger.trace(LOG_PAYLOAD_FMT, LOG_RECEIVED_MARK, json);
294             } else if (logger.isDebugEnabled()) {
295                 logger.debug(LOG_CONTENT_LENGTH_ABR, LOG_RECEIVED_MSG, json.length());
296                 logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_RECEIVED_MARK, json.substring(0, Math.min(json.length(), 30)));
297             }
298
299             @Nullable
300             RdsDataPoints newPoints = GSON.fromJson(json, RdsDataPoints.class);
301
302             Map<String, @Nullable BasePoint> newPointsMap = newPoints != null ? newPoints.points : null;
303
304             if (newPointsMap == null) {
305                 throw new RdsCloudException("new points map empty");
306             }
307
308             synchronized (this) {
309                 for (Entry<String, @Nullable BasePoint> entry : newPointsMap.entrySet()) {
310                     @Nullable
311                     String pointId = entry.getKey();
312
313                     @Nullable
314                     BasePoint newPoint = entry.getValue();
315                     if (newPoint == null) {
316                         throw new RdsCloudException("invalid new point");
317                     }
318
319                     @Nullable
320                     BasePoint myPoint = getPointById(pointId);
321
322                     if (!(newPoint.getClass().equals(myPoint.getClass()))) {
323                         throw new RdsCloudException("existing vs. new point class mismatch");
324                     }
325
326                     myPoint.refreshValueFrom(newPoint);
327
328                     if (logger.isDebugEnabled()) {
329                         logger.debug("refresh {}.{}: {} << {}", getDescription(), myPoint.getPointClass(),
330                                 myPoint.getState(), newPoint.getState());
331                     }
332                 }
333             }
334
335             return true;
336         } catch (RdsCloudException e) {
337             logger.warn(LOG_SYSTEM_EXCEPTION, "refresh()", e.getClass().getName(), e.getMessage());
338         } catch (JsonParseException | IOException e) {
339             logger.warn(LOG_RUNTIME_EXCEPTION, "refresh()", e.getClass().getName(), e.getMessage());
340         }
341         return false;
342     }
343
344     /*
345      * initialize the second index into to the points Map
346      */
347     private void initClassToIdNameIndex() {
348         Map<String, @Nullable BasePoint> points = this.points;
349         if (points != null) {
350             indexClassToId.clear();
351             for (Entry<String, @Nullable BasePoint> entry : points.entrySet()) {
352                 String pointKey = entry.getKey();
353                 BasePoint pointValue = entry.getValue();
354                 if (pointValue != null) {
355                     indexClassToId.put(pointValue.getPointClass(), pointKey);
356                 }
357             }
358         }
359     }
360
361     /*
362      * public method: return the state of the "Description" data point
363      */
364     public String getDescription() throws RdsCloudException {
365         return getPointByClass(HIE_DESCRIPTION).getState().toString();
366     }
367 }