2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.lutron.internal.protocol.leap;
15 import java.util.LinkedList;
16 import java.util.List;
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;
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;
41 * Class responsible for parsing incoming LEAP messages
43 * @author Bob Adair - Initial contribution
46 public class LeapMessageParser {
47 private final Logger logger = LoggerFactory.getLogger(LeapMessageParser.class);
49 private final Gson gson;
50 private final LeapMessageParserCallbacks callback;
53 * LeapMessageParser Constructor
55 * @param callback Object implementing the LeapMessageParserCallbacks interface
57 public LeapMessageParser(LeapMessageParserCallbacks callback) {
58 gson = new GsonBuilder().create();
59 this.callback = callback;
63 * Parse and process a LEAP protocol message
65 * @param msg String containing the LEAP message
67 public void handleMessage(String msg) {
68 if (msg.trim().equals("")) {
69 return; // Ignore empty lines
71 logger.trace("Received message: {}", msg);
74 JsonObject message = (JsonObject) new JsonParser().parse(msg);
76 if (!message.has("CommuniqueType")) {
77 logger.debug("No CommuniqueType found in message: {}", msg);
81 String communiqueType = message.get("CommuniqueType").getAsString();
82 // CommuniqueType type = CommuniqueType.valueOf(communiqueType);
83 logger.debug("Received CommuniqueType: {}", communiqueType);
84 callback.validMessageReceived(communiqueType);
86 switch (communiqueType) {
87 case "CreateResponse":
90 handleReadResponseMessage(message);
92 case "UpdateResponse":
94 case "SubscribeResponse":
95 // Subscribe responses can contain bodies with data
96 handleReadResponseMessage(message);
98 case "UnsubscribeResponse":
100 case "ExceptionResponse":
101 handleExceptionResponse(message);
104 logger.debug("Unknown CommuniqueType received: {}", communiqueType);
107 } catch (JsonParseException e) {
108 logger.debug("Error parsing message: {}", e.getMessage());
114 * Method called by handleMessage() to handle all LEAP ExceptionResponse messages.
116 * @param message LEAP message
118 private void handleExceptionResponse(JsonObject message) {
119 String detailMessage = "";
122 JsonObject header = message.get("Header").getAsJsonObject();
123 Header headerObj = gson.fromJson(header, Header.class);
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;
133 logger.debug("Exception response received. Status: {} URL: {} Message: {}", headerObj.statusCode,
134 headerObj.url, detailMessage);
136 } catch (JsonParseException | IllegalStateException e) {
137 logger.debug("Exception response received. Error parsing exception message: {}", e.getMessage());
143 * Method called by handleMessage() to handle all LEAP ReadResponse and SubscribeResponse messages.
145 * @param message LEAP message
147 private void handleReadResponseMessage(JsonObject message) {
149 JsonObject header = message.get("Header").getAsJsonObject();
150 Header headerObj = gson.fromJson(header, Header.class);
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();
159 if (!header.has("MessageBodyType")) {
160 logger.trace("No MessageBodyType in header");
163 String messageBodyType = header.get("MessageBodyType").getAsString();
164 logger.trace("MessageBodyType: {}", messageBodyType);
166 if (!message.has("Body")) {
167 logger.debug("No Body found in message");
170 JsonObject body = message.get("Body").getAsJsonObject();
172 switch (messageBodyType) {
173 case "OnePingResponse":
174 parseOnePingResponse(body);
176 case "OneZoneStatus":
177 parseOneZoneStatus(body);
179 case "MultipleAreaDefinition":
180 parseMultipleAreaDefinition(body);
182 case "MultipleButtonGroupDefinition":
183 parseMultipleButtonGroupDefinition(body);
185 case "MultipleDeviceDefinition":
186 parseMultipleDeviceDefinition(body);
188 case "MultipleOccupancyGroupDefinition":
189 parseMultipleOccupancyGroupDefinition(body);
191 case "MultipleOccupancyGroupStatus":
192 parseMultipleOccupancyGroupStatus(body);
194 case "MultipleVirtualButtonDefinition":
197 logger.debug("Unknown MessageBodyType received: {}", messageBodyType);
200 } catch (JsonParseException | IllegalStateException e) {
201 logger.debug("Error parsing message: {}", e.getMessage());
206 private @Nullable <T extends AbstractMessageBody> T parseBodySingle(JsonObject messageBody, String memberName,
209 if (messageBody.has(memberName)) {
210 JsonObject jsonObject = messageBody.get(memberName).getAsJsonObject();
211 T obj = gson.fromJson(jsonObject, type);
214 logger.debug("Member name {} not found in JSON message", memberName);
217 } catch (IllegalStateException | JsonSyntaxException e) {
218 logger.debug("Error parsing JSON message: {}", e.getMessage());
223 private <T extends AbstractMessageBody> List<T> parseBodyMultiple(JsonObject messageBody, String memberName,
225 List<T> objList = new LinkedList<T>();
227 if (messageBody.has(memberName)) {
228 JsonArray jsonArray = messageBody.get(memberName).getAsJsonArray();
230 for (JsonElement element : jsonArray) {
231 JsonObject jsonObject = element.getAsJsonObject();
232 T obj = gson.fromJson(jsonObject, type);
237 logger.debug("Member name {} not found in JSON message", memberName);
240 } catch (IllegalStateException | JsonSyntaxException e) {
241 logger.debug("Error parsing JSON message: {}", e.getMessage());
246 private void parseOnePingResponse(JsonObject messageBody) {
247 logger.debug("Ping response received");
251 * Parses a OneZoneStatus message body. Calls handleZoneUpdate() to dispatch zone updates.
253 private void parseOneZoneStatus(JsonObject messageBody) {
254 ZoneStatus zoneStatus = parseBodySingle(messageBody, "ZoneStatus", ZoneStatus.class);
255 if (zoneStatus != null) {
256 callback.handleZoneUpdate(zoneStatus);
261 * Parses a MultipleAreaDefinition message body.
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);
270 * Parses a MultipleOccupancyGroupDefinition message body.
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);
279 * Parses a MultipleOccupancyGroupStatus message body and updates occupancy status.
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);
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.
298 private void parseMultipleDeviceDefinition(JsonObject messageBody) {
299 List<Device> deviceList = parseBodyMultiple(messageBody, "Devices", Device.class);
300 callback.handleMultipleDeviceDefintion(deviceList);
304 * Parse a MultipleButtonGroupDefinition message body and load the results into deviceButtonMap.
306 private void parseMultipleButtonGroupDefinition(JsonObject messageBody) {
307 List<ButtonGroup> buttonGroupList = parseBodyMultiple(messageBody, "ButtonGroups", ButtonGroup.class);
308 callback.handleMultipleButtonGroupDefinition(buttonGroupList);