Skip to content

Commit

Permalink
[shelly] Add Plus/Pro support, some bugfixes (openhab#13439)
Browse files Browse the repository at this point in the history
* Plus/Pro support and some refactoring, bugfixes

Signed-off-by: Markus Michels <markus7017@gmail.com>

* Review changes applied

Signed-off-by: Markus Michels <markus7017@gmail.com>

* review changes

Signed-off-by: Markus Michels <markus7017@gmail.com>

Signed-off-by: Markus Michels <markus7017@gmail.com>
  • Loading branch information
markus7017 authored and nemerdaud committed Feb 28, 2023
1 parent 39562f6 commit cccbb20
Show file tree
Hide file tree
Showing 41 changed files with 4,107 additions and 567 deletions.
327 changes: 284 additions & 43 deletions bundles/org.openhab.binding.shelly/README.md

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion bundles/org.openhab.binding.shelly/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@


<artifactId>org.openhab.binding.shelly</artifactId>
<name>openHAB Add-ons :: Bundles :: Shelly Binding</name>
<name>openHAB Add-ons :: Bundles :: Shelly Binding Gen1+2</name>

<dependencies>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
<version>9.4.46.v20220331</version>
<scope>compile</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,23 @@ public class ShellyBindingConstants {
public static final String BINDING_ID = "shelly";
public static final String SYSTEM_ID = "system";

public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1L, THING_TYPE_SHELLY1PM,
THING_TYPE_SHELLYEM, THING_TYPE_SHELLY3EM, THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY2_ROLLER,
THING_TYPE_SHELLY25_RELAY, THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG,
THING_TYPE_SHELLYPLUGS, THING_TYPE_SHELLYPLUGU1, THING_TYPE_SHELLYUNI, THING_TYPE_SHELLYDIMMER,
THING_TYPE_SHELLYDIMMER2, THING_TYPE_SHELLYIX3, THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO,
THING_TYPE_SHELLYVINTAGE, THING_TYPE_SHELLYDUORGBW, THING_TYPE_SHELLYRGBW2_COLOR,
THING_TYPE_SHELLYRGBW2_WHITE, THING_TYPE_SHELLYHT, THING_TYPE_SHELLYTRV, THING_TYPE_SHELLYSENSE,
THING_TYPE_SHELLYEYE, THING_TYPE_SHELLYSMOKE, THING_TYPE_SHELLYGAS, THING_TYPE_SHELLYFLOOD,
THING_TYPE_SHELLYDOORWIN, THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1,
THING_TYPE_SHELLYBUTTON2, THING_TYPE_SHELLMOTION, THING_TYPE_SHELLMOTION,
THING_TYPE_SHELLYPROTECTED, THING_TYPE_SHELLYUNKNOWN).collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1L, THING_TYPE_SHELLY1PM, THING_TYPE_SHELLYEM,
THING_TYPE_SHELLY3EM, THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY25_RELAY,
THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG, THING_TYPE_SHELLYPLUGS,
THING_TYPE_SHELLYPLUGU1, THING_TYPE_SHELLYUNI, THING_TYPE_SHELLYDIMMER, THING_TYPE_SHELLYDIMMER2,
THING_TYPE_SHELLYIX3, THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO, THING_TYPE_SHELLYVINTAGE,
THING_TYPE_SHELLYDUORGBW, THING_TYPE_SHELLYRGBW2_COLOR, THING_TYPE_SHELLYRGBW2_WHITE,
THING_TYPE_SHELLYHT, THING_TYPE_SHELLYTRV, THING_TYPE_SHELLYSENSE, THING_TYPE_SHELLYEYE,
THING_TYPE_SHELLYSMOKE, THING_TYPE_SHELLYGAS, THING_TYPE_SHELLYFLOOD, THING_TYPE_SHELLYDOORWIN,
THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1, THING_TYPE_SHELLYBUTTON2,
THING_TYPE_SHELLMOTION, THING_TYPE_SHELLMOTION, THING_TYPE_SHELLYPLUS1, THING_TYPE_SHELLYPLUS1PM,
THING_TYPE_SHELLYPLUS2PM_RELAY, THING_TYPE_SHELLYPLUS2PM_ROLLER, THING_TYPE_SHELLYPRO1,
THING_TYPE_SHELLYPRO1PM, THING_TYPE_SHELLYPRO2_RELAY, THING_TYPE_SHELLYPRO2PM_RELAY,
THING_TYPE_SHELLYPRO2PM_ROLLER, THING_TYPE_SHELLYPRO3, THING_TYPE_SHELLYPRO4PM,
THING_TYPE_SHELLYPLUSI4, THING_TYPE_SHELLYPLUSI4DC, THING_TYPE_SHELLYPLUSHT,
THING_TYPE_SHELLYPLUSPLUGUS, THING_TYPE_SHELLYPROTECTED, THING_TYPE_SHELLYUNKNOWN)
.collect(Collectors.toSet()));

// Thing Configuration Properties
public static final String CONFIG_DEVICEIP = "deviceIp";
Expand Down Expand Up @@ -218,6 +223,7 @@ public class ShellyBindingConstants {
public static final String SHELLY_API_MIN_FWCOIOT = "v1.6";// v1.6.0+
public static final String SHELLY_API_FWCOIOT2 = "v1.8";// CoAP 2 with FW 1.8+
public static final String SHELLY_API_FW_110 = "v1.10"; // FW 1.10 or newer detected, activates some add feature
public static final String SHELLY2_API_MIN_FWVERSION = "v0.10.2"; // Gen 2 minimum FW

// Alarm types/messages
public static final String ALARM_TYPE_NONE = "NONE";
Expand All @@ -238,7 +244,8 @@ public class ShellyBindingConstants {
public static final String EVENT_TYPE_SENSORDATA = "report";

// URI for the EventServlet
public static final String SHELLY_CALLBACK_URI = "/shelly/event";
public static final String SHELLY1_CALLBACK_URI = "/shelly/event";
public static final String SHELLY2_CALLBACK_URI = "/shelly/wsevent";

public static final int DIM_STEPSIZE = 5;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
*/
package org.openhab.binding.shelly.internal.api;

import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.NoRouteToHostException;
import java.net.PortUnreachableException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -65,19 +70,21 @@ public ShellyApiException(ShellyApiResult result, Exception exception) {

@Override
public String toString() {
String message = nonNullString(super.getMessage());
String message = nonNullString(super.getMessage()).replace("java.util.concurrent.ExecutionException: ", "")
.replace("java.net.", "");
String cause = getCauseClass().toString();
String url = apiResult.getUrl();
if (!isEmpty()) {
if (isUnknownHost()) {
String[] string = message.split(": "); // java.net.UnknownHostException: api.rach.io
message = MessageFormat.format("Unable to connect to {0} (Unknown host / Network down / Low signal)",
string[1]);
} else if (isMalformedURL()) {
message = MessageFormat.format("Invalid URL: {0}", apiResult.getUrl());
message = "Invalid URL: " + url;
} else if (isTimeout()) {
message = MessageFormat.format("Device unreachable or API Timeout ({0})", apiResult.getUrl());
} else {
message = MessageFormat.format("{0} ({1})", message, cause);
message = "API Timeout for " + url;
} else if (!isConnectionError()) {
message = message + "(" + cause + ")";
}
} else {
message = apiResult.toString();
Expand All @@ -91,21 +98,28 @@ public boolean isApiException() {

public boolean isTimeout() {
Class<?> extype = !isEmpty() ? getCauseClass() : null;
return (extype != null) && ((extype == TimeoutException.class) || (extype == ExecutionException.class)
|| (extype == InterruptedException.class)
return (extype != null) && ((extype == TimeoutException.class) || extype == InterruptedException.class
|| extype == SocketTimeoutException.class
|| nonNullString(getMessage()).toLowerCase().contains("timeout"));
}

public boolean isHttpAccessUnauthorized() {
return apiResult.isHttpAccessUnauthorized();
public boolean isConnectionError() {
Class<?> exType = getCauseClass();
return isUnknownHost() || isMalformedURL() || exType == ConnectException.class
|| exType == SocketException.class || exType == PortUnreachableException.class
|| exType == NoRouteToHostException.class;
}

public boolean isUnknownHost() {
return getCauseClass() == MalformedURLException.class;
return getCauseClass() == UnknownHostException.class;
}

public boolean isMalformedURL() {
return getCauseClass() == UnknownHostException.class;
return getCauseClass() == MalformedURLException.class;
}

public boolean isHttpAccessUnauthorized() {
return apiResult.isHttpAccessUnauthorized();
}

public boolean isJSONException() {
Expand All @@ -126,6 +140,9 @@ private static String nonNullString(@Nullable String s) {

private Class<?> getCauseClass() {
Throwable cause = getCause();
if (cause != null && cause.getClass() == ExecutionException.class) {
cause = cause.getCause();
}
if (cause != null) {
return cause.getClass();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLogin;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsUpdate;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
Expand Down Expand Up @@ -59,7 +60,7 @@ public interface ShellyApiInterface {

public void setRollerPos(int relayIndex, int position) throws ShellyApiException;

public void setTimer(int index, String timerName, int value) throws ShellyApiException;
public void setAutoTimer(int index, String timerName, double value) throws ShellyApiException;

public ShellyStatusSensor getSensorStatus() throws ShellyApiException;

Expand Down Expand Up @@ -92,12 +93,20 @@ public interface ShellyApiInterface {

public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException;

public ShellySettingsUpdate firmwareUpdate(String uri) throws ShellyApiException;

public ShellySettingsLogin getLoginSettings() throws ShellyApiException;

public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException;

public String setWiFiRecovery(boolean enable) throws ShellyApiException;

public boolean setWiFiRangeExtender(boolean enable) throws ShellyApiException;

public boolean setEthernet(boolean enable) throws ShellyApiException;

public boolean setBluetooth(boolean enable) throws ShellyApiException;

public String deviceReboot() throws ShellyApiException;

public String setDebug(boolean enabled) throws ShellyApiException;
Expand All @@ -123,4 +132,6 @@ public interface ShellyApiInterface {
public void setActionURLs() throws ShellyApiException;

public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException;

public void close();
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,20 @@ public class ShellyDeviceProfile {
public ShellyDeviceProfile() {
}

public ShellyDeviceProfile initialize(String thingType, String json) throws ShellyApiException {
public ShellyDeviceProfile initialize(String thingType, String jsonIn) throws ShellyApiException {
Gson gson = new Gson();

initialized = false;

initFromThingType(thingType);

String json = jsonIn;
if (json.contains("\"ext_temperature\":{\"0\":[{")) {
// Shelly UNI uses ext_temperature array, reformat to avoid GSON exception
json = json.replace("ext_temperature", "ext_temperature_array");
}
settingsJson = json;
ShellySettingsGlobal gs = fromJson(gson, json, ShellySettingsGlobal.class);
settings = gs; // only update when no exception
settings = fromJson(gson, json, ShellySettingsGlobal.class);

// General settings
name = getString(settings.name);
Expand Down Expand Up @@ -282,6 +287,7 @@ public String getInputSuffix(int i) {
return "";
}

@SuppressWarnings("null")
public boolean inButtonMode(int idx) {
if (idx < 0) {
logger.debug("{}: Invalid index {} for inButtonMode()", thingName, idx);
Expand Down Expand Up @@ -323,8 +329,8 @@ public boolean inButtonMode(int idx) {
}

public int getRollerFav(int id) {
if ((id >= 0) && getBool(settings.favoritesEnabled) && (settings.favorites != null)
&& (id < settings.favorites.size())) {
if (id >= 0 && getBool(settings.favoritesEnabled) && settings.favorites != null
&& id < settings.favorites.size()) {
return settings.favorites.get(id).pos;
}
return -1;
Expand All @@ -348,8 +354,11 @@ public String getValueProfile(int valveId, int profileId) {

public static String extractFwVersion(@Nullable String version) {
if (version != null) {
// fix version e.g. 20210319-122304/v.1.10-Dimmer1-gfd4cc10 (with v.1. instead of v1.)
String vers = version.replace("/v.1.10-", "/v1.10.0-");
// fix version e.g.
// 20210319-122304/v.1.10-Dimmer1-gfd4cc10 (with v.1. instead of v1.)
// 20220809-125346/v1.12-g99f7e0b (.0 in 1.12.0 missing)
String vers = version.replace("/v.1.10-", "/v1.10.0-") //
.replace("/v1.12-", "/v1.12.0");

// Extract version from string, e.g. 20210226-091047/v1.10.0-rc2-89-g623b41ec0-master
Matcher matcher = VERSION_PATTERN.matcher(vers);
Expand Down
Loading

0 comments on commit cccbb20

Please sign in to comment.