[lutron] Provides device location and names during discovery (#18484)

* [lutron] Provides device location and names during discovery

Signed-off-by: Peter J Wojciechowski <peterwoj@dwellersoul.com>
pull/18619/head
Peter Wojciechowski 2025-04-28 12:20:15 -07:00 committed by GitHub
parent 72cdd823a3
commit 5e51365de5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 243 additions and 7 deletions

View File

@ -68,11 +68,24 @@ public class LeapDeviceDiscoveryService extends AbstractThingHandlerDiscoverySer
thingHandler.queryDiscoveryData();
}
public void processDeviceDefinitions(List<Device> deviceList) {
public void processDeviceDefinitions(List<Device> deviceList, Map<Integer, String> zoneIdToName,
Map<Integer, String> areaIdToName) {
for (Device device : deviceList) {
// Integer zoneid = device.getZone();
Integer zoneId = device.getZone();
Integer deviceId = device.getDevice();
Integer areaId = device.getArea();
String label = device.getFullyQualifiedName();
// RA3 doesn't provide a name for the above instead you
// need to get the Area and Zone name to have it make sense
if (label.isEmpty()) {
String areaName = areaIdToName.getOrDefault(areaId, "");
if (areaName.isEmpty()) {
label = zoneIdToName.getOrDefault(zoneId, "");
} else {
label = String.format("%s - %s", areaName, zoneIdToName.getOrDefault(zoneId, ""));
}
}
if (deviceId > 0) {
logger.debug("Discovered device: {} type: {} id: {}", label, device.deviceType, deviceId);
if (device.deviceType != null) {
@ -117,6 +130,11 @@ public class LeapDeviceDiscoveryService extends AbstractThingHandlerDiscoverySer
case "QsWirelessShade":
notifyDiscovery(THING_TYPE_SHADE, deviceId, label);
break;
case "RPSWallMountedOccupancySensor":
// TODO: Handle ra3 OccupancySensors, will need to get area
// that the sensor is associated with to get at the sensor
// status
break;
case "RPSOccupancySensor":
// Don't discover sensors. Using occupancy groups instead.
break;

View File

@ -72,6 +72,7 @@ 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;
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneExpanded;
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
import org.openhab.core.library.types.StringType;
@ -133,6 +134,11 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
private final Map<Integer, Integer> deviceToZone = new HashMap<>();
private final Object zoneMapsLock = new Object();
private final Object zoneIdToNameLock = new Object();
private final Map<Integer, String> zoneIdToName = new HashMap<>();
public final Object areaIdToNameLock = new Object();
public final Map<Integer, String> areaIdToName = new HashMap<>();
private @Nullable Map<Integer, List<Integer>> deviceButtonMap;
private Map<Integer, Integer> buttonToDevice = new HashMap<>();
private final Object deviceButtonMapLock = new Object();
@ -325,12 +331,14 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
* Called by connect() and discovery service to request fresh discovery data
*/
public void queryDiscoveryData() {
sendCommand(new LeapCommand(Request.getAreas()));
if (!isRadioRA3) {
sendCommand(new LeapCommand(Request.getDevices()));
} else {
sendCommand(new LeapCommand(Request.getZoneStatuses()));
sendCommand(new LeapCommand(Request.getDevices(false)));
}
sendCommand(new LeapCommand(Request.getAreas()));
sendCommand(new LeapCommand(Request.getOccupancyGroups()));
}
@ -628,7 +636,7 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
discoveryService.processDeviceDefinitions(Arrays.asList(device));
discoveryService.processDeviceDefinitions(Arrays.asList(device), zoneIdToName, areaIdToName);
}
}
@ -655,12 +663,36 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
discoveryService.processDeviceDefinitions(deviceList);
discoveryService.processDeviceDefinitions(deviceList, zoneIdToName, areaIdToName);
}
}
@Override
public void handleMultipleZoneExpandedUpdate(List<ZoneExpanded> expandedZones) {
synchronized (zoneIdToNameLock) {
zoneIdToName.clear();
for (ZoneExpanded zone : expandedZones) {
int zoneId = zone.zone.getZone();
if (zoneId > 0) {
zoneIdToName.put(zoneId, zone.zone.name);
}
}
}
}
@Override
public void handleMultipleAreaDefinition(List<Area> areaList) {
synchronized (areaIdToNameLock) {
areaIdToName.clear();
for (Area area : areaList) {
int areaId = area.getArea();
if (areaId > 0) {
logger.debug("area[{}] == {}", areaId, area.name);
areaIdToName.put(areaId, area.name);
}
}
}
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
discoveryService.setAreas(areaList);

View File

@ -27,6 +27,7 @@ import org.openhab.binding.lutron.internal.protocol.leap.dto.Header;
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroupStatus;
import org.openhab.binding.lutron.internal.protocol.leap.dto.Project;
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneExpanded;
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -213,6 +214,9 @@ public class LeapMessageParser {
case "MultipleZoneStatus":
parseMultipleZoneStatus(body);
break;
case "MultipleZoneExpandedStatus":
parseMultipleZoneExpandedStatus(body);
break;
default:
logger.debug("Unknown MessageBodyType received: {}", messageBodyType);
break;
@ -330,6 +334,12 @@ public class LeapMessageParser {
}
}
private void parseMultipleZoneExpandedStatus(JsonObject messageBody) {
List<ZoneExpanded> zoneExpandedList = parseBodyMultiple(messageBody, "ZoneExpandedStatuses",
ZoneExpanded.class);
callback.handleMultipleZoneExpandedUpdate(zoneExpandedList);
}
/**
* Parses a MultipleDeviceDefinition message body and loads the zoneToDevice and deviceToZone maps. Also passes the
* device data on to the discovery service and calls setBridgeProperties() with the hub's device entry.

View File

@ -21,6 +21,7 @@ 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;
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneExpanded;
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
/**
@ -41,6 +42,8 @@ public interface LeapMessageParserCallbacks {
void handleZoneUpdate(ZoneStatus zoneStatus);
void handleMultipleZoneExpandedUpdate(List<ZoneExpanded> expandedZones);
void handleGroupUpdate(int groupNumber, String occupancyStatus);
void handleMultipleButtonGroupDefinition(List<ButtonGroup> buttonGroupList);

View File

@ -14,6 +14,9 @@ package org.openhab.binding.lutron.internal.protocol.leap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
import org.openhab.binding.lutron.internal.protocol.leap.dto.LeapRequest;
import com.google.gson.Gson;
/**
* Contains static methods for constructing LEAP messages
@ -99,8 +102,13 @@ public class Request {
}
public static String request(CommuniqueType cType, String url) {
String request = "{\"CommuniqueType\": \"%s\",\"Header\": {\"Url\": \"%s\"}}";
return String.format(request, cType.toString(), url);
LeapRequest leapRequest = new LeapRequest();
leapRequest.communiqueType = cType.toString();
leapRequest.header = new LeapRequest.Header();
leapRequest.header.url = url;
Gson gson = new Gson();
return gson.toJson(leapRequest);
}
public static String ping() {
@ -150,6 +158,11 @@ public class Request {
return request(CommuniqueType.READREQUEST, String.format("/zone/%d/status", zone));
}
public static String getZoneStatuses() {
return request(CommuniqueType.READREQUEST,
"/zone/status/expanded?where=Zone.ControlType:\"Dimmed\"|\"Switched\"|\"CCO\"|\"Shade\"");
}
public static String getOccupancyGroupStatus() {
return request(CommuniqueType.READREQUEST, "/occupancygroup/status");
}
@ -165,4 +178,8 @@ public class Request {
public static String subscribeZoneStatus() {
return request(CommuniqueType.SUBSCRIBEREQUEST, "/zone/status");
}
public static String subscribeAreaStatus() {
return request(CommuniqueType.SUBSCRIBEREQUEST, "/area/status");
}
}

View File

@ -26,6 +26,7 @@ import com.google.gson.annotations.SerializedName;
public class Device extends AbstractMessageBody {
public static final Pattern DEVICE_HREF_PATTERN = Pattern.compile("/device/([0-9]+)");
private static final Pattern ZONE_HREF_PATTERN = Pattern.compile("/zone/([0-9]+)");
private static final Pattern AREA_HREF_PATTERN = Pattern.compile("/area/([0-9]+)");
@SerializedName("href")
public String href;
@ -108,6 +109,14 @@ public class Device extends AbstractMessageBody {
}
}
public int getArea() {
if (associatedArea != null && !associatedArea.href.isEmpty()) {
return hrefNumber(AREA_HREF_PATTERN, associatedArea.href);
} else {
return 0;
}
}
public String getFullyQualifiedName() {
if (fullyQualifiedName != null && fullyQualifiedName.length > 0) {
return String.join(" ", fullyQualifiedName);

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2010-2025 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 com.google.gson.annotations.SerializedName;
/**
* LeapRequest object
*
* @author Peter J Wojciechowski - initial
*/
public class LeapRequest {
@SerializedName("CommuniqueType")
public String communiqueType = "";
@SerializedName("Header")
public Header header;
@SerializedName("Body")
public Body body;
public static class Body {
@SerializedName("Command")
public Command command;
}
public static class Command {
@SerializedName("CommandType")
public String commandType;
@SerializedName("Parameter")
public CommandParameter commandParameter;
public static class CommandParameter {
@SerializedName("Type")
public String type;
@SerializedName("Value")
public String value;
}
}
public static class Header {
@SerializedName("Url")
public String url;
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2010-2025 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 Zone Object
*
* @author Peter J Wojciechowski - Initial contribution
*/
public class Zone extends AbstractMessageBody {
public static final Pattern ZONE_HREF_PATTERN = Pattern.compile("/zone/([0-9]+)");
@SerializedName("href")
public String href = "";
@SerializedName("XID")
public String xid = "";
@SerializedName("Name")
public String name = "";
@SerializedName("AvailableControlTypes")
public String[] controlTypes = {};
@SerializedName("Category")
public ZoneCategory zoneCategory = new ZoneCategory();
@SerializedName("AssociatedArea")
public Href associatedArea = new Href();
@SerializedName("SortOrder")
public int sortOrder;
@SerializedName("ControlType")
public String controlType = "";
public int getZone() {
if (href != null) {
return hrefNumber(ZONE_HREF_PATTERN, href);
} else {
return 0;
}
}
public class ZoneCategory {
@SerializedName("Type")
public String type = "";
@SerializedName("IsLight")
public boolean isLight = false;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2010-2025 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 org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
import com.google.gson.annotations.SerializedName;
/**
* ZoneExpanded object
*
* @author Peter J Wojciechowski - initial
*/
public class ZoneExpanded extends AbstractMessageBody {
@SerializedName("href")
public String href;
@SerializedName("Level")
public int level;
@SerializedName("StatusAccuracy")
public String statusAccuracy;
@SerializedName("Zone")
public Zone zone;
}