]> git.basschouten.com Git - openhab-addons.git/blob
99720d93c5f0db43fc4e194e7420383672b00f8a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.lutron.internal.protocol.leap;
14
15 import java.util.LinkedList;
16 import java.util.List;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
21 import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonGroup;
22 import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
23 import org.openhab.binding.lutron.internal.protocol.leap.dto.ExceptionDetail;
24 import org.openhab.binding.lutron.internal.protocol.leap.dto.Header;
25 import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
26 import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroupStatus;
27 import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 import com.google.gson.Gson;
32 import com.google.gson.GsonBuilder;
33 import com.google.gson.JsonArray;
34 import com.google.gson.JsonElement;
35 import com.google.gson.JsonObject;
36 import com.google.gson.JsonParseException;
37 import com.google.gson.JsonParser;
38 import com.google.gson.JsonSyntaxException;
39
40 /**
41  * Class responsible for parsing incoming LEAP messages
42  *
43  * @author Bob Adair - Initial contribution
44  */
45 @NonNullByDefault
46 public class LeapMessageParser {
47     private final Logger logger = LoggerFactory.getLogger(LeapMessageParser.class);
48
49     private final Gson gson;
50     private final LeapMessageParserCallbacks callback;
51
52     /**
53      * LeapMessageParser Constructor
54      *
55      * @param callback Object implementing the LeapMessageParserCallbacks interface
56      */
57     public LeapMessageParser(LeapMessageParserCallbacks callback) {
58         gson = new GsonBuilder().create();
59         this.callback = callback;
60     }
61
62     /**
63      * Parse and process a LEAP protocol message
64      *
65      * @param msg String containing the LEAP message
66      */
67     public void handleMessage(String msg) {
68         if (msg.trim().equals("")) {
69             return; // Ignore empty lines
70         }
71         logger.trace("Received message: {}", msg);
72
73         try {
74             JsonObject message = (JsonObject) new JsonParser().parse(msg);
75
76             if (!message.has("CommuniqueType")) {
77                 logger.debug("No CommuniqueType found in message: {}", msg);
78                 return;
79             }
80
81             String communiqueType = message.get("CommuniqueType").getAsString();
82             // CommuniqueType type = CommuniqueType.valueOf(communiqueType);
83             logger.debug("Received CommuniqueType: {}", communiqueType);
84             callback.validMessageReceived(communiqueType);
85
86             switch (communiqueType) {
87                 case "CreateResponse":
88                     return;
89                 case "ReadResponse":
90                     handleReadResponseMessage(message);
91                     break;
92                 case "UpdateResponse":
93                     break;
94                 case "SubscribeResponse":
95                     // Subscribe responses can contain bodies with data
96                     handleReadResponseMessage(message);
97                     return;
98                 case "UnsubscribeResponse":
99                     return;
100                 case "ExceptionResponse":
101                     handleExceptionResponse(message);
102                     return;
103                 default:
104                     logger.debug("Unknown CommuniqueType received: {}", communiqueType);
105                     break;
106             }
107         } catch (JsonParseException e) {
108             logger.debug("Error parsing message: {}", e.getMessage());
109             return;
110         }
111     }
112
113     /**
114      * Method called by handleMessage() to handle all LEAP ExceptionResponse messages.
115      *
116      * @param message LEAP message
117      */
118     private void handleExceptionResponse(JsonObject message) {
119         String detailMessage = "";
120
121         try {
122             JsonObject header = message.get("Header").getAsJsonObject();
123             Header headerObj = gson.fromJson(header, Header.class);
124
125             if (MessageBodyType.ExceptionDetail.toString().equalsIgnoreCase(headerObj.messageBodyType)
126                     && message.has("Body")) {
127                 JsonObject body = message.get("Body").getAsJsonObject();
128                 ExceptionDetail exceptionDetail = gson.fromJson(body, ExceptionDetail.class);
129                 if (exceptionDetail != null) {
130                     detailMessage = exceptionDetail.message;
131                 }
132             }
133             logger.debug("Exception response received. Status: {} URL: {} Message: {}", headerObj.statusCode,
134                     headerObj.url, detailMessage);
135
136         } catch (JsonParseException | IllegalStateException e) {
137             logger.debug("Exception response received. Error parsing exception message: {}", e.getMessage());
138             return;
139         }
140     }
141
142     /**
143      * Method called by handleMessage() to handle all LEAP ReadResponse and SubscribeResponse messages.
144      *
145      * @param message LEAP message
146      */
147     private void handleReadResponseMessage(JsonObject message) {
148         try {
149             JsonObject header = message.get("Header").getAsJsonObject();
150             Header headerObj = gson.fromJson(header, Header.class);
151
152             // if 204/NoContent response received for buttongroup request, create empty button map
153             if (Request.BUTTON_GROUP_URL.equals(headerObj.url)
154                     && Header.STATUS_NO_CONTENT.equalsIgnoreCase(headerObj.statusCode)) {
155                 callback.handleEmptyButtonGroupDefinition();
156                 return;
157             }
158
159             if (!header.has("MessageBodyType")) {
160                 logger.trace("No MessageBodyType in header");
161                 return;
162             }
163             String messageBodyType = header.get("MessageBodyType").getAsString();
164             logger.trace("MessageBodyType: {}", messageBodyType);
165
166             if (!message.has("Body")) {
167                 logger.debug("No Body found in message");
168                 return;
169             }
170             JsonObject body = message.get("Body").getAsJsonObject();
171
172             switch (messageBodyType) {
173                 case "OnePingResponse":
174                     parseOnePingResponse(body);
175                     break;
176                 case "OneZoneStatus":
177                     parseOneZoneStatus(body);
178                     break;
179                 case "MultipleAreaDefinition":
180                     parseMultipleAreaDefinition(body);
181                     break;
182                 case "MultipleButtonGroupDefinition":
183                     parseMultipleButtonGroupDefinition(body);
184                     break;
185                 case "MultipleDeviceDefinition":
186                     parseMultipleDeviceDefinition(body);
187                     break;
188                 case "MultipleOccupancyGroupDefinition":
189                     parseMultipleOccupancyGroupDefinition(body);
190                     break;
191                 case "MultipleOccupancyGroupStatus":
192                     parseMultipleOccupancyGroupStatus(body);
193                     break;
194                 case "MultipleVirtualButtonDefinition":
195                     break;
196                 default:
197                     logger.debug("Unknown MessageBodyType received: {}", messageBodyType);
198                     break;
199             }
200         } catch (JsonParseException | IllegalStateException e) {
201             logger.debug("Error parsing message: {}", e.getMessage());
202             return;
203         }
204     }
205
206     private @Nullable <T extends AbstractMessageBody> T parseBodySingle(JsonObject messageBody, String memberName,
207             Class<T> type) {
208         try {
209             if (messageBody.has(memberName)) {
210                 JsonObject jsonObject = messageBody.get(memberName).getAsJsonObject();
211                 T obj = gson.fromJson(jsonObject, type);
212                 return obj;
213             } else {
214                 logger.debug("Member name {} not found in JSON message", memberName);
215                 return null;
216             }
217         } catch (IllegalStateException | JsonSyntaxException e) {
218             logger.debug("Error parsing JSON message: {}", e.getMessage());
219             return null;
220         }
221     }
222
223     private <T extends AbstractMessageBody> List<T> parseBodyMultiple(JsonObject messageBody, String memberName,
224             Class<T> type) {
225         List<T> objList = new LinkedList<T>();
226         try {
227             if (messageBody.has(memberName)) {
228                 JsonArray jsonArray = messageBody.get(memberName).getAsJsonArray();
229
230                 for (JsonElement element : jsonArray) {
231                     JsonObject jsonObject = element.getAsJsonObject();
232                     T obj = gson.fromJson(jsonObject, type);
233                     objList.add(obj);
234                 }
235                 return objList;
236             } else {
237                 logger.debug("Member name {} not found in JSON message", memberName);
238                 return objList;
239             }
240         } catch (IllegalStateException | JsonSyntaxException e) {
241             logger.debug("Error parsing JSON message: {}", e.getMessage());
242             return objList;
243         }
244     }
245
246     private void parseOnePingResponse(JsonObject messageBody) {
247         logger.debug("Ping response received");
248     }
249
250     /**
251      * Parses a OneZoneStatus message body. Calls handleZoneUpdate() to dispatch zone updates.
252      */
253     private void parseOneZoneStatus(JsonObject messageBody) {
254         ZoneStatus zoneStatus = parseBodySingle(messageBody, "ZoneStatus", ZoneStatus.class);
255         if (zoneStatus != null) {
256             callback.handleZoneUpdate(zoneStatus);
257         }
258     }
259
260     /**
261      * Parses a MultipleAreaDefinition message body.
262      */
263     private void parseMultipleAreaDefinition(JsonObject messageBody) {
264         logger.trace("Parsing area list");
265         List<Area> areaList = parseBodyMultiple(messageBody, "Areas", Area.class);
266         callback.handleMultipleAreaDefinition(areaList);
267     }
268
269     /**
270      * Parses a MultipleOccupancyGroupDefinition message body.
271      */
272     private void parseMultipleOccupancyGroupDefinition(JsonObject messageBody) {
273         logger.trace("Parsing occupancy group list");
274         List<OccupancyGroup> oGroupList = parseBodyMultiple(messageBody, "OccupancyGroups", OccupancyGroup.class);
275         callback.handleMultipleOccupancyGroupDefinition(oGroupList);
276     }
277
278     /**
279      * Parses a MultipleOccupancyGroupStatus message body and updates occupancy status.
280      */
281     private void parseMultipleOccupancyGroupStatus(JsonObject messageBody) {
282         logger.trace("Parsing occupancy group status list");
283         List<OccupancyGroupStatus> statusList = parseBodyMultiple(messageBody, "OccupancyGroupStatuses",
284                 OccupancyGroupStatus.class);
285         for (OccupancyGroupStatus status : statusList) {
286             int groupNumber = status.getOccupancyGroup();
287             if (groupNumber > 0) {
288                 logger.debug("OccupancyGroup: {} Status: {}", groupNumber, status.occupancyStatus);
289                 callback.handleGroupUpdate(groupNumber, status.occupancyStatus);
290             }
291         }
292     }
293
294     /**
295      * Parses a MultipleDeviceDefinition message body and loads the zoneToDevice and deviceToZone maps. Also passes the
296      * device data on to the discovery service and calls setBridgeProperties() with the hub's device entry.
297      */
298     private void parseMultipleDeviceDefinition(JsonObject messageBody) {
299         List<Device> deviceList = parseBodyMultiple(messageBody, "Devices", Device.class);
300         callback.handleMultipleDeviceDefintion(deviceList);
301     }
302
303     /**
304      * Parse a MultipleButtonGroupDefinition message body and load the results into deviceButtonMap.
305      */
306     private void parseMultipleButtonGroupDefinition(JsonObject messageBody) {
307         List<ButtonGroup> buttonGroupList = parseBodyMultiple(messageBody, "ButtonGroups", ButtonGroup.class);
308         callback.handleMultipleButtonGroupDefinition(buttonGroupList);
309     }
310 }