[lutron] initial submission for RA3 support (#15541)

* [lutron] initial submission for RA3 support
* add parameter to getDevices(), remove getRA3Devices()

---------

Signed-off-by: Peter J Wojciechowski <peterwoj@dwellersoul.com>
This commit is contained in:
Peter Wojciechowski
2023-10-15 01:41:41 -07:00
committed by GitHub
parent 57b265574d
commit 3c179d3fa7
9 changed files with 240 additions and 39 deletions

View File

@@ -91,6 +91,11 @@ public class LeapDeviceDiscoveryService extends AbstractDiscoveryService
case "RA2SelectMainRepeater":
notifyDiscovery(THING_TYPE_VIRTUALKEYPAD, deviceId, label, "model", "Caseta");
break;
case "RadioRa3Processor":
notifyDiscovery(THING_TYPE_VIRTUALKEYPAD, deviceId, label, "model", "RadioRA 3");
break;
case "MaestroDimmer":
case "SunnataDimmer":
case "WallDimmer":
case "PlugInDimmer":
notifyDiscovery(THING_TYPE_DIMMER, deviceId, label);

View File

@@ -30,6 +30,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -66,6 +67,7 @@ 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.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.ZoneStatus;
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
import org.openhab.core.library.types.StringType;
@@ -94,6 +96,7 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
private static final long KEEPALIVE_TIMEOUT_SECONDS = 30;
private static final String STATUS_INITIALIZING = "Initializing";
private static final String LUTRON_RADIORA_3_PROJECT = "Lutron RadioRA 3 Project";
private final Logger logger = LoggerFactory.getLogger(LeapBridgeHandler.class);
@@ -101,6 +104,7 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
private int reconnectInterval;
private int heartbeatInterval;
private int sendDelay;
private boolean isRadioRA3 = false;
private @NonNullByDefault({}) SSLSocketFactory sslsocketfactory;
private @Nullable SSLSocket sslsocket;
@@ -305,9 +309,7 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
senderThread.start();
this.senderThread = senderThread;
sendCommand(new LeapCommand(Request.getButtonGroups()));
queryDiscoveryData();
sendCommand(new LeapCommand(Request.subscribeOccupancyGroupStatus()));
sendCommand(new LeapCommand(Request.getProject()));
logger.debug("Starting keepalive job with interval {}", heartbeatInterval);
keepAliveJob = scheduler.scheduleWithFixedDelay(this::sendKeepAlive, heartbeatInterval, heartbeatInterval,
@@ -318,7 +320,11 @@ 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.getDevices()));
if (!isRadioRA3) {
sendCommand(new LeapCommand(Request.getDevices()));
} else {
sendCommand(new LeapCommand(Request.getDevices(false)));
}
sendCommand(new LeapCommand(Request.getAreas()));
sendCommand(new LeapCommand(Request.getOccupancyGroups()));
}
@@ -591,7 +597,31 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
}
@Override
public void handleMultipleDeviceDefintion(List<Device> deviceList) {
public void handleDeviceDefinition(Device device) {
synchronized (zoneMapsLock) {
int deviceId = device.getDevice();
int zoneId = device.getZone();
if (zoneId > 0 && deviceId > 0) {
zoneToDevice.put(zoneId, deviceId);
deviceToZone.put(deviceId, zoneId);
}
if (deviceId == 1 || device.isThisDevice) {
setBridgeProperties(device);
}
}
checkInitialized();
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
if (discoveryService != null) {
discoveryService.processDeviceDefinitions(Arrays.asList(device));
}
}
@Override
public void handleMultipleDeviceDefinition(List<Device> deviceList) {
synchronized (zoneMapsLock) {
zoneToDevice.clear();
deviceToZone.clear();
@@ -603,7 +633,7 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
zoneToDevice.put(zoneid, deviceid);
deviceToZone.put(deviceid, zoneid);
}
if (deviceid == 1) { // ID 1 is the bridge
if (deviceid == 1 || device.isThisDevice) { // ID 1 is the bridge
setBridgeProperties(device);
}
}
@@ -633,6 +663,26 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
}
}
@Override
public void handleProjectDefinition(Project project) {
isRadioRA3 = LUTRON_RADIORA_3_PROJECT.equals(project.productType);
if (project.masterDeviceList.devices.length > 0 && project.masterDeviceList.devices[0].href != null) {
sendCommand(new LeapCommand(Request.getDevices(true)));
}
sendCommand(new LeapCommand(Request.getButtonGroups()));
queryDiscoveryData();
if (!isRadioRA3) {
logger.debug("Caseta Bridge Detected: {}", project.productType);
} else {
logger.debug("Detected a RadioRA 3 System: {}", project.productType);
sendCommand(new LeapCommand(Request.subscribeZoneStatus()));
}
sendCommand(new LeapCommand(Request.subscribeOccupancyGroupStatus()));
}
@Override
public void validMessageReceived(String communiqueType) {
reconnectTaskCancel(true); // Got a good message, so cancel reconnect task.
@@ -642,7 +692,7 @@ public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessag
* Set informational bridge properties from the Device entry for the hub/repeater
*/
private void setBridgeProperties(Device device) {
if (device.getDevice() == 1 && device.repeaterProperties != null) {
if ((device.getDevice() == 1 && device.repeaterProperties != null) || (device.isThisDevice)) {
Map<String, String> properties = editProperties();
if (device.name != null) {
properties.put(PROPERTY_PRODTYP, device.name);

View File

@@ -25,6 +25,7 @@ import org.openhab.binding.lutron.internal.protocol.leap.dto.ExceptionDetail;
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.ZoneStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -181,6 +182,12 @@ public class LeapMessageParser {
case "OneZoneStatus":
parseOneZoneStatus(body);
break;
case "OneProjectDefinition":
parseOneProjectDefinition(body);
break;
case "OneDeviceDefinition":
parseOneDeviceDefinition(body);
break;
case "MultipleAreaDefinition":
parseMultipleAreaDefinition(body);
break;
@@ -198,6 +205,9 @@ public class LeapMessageParser {
break;
case "MultipleVirtualButtonDefinition":
break;
case "MultipleZoneStatus":
parseMultipleZoneStatus(body);
break;
default:
logger.debug("Unknown MessageBodyType received: {}", messageBodyType);
break;
@@ -297,13 +307,21 @@ public class LeapMessageParser {
}
}
private void parseMultipleZoneStatus(JsonObject messageBody) {
List<ZoneStatus> statusList = parseBodyMultiple(messageBody, "ZoneStatuses", ZoneStatus.class);
for (ZoneStatus status : statusList) {
logger.debug("Setting zone {} to level: {}", status.href, status.level);
callback.handleZoneUpdate(status);
}
}
/**
* 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.
*/
private void parseMultipleDeviceDefinition(JsonObject messageBody) {
List<Device> deviceList = parseBodyMultiple(messageBody, "Devices", Device.class);
callback.handleMultipleDeviceDefintion(deviceList);
callback.handleMultipleDeviceDefinition(deviceList);
}
/**
@@ -313,4 +331,18 @@ public class LeapMessageParser {
List<ButtonGroup> buttonGroupList = parseBodyMultiple(messageBody, "ButtonGroups", ButtonGroup.class);
callback.handleMultipleButtonGroupDefinition(buttonGroupList);
}
private void parseOneProjectDefinition(JsonObject messageBody) {
Project project = parseBodySingle(messageBody, "Project", Project.class);
if (project != null) {
callback.handleProjectDefinition(project);
}
}
private void parseOneDeviceDefinition(JsonObject messageBody) {
Device device = parseBodySingle(messageBody, "Device", Device.class);
if (device != null) {
callback.handleDeviceDefinition(device);
}
}
}

View File

@@ -19,6 +19,7 @@ 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.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.ZoneStatus;
/**
@@ -29,6 +30,10 @@ import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
@NonNullByDefault
public interface LeapMessageParserCallbacks {
void handleProjectDefinition(Project project);
void handleDeviceDefinition(Device device);
void validMessageReceived(String communiqueType);
void handleEmptyButtonGroupDefinition();
@@ -39,7 +44,7 @@ public interface LeapMessageParserCallbacks {
void handleMultipleButtonGroupDefinition(List<ButtonGroup> buttonGroupList);
void handleMultipleDeviceDefintion(List<Device> deviceList);
void handleMultipleDeviceDefinition(List<Device> deviceList);
void handleMultipleAreaDefinition(List<Area> areaList);

View File

@@ -108,7 +108,22 @@ public class Request {
}
public static String getDevices() {
return request(CommuniqueType.READREQUEST, "/device");
return getDevices("");
}
public static String getDevices(boolean thisDevice) {
String url = String.format("where=IsThisDevice:%s", (thisDevice) ? "true" : "false");
return getDevices(url);
}
public static String getDevices(String predicate) {
String url = "/device";
if (!predicate.isEmpty()) {
url = String.format("%s?%s", url, predicate);
}
return request(CommuniqueType.READREQUEST, url);
}
public static String getVirtualButtons() {
@@ -119,6 +134,10 @@ public class Request {
return request(CommuniqueType.READREQUEST, BUTTON_GROUP_URL);
}
public static String getProject() {
return request(CommuniqueType.READREQUEST, "/project");
}
public static String getAreas() {
return request(CommuniqueType.READREQUEST, "/area");
}
@@ -138,4 +157,8 @@ public class Request {
public static String subscribeOccupancyGroupStatus() {
return request(CommuniqueType.SUBSCRIBEREQUEST, "/occupancygroup/status");
}
public static String subscribeZoneStatus() {
return request(CommuniqueType.SUBSCRIBEREQUEST, "/zone/status");
}
}

View File

@@ -69,11 +69,14 @@ public class Device extends AbstractMessageBody {
@SerializedName("FirmwareImage")
public FirmwareImage firmwareImage;
@SerializedName("IsThisDevice")
public boolean isThisDevice;
public class FirmwareImage {
@SerializedName("Firmware")
public Firmware firmware;
@SerializedName("Installed")
public Installed installed;
public ProjectTimestamp installed;
}
public class Firmware {
@@ -81,23 +84,6 @@ public class Device extends AbstractMessageBody {
public String displayName;
}
public class Installed {
@SerializedName("Year")
public int year;
@SerializedName("Month")
public int month;
@SerializedName("Day")
public int day;
@SerializedName("Hour")
public int hour;
@SerializedName("Minute")
public int minute;
@SerializedName("Second")
public int second;
@SerializedName("Utc")
public String utc;
}
public class RepeaterProperties {
@SerializedName("IsRepeater")
public boolean isRepeater;

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2023 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 Project Object
*
* @author Peter Wojciechowski - Initial contribution
*/
public class Project extends AbstractMessageBody {
@SerializedName("href")
public String href;
@SerializedName("Name")
public String name;
@SerializedName("ProductType")
public String productType;
@SerializedName("MasterDeviceList")
public MasterDeviceList masterDeviceList;
@SerializedName("Contacts")
public Href[] contacts;
@SerializedName("TimeclockEventRules")
public Href timeclockEventRules;
@SerializedName("ProjectModifiedTimestamp")
public ProjectTimestamp projectModifiedTimestamp;
public class MasterDeviceList {
public static final Pattern DEVICE_HREF_PATTERN = Pattern.compile("/device/([0-9]+)");
public int getDeviceIdFromHref(int deviceIndex) {
if (devices.length == 0) {
return 0;
}
return hrefNumber(DEVICE_HREF_PATTERN, devices[deviceIndex].href);
}
@SerializedName("Devices")
public Href[] devices;
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2023 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;
/**
* LEAP ProjectTimestamp Object
*
* @author Peter Wojciechowski - Initial contribution
*/
public class ProjectTimestamp {
@SerializedName("Year")
public int year;
@SerializedName("Month")
public int month;
@SerializedName("Day")
public int day;
@SerializedName("Hour")
public int hour;
@SerializedName("Minute")
public int minute;
@SerializedName("Second")
public int second;
@SerializedName("Utc")
public String utc;
}