]> git.basschouten.com Git - openhab-addons.git/blob
dad8807ed2cef08b56fa2f5f25187e64fbfb4a51
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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) throws IOException {
87         /*
88          * NOTE: this class uses JAVAX HttpsURLConnection library instead of the
89          * preferred JETTY library; the reason is that JETTY does not allow sending the
90          * square brackets characters "[]" verbatim over HTTP connections
91          */
92         URL url = new URL(urlString);
93         HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
94
95         https.setRequestMethod(HTTP_GET);
96
97         https.setRequestProperty(USER_AGENT, MOZILLA);
98         https.setRequestProperty(ACCEPT, APPLICATION_JSON);
99         https.setRequestProperty(SUBSCRIPTION_KEY, apiKey);
100         https.setRequestProperty(AUTHORIZATION, String.format(BEARER, token));
101
102         if (https.getResponseCode() != HttpURLConnection.HTTP_OK) {
103             throw new IOException(https.getResponseMessage());
104         }
105
106         try (InputStream inputStream = https.getInputStream();
107                 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
108                 BufferedReader reader = new BufferedReader(inputStreamReader)) {
109             String inputString;
110             StringBuffer response = new StringBuffer();
111             while ((inputString = reader.readLine()) != null) {
112                 response.append(inputString);
113             }
114             return response.toString();
115         }
116     }
117
118     /*
119      * public static method: parse the JSON, and create a real instance of this
120      * class that encapsulates the data data point values
121      */
122     public static @Nullable RdsDataPoints createFromJson(String json) {
123         return GSON.fromJson(json, RdsDataPoints.class);
124     }
125
126     /*
127      * private method: execute an HTTP PUT on the server to set a data point value
128      */
129     private void httpSetPointValueJson(String apiKey, String token, String pointUrl, String json)
130             throws RdsCloudException, ProtocolException, MalformedURLException, IOException {
131         /*
132          * NOTE: this class uses JAVAX HttpsURLConnection library instead of the
133          * preferred JETTY library; the reason is that JETTY does not allow sending the
134          * square brackets characters "[]" verbatim over HTTP connections
135          */
136         URL url = new URL(pointUrl);
137
138         HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
139
140         https.setRequestMethod(HTTP_PUT);
141
142         https.setRequestProperty(USER_AGENT, MOZILLA);
143         https.setRequestProperty(CONTENT_TYPE, APPLICATION_JSON);
144         https.setRequestProperty(SUBSCRIPTION_KEY, apiKey);
145         https.setRequestProperty(AUTHORIZATION, String.format(BEARER, token));
146
147         https.setDoOutput(true);
148
149         try (OutputStream outputStream = https.getOutputStream();
150                 DataOutputStream writer = new DataOutputStream(outputStream)) {
151             writer.writeBytes(json);
152         }
153
154         if (https.getResponseCode() != HttpURLConnection.HTTP_OK) {
155             throw new IOException(https.getResponseMessage());
156         }
157     }
158
159     /*
160      * public method: retrieve the data point with the given pointClass
161      */
162     public BasePoint getPointByClass(String pointClass) throws RdsCloudException {
163         if (indexClassToId.isEmpty()) {
164             initClassToIdNameIndex();
165         }
166         @Nullable
167         String pointId = indexClassToId.get(pointClass);
168         if (pointId != null) {
169             return getPointById(pointId);
170         }
171         throw new RdsCloudException(String.format("pointClass \"%s\" not found", pointClass));
172     }
173
174     /*
175      * public method: retrieve the data point with the given pointId
176      */
177     public BasePoint getPointById(String pointId) throws RdsCloudException {
178         Map<String, @Nullable BasePoint> points = this.points;
179         if (points != null) {
180             @Nullable
181             BasePoint point = points.get(pointId);
182             if (point != null) {
183                 return point;
184             }
185         }
186         throw new RdsCloudException(String.format("pointId \"%s\" not found", pointId));
187     }
188
189     /*
190      * private method: retrieve Id of data point with the given pointClass
191      */
192     public String pointClassToId(String pointClass) throws RdsCloudException {
193         if (indexClassToId.isEmpty()) {
194             initClassToIdNameIndex();
195         }
196         @Nullable
197         String pointId = indexClassToId.get(pointClass);
198         if (pointId != null && !pointId.isEmpty()) {
199             return pointId;
200         }
201         throw new RdsCloudException(String.format("no pointId to match pointClass \"%s\"", pointClass));
202     }
203
204     /*
205      * public method: return the state of the "Online" data point
206      */
207     public boolean isOnline() throws RdsCloudException {
208         BasePoint point = getPointByClass(HIE_ONLINE);
209         return "Online".equals(point.getEnum().toString());
210     }
211
212     /*
213      * public method: set a new data point value on the server
214      */
215     public void setValue(String apiKey, String token, String pointClass, String value) {
216         try {
217             String pointId = pointClassToId(pointClass);
218             BasePoint point = getPointByClass(pointClass);
219
220             String url = String.format(URL_SETVAL, pointId);
221             String payload = point.commandJson(value);
222
223             if (logger.isTraceEnabled()) {
224                 logger.trace(LOG_HTTP_COMMAND, HTTP_PUT, url.length());
225                 logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
226                 logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, payload);
227             } else if (logger.isDebugEnabled()) {
228                 logger.debug(LOG_HTTP_COMMAND_ABR, HTTP_PUT, url.length());
229                 logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK, url.substring(0, Math.min(url.length(), 30)));
230                 logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK,
231                         payload.substring(0, Math.min(payload.length(), 30)));
232             }
233
234             httpSetPointValueJson(apiKey, token, url, payload);
235         } catch (RdsCloudException e) {
236             logger.warn(LOG_SYSTEM_EXCEPTION, "setValue()", e.getClass().getName(), e.getMessage());
237         } catch (JsonParseException | IOException e) {
238             logger.warn(LOG_RUNTIME_EXCEPTION, "setValue()", e.getClass().getName(), e.getMessage());
239         }
240     }
241
242     /*
243      * public method: refresh the data point value from the server
244      */
245     public boolean refresh(String apiKey, String token) {
246         try {
247             // initialize the value filter
248             if (valueFilter.isEmpty()) {
249                 Set<String> set = new HashSet<>();
250                 String pointId;
251
252                 for (ChannelMap channel : CHAN_MAP) {
253                     try {
254                         pointId = pointClassToId(channel.clazz);
255                         set.add(String.format("\"%s\"", pointId));
256                     } catch (RdsCloudException e) {
257                         logger.debug("{} \"{}\" not implemented; don't include in request", channel.id, channel.clazz);
258                     }
259                 }
260
261                 Map<String, @Nullable BasePoint> points = this.points;
262                 if (points != null) {
263                     for (Map.Entry<String, @Nullable BasePoint> entry : points.entrySet()) {
264                         @Nullable
265                         BasePoint point = entry.getValue();
266                         if (point != null) {
267                             if ("Online".equals(point.getMemberName())) {
268                                 set.add(String.format("\"%s\"", entry.getKey()));
269                                 break;
270                             }
271                         }
272                     }
273                 }
274
275                 valueFilter = String.join(",", set);
276             }
277
278             String url = String.format(URL_VALUES, valueFilter);
279
280             if (logger.isTraceEnabled()) {
281                 logger.trace(LOG_HTTP_COMMAND, HTTP_GET, url.length());
282                 logger.trace(LOG_PAYLOAD_FMT, LOG_SENDING_MARK, url);
283             } else if (logger.isDebugEnabled()) {
284                 logger.debug(LOG_HTTP_COMMAND_ABR, HTTP_GET, url.length());
285                 logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_SENDING_MARK, url.substring(0, Math.min(url.length(), 30)));
286             }
287
288             String json = httpGenericGetJson(apiKey, token, url);
289
290             if (logger.isTraceEnabled()) {
291                 logger.trace(LOG_CONTENT_LENGTH, LOG_RECEIVED_MSG, json.length());
292                 logger.trace(LOG_PAYLOAD_FMT, LOG_RECEIVED_MARK, json);
293             } else if (logger.isDebugEnabled()) {
294                 logger.debug(LOG_CONTENT_LENGTH_ABR, LOG_RECEIVED_MSG, json.length());
295                 logger.debug(LOG_PAYLOAD_FMT_ABR, LOG_RECEIVED_MARK, json.substring(0, Math.min(json.length(), 30)));
296             }
297
298             @Nullable
299             RdsDataPoints newPoints = GSON.fromJson(json, RdsDataPoints.class);
300
301             Map<String, @Nullable BasePoint> newPointsMap = newPoints != null ? newPoints.points : null;
302
303             if (newPointsMap == null) {
304                 throw new RdsCloudException("new points map empty");
305             }
306
307             synchronized (this) {
308                 for (Entry<String, @Nullable BasePoint> entry : newPointsMap.entrySet()) {
309                     @Nullable
310                     String pointId = entry.getKey();
311
312                     @Nullable
313                     BasePoint newPoint = entry.getValue();
314                     if (newPoint == null) {
315                         throw new RdsCloudException("invalid new point");
316                     }
317
318                     @Nullable
319                     BasePoint myPoint = getPointById(pointId);
320
321                     if (!(newPoint.getClass().equals(myPoint.getClass()))) {
322                         throw new RdsCloudException("existing vs. new point class mismatch");
323                     }
324
325                     myPoint.refreshValueFrom(newPoint);
326
327                     if (logger.isDebugEnabled()) {
328                         logger.debug("refresh {}.{}: {} << {}", getDescription(), myPoint.getPointClass(),
329                                 myPoint.getState(), newPoint.getState());
330                     }
331                 }
332             }
333
334             return true;
335         } catch (RdsCloudException e) {
336             logger.warn(LOG_SYSTEM_EXCEPTION, "refresh()", e.getClass().getName(), e.getMessage());
337         } catch (JsonParseException | IOException e) {
338             logger.warn(LOG_RUNTIME_EXCEPTION, "refresh()", e.getClass().getName(), e.getMessage());
339         }
340         return false;
341     }
342
343     /*
344      * initialize the second index into to the points Map
345      */
346     private void initClassToIdNameIndex() {
347         Map<String, @Nullable BasePoint> points = this.points;
348         if (points != null) {
349             indexClassToId.clear();
350             for (Entry<String, @Nullable BasePoint> entry : points.entrySet()) {
351                 String pointKey = entry.getKey();
352                 BasePoint pointValue = entry.getValue();
353                 if (pointValue != null) {
354                     indexClassToId.put(pointValue.getPointClass(), pointKey);
355                 }
356             }
357         }
358     }
359
360     /*
361      * public method: return the state of the "Description" data point
362      */
363     public String getDescription() throws RdsCloudException {
364         return getPointByClass(HIE_DESCRIPTION).getState().toString();
365     }
366 }