Each is described in a separate section below.
-# Lutron RadioRA 2/HomeWorks QS/RA2 Select/Caseta Binding
+# Lutron RadioRA 3/ RadioRA 2/HomeWorks QS/RA2 Select/Caseta Binding
**Note:** While the Lutron Integration Protocol used by ipbridge in this binding should largely be compatible with other current Lutron systems, it has only been fully tested with RadioRA 2, HomeWorks QS, and Caseta with Smart Bridge Pro.
Homeworks QS support is still a work in progress, since not all features/devices are supported yet.
This binding currently supports the following thing types:
- **ipbridge** - The Lutron main repeater/processor/hub
-- **leapbridge** - Experimental bridge that uses LEAP protocol (Caseta & RA2 Select only)
+- **leapbridge** - Experimental bridge that uses LEAP protocol (Caseta, Radio RA3 & RA2 Select only)
- **dimmer** - Light dimmer
- **switch** - Switch or relay module
- **fan** - Fan controller
Other supported Lutron systems must be configured manually.
**Note:** Discovery selects ipbridge for HomeWorks QS, RadioRA 2, RA2 Select, and Caseta Smart Bridge Pro.
-It select leapbridge for Caseta Smart Bridge, since only LEAP protocol is supported by this system.
+It select leapbridge for Caseta Smart Bridge and Radio RA 3, since only LEAP protocol is supported by these systems.
## Binding Configuration
The LIP protocol is supported by ipbridge while the LEAP protocol is supported by leapbridge.
Current systems support one or both protocols as shown below.
-|Bridge Device | LIP | LEAP |
-|------------------------|-----|------|
-|HomeWorks QS Processor | X | |
-|RadioRA 2 Main Repeater | X | |
-|RA2 Select Main Repeater| X | X |
-|Caseta Smart Bridge Pro | X | X |
-|Caseta Smart Bridge | | X |
+| Bridge Device | LIP | LEAP |
+|--------------------------|-----|------|
+| HomeWorks QS Processor | X | |
+| RadioRA 2 Main Repeater | X | |
+| RA2 Select Main Repeater | X | X |
+| Caseta Smart Bridge Pro | X | X |
+| Caseta Smart Bridge | | X |
+| RadioRA 3 Processor | | X |
If your system supports only one protocol, then the choice of bridge is easy.
If you have a system that supports both protocols, you must decide which you wish to use.
#### leapbridge [**experimental**]
-The leapbridge is an experimental bridge which allows the binding to work with the Caseta Smart Hub (non-Pro version).
+The leapbridge is an experimental bridge which allows the binding to work with the Caseta Smart Hub (non-Pro version) and the RadioRA 3 Processor.
It can also be used to provide additional features, such as support for occupancy groups and device discovery, when used with Caseta Smart Hub Pro or RA2 Select.
It uses the LEAP protocol over SSL, which is an undocumented protocol supported by some of Lutron's newer systems.
Note that the LEAP protocol will not notify the bridge of keypad key presses.
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);
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;
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;
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);
private int reconnectInterval;
private int heartbeatInterval;
private int sendDelay;
+ private boolean isRadioRA3 = false;
private @NonNullByDefault({}) SSLSocketFactory sslsocketfactory;
private @Nullable SSLSocket sslsocket;
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,
* 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()));
}
}
@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();
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);
}
}
}
}
+ @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.
* 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);
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;
case "OneZoneStatus":
parseOneZoneStatus(body);
break;
+ case "OneProjectDefinition":
+ parseOneProjectDefinition(body);
+ break;
+ case "OneDeviceDefinition":
+ parseOneDeviceDefinition(body);
+ break;
case "MultipleAreaDefinition":
parseMultipleAreaDefinition(body);
break;
break;
case "MultipleVirtualButtonDefinition":
break;
+ case "MultipleZoneStatus":
+ parseMultipleZoneStatus(body);
+ break;
default:
logger.debug("Unknown MessageBodyType received: {}", messageBodyType);
break;
}
}
+ 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);
}
/**
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);
+ }
+ }
}
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;
/**
@NonNullByDefault
public interface LeapMessageParserCallbacks {
+ void handleProjectDefinition(Project project);
+
+ void handleDeviceDefinition(Device device);
+
void validMessageReceived(String communiqueType);
void handleEmptyButtonGroupDefinition();
void handleMultipleButtonGroupDefinition(List<ButtonGroup> buttonGroupList);
- void handleMultipleDeviceDefintion(List<Device> deviceList);
+ void handleMultipleDeviceDefinition(List<Device> deviceList);
void handleMultipleAreaDefinition(List<Area> areaList);
}
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() {
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");
}
public static String subscribeOccupancyGroupStatus() {
return request(CommuniqueType.SUBSCRIBEREQUEST, "/occupancygroup/status");
}
+
+ public static String subscribeZoneStatus() {
+ return request(CommuniqueType.SUBSCRIBEREQUEST, "/zone/status");
+ }
}
@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 {
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;
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+}