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