import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lutron.internal.config.LeapBridgeConfig;
import org.openhab.binding.lutron.internal.discovery.LeapDeviceDiscoveryService;
+import org.openhab.binding.lutron.internal.protocol.DeviceCommand;
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
import org.openhab.binding.lutron.internal.protocol.GroupCommand;
import org.openhab.binding.lutron.internal.protocol.LutronCommandNew;
import org.openhab.binding.lutron.internal.protocol.leap.Request;
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonGroup;
+import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonStatus;
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
import org.openhab.binding.lutron.internal.protocol.leap.dto.Project;
private final Object zoneMapsLock = new Object();
private @Nullable Map<Integer, List<Integer>> deviceButtonMap;
+ private Map<Integer, Integer> buttonToDevice = new HashMap<>();
private final Object deviceButtonMapLock = new Object();
private volatile boolean deviceDataLoaded = false;
logger.debug("No content in button group definition. Creating empty deviceButtonMap.");
Map<Integer, List<Integer>> deviceButtonMap = new HashMap<>();
synchronized (deviceButtonMapLock) {
+ buttonToDevice.clear();
this.deviceButtonMap = deviceButtonMap;
buttonDataLoaded = true;
}
@Override
public void handleMultipleButtonGroupDefinition(List<ButtonGroup> buttonGroupList) {
Map<Integer, List<Integer>> deviceButtonMap = new HashMap<>();
+ Map<Integer, Integer> buttonToDevice = new HashMap<>();
for (ButtonGroup buttonGroup : buttonGroupList) {
int parentDevice = buttonGroup.getParentDevice();
logger.trace("Found ButtonGroup: {} parent device: {}", buttonGroup.getButtonGroup(), parentDevice);
List<Integer> buttonList = buttonGroup.getButtonList();
deviceButtonMap.put(parentDevice, buttonList);
+ for (Integer buttonId : buttonList) {
+ buttonToDevice.put(buttonId, parentDevice);
+ sendCommand(new LeapCommand(Request.subscribeButtonStatus(buttonId)));
+ }
}
synchronized (deviceButtonMapLock) {
this.deviceButtonMap = deviceButtonMap;
+ this.buttonToDevice = buttonToDevice;
buttonDataLoaded = true;
}
checkInitialized();
sendCommand(new LeapCommand(Request.subscribeOccupancyGroupStatus()));
}
+ /**
+ * Notify child thing handler of a button update.
+ */
+ @Override
+ public void handleButtonStatus(ButtonStatus buttonStatus) {
+ int buttonId = buttonStatus.getButton();
+ logger.trace("Button: {} eventType: {}", buttonId, buttonStatus.buttonEvent.eventType);
+ Entry<Integer, Integer> entry = buttonToDeviceAndIndex(buttonId);
+
+ if (entry == null) {
+ logger.debug("Unable to map button {} to device", buttonId);
+ return;
+ }
+ int integrationId = entry.getKey();
+ int index = entry.getValue();
+ logger.trace("Button {} mapped to device id {}, index {}", buttonId, integrationId, index);
+
+ int action;
+ if ("Press".equals(buttonStatus.buttonEvent.eventType)) {
+ action = DeviceCommand.ACTION_PRESS;
+ } else if ("Release".equals(buttonStatus.buttonEvent.eventType)) {
+ action = DeviceCommand.ACTION_RELEASE;
+ } else {
+ logger.warn("Unrecognized button event {} for button {} on device {}", buttonStatus.buttonEvent.eventType,
+ index, integrationId);
+ return;
+ }
+
+ // dispatch update to proper thing handler
+ LutronHandler handler = findThingHandler(integrationId);
+ if (handler != null) {
+ try {
+ handler.handleUpdate(LutronCommandType.DEVICE, String.valueOf(index), String.valueOf(action));
+ } catch (NumberFormatException e) {
+ logger.warn("Number format exception parsing update");
+ } catch (RuntimeException e) {
+ logger.warn("Runtime exception while processing update");
+ }
+ } else {
+ logger.debug("No thing configured for integration ID {}", integrationId);
+ }
+ }
+
@Override
public void validMessageReceived(String communiqueType) {
reconnectTaskCancel(true); // Got a good message, so cancel reconnect task.
}
}
+ private @Nullable Entry<Integer, Integer> buttonToDeviceAndIndex(int buttonId) {
+ synchronized (deviceButtonMapLock) {
+ Integer deviceId = buttonToDevice.get(buttonId);
+ if (deviceId == null) {
+ return null;
+ }
+ List<Integer> buttonList = deviceButtonMap.get(deviceId);
+ int buttonIndex = buttonList.indexOf(buttonId);
+ if (buttonIndex == -1) {
+ return null;
+ }
+
+ return new SimpleEntry(deviceId, buttonIndex + 1);
+ }
+ }
+
/**
* Executed by keepAliveJob. Sends a LEAP ping request and schedules a reconnect task.
*/
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonGroup;
+import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonStatus;
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
import org.openhab.binding.lutron.internal.protocol.leap.dto.ExceptionDetail;
import org.openhab.binding.lutron.internal.protocol.leap.dto.Header;
handleReadResponseMessage(message);
break;
case "UpdateResponse":
+ handleReadResponseMessage(message);
break;
case "SubscribeResponse":
// Subscribe responses can contain bodies with data
case "OneDeviceDefinition":
parseOneDeviceDefinition(body);
break;
+ case "OneButtonStatusEvent":
+ parseOneButtonStatusEvent(body);
+ break;
case "MultipleAreaDefinition":
parseMultipleAreaDefinition(body);
break;
}
}
+ /**
+ * Parses a OneButtonStatusEvent message body. Calls handleButtonStatusEvent() to dispatch button events.
+ */
+ private void parseOneButtonStatusEvent(JsonObject messageBody) {
+ ButtonStatus buttonStatus = parseBodySingle(messageBody, "ButtonStatus", ButtonStatus.class);
+ if (buttonStatus != null) {
+ callback.handleButtonStatus(buttonStatus);
+ }
+ }
+
/**
* Parses a MultipleAreaDefinition message body.
*/
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.lutron.internal.protocol.leap.dto;
+
+import java.util.regex.Pattern;
+
+import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * LEAP ButtonStatus Object
+ *
+ * @author Cody Cutrer - Initial contribution
+ */
+public class ButtonStatus extends AbstractMessageBody {
+ public static final Pattern BUTTON_HREF_PATTERN = Pattern.compile("/button/([0-9]+)");
+
+ @SerializedName("ButtonEvent")
+ public ButtonEvent buttonEvent;
+ @SerializedName("Button")
+ public Href button = new Href();
+
+ public ButtonStatus() {
+ }
+
+ public int getButton() {
+ if (button != null) {
+ return hrefNumber(BUTTON_HREF_PATTERN, button.href);
+ } else {
+ return 0;
+ }
+ }
+}