[shelly] Add Plus/Pro support, some bugfixes (#13439)
* 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>
This commit is contained in:
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -65,19 +70,21 @@ public class ShellyApiException extends 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();
|
||||
@@ -91,23 +98,30 @@ public class ShellyApiException extends Exception {
|
||||
|
||||
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 isConnectionError() {
|
||||
Class<?> exType = getCauseClass();
|
||||
return isUnknownHost() || isMalformedURL() || exType == ConnectException.class
|
||||
|| exType == SocketException.class || exType == PortUnreachableException.class
|
||||
|| exType == NoRouteToHostException.class;
|
||||
}
|
||||
|
||||
public boolean isUnknownHost() {
|
||||
return getCauseClass() == UnknownHostException.class;
|
||||
}
|
||||
|
||||
public boolean isMalformedURL() {
|
||||
return getCauseClass() == MalformedURLException.class;
|
||||
}
|
||||
|
||||
public boolean isHttpAccessUnauthorized() {
|
||||
return apiResult.isHttpAccessUnauthorized();
|
||||
}
|
||||
|
||||
public boolean isUnknownHost() {
|
||||
return getCauseClass() == MalformedURLException.class;
|
||||
}
|
||||
|
||||
public boolean isMalformedURL() {
|
||||
return getCauseClass() == UnknownHostException.class;
|
||||
}
|
||||
|
||||
public boolean isJSONException() {
|
||||
return getCauseClass() == JsonSyntaxException.class;
|
||||
}
|
||||
@@ -126,6 +140,9 @@ public class ShellyApiException extends Exception {
|
||||
|
||||
private Class<?> getCauseClass() {
|
||||
Throwable cause = getCause();
|
||||
if (cause != null && cause.getClass() == ExecutionException.class) {
|
||||
cause = cause.getCause();
|
||||
}
|
||||
if (cause != null) {
|
||||
return cause.getClass();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerSt
|
||||
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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -123,4 +132,6 @@ public interface ShellyApiInterface {
|
||||
public void setActionURLs() throws ShellyApiException;
|
||||
|
||||
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException;
|
||||
|
||||
public void close();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -282,6 +287,7 @@ public class ShellyDeviceProfile {
|
||||
return "";
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public boolean inButtonMode(int idx) {
|
||||
if (idx < 0) {
|
||||
logger.debug("{}: Invalid index {} for inButtonMode()", thingName, idx);
|
||||
@@ -323,8 +329,8 @@ public class ShellyDeviceProfile {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -348,8 +354,11 @@ public class ShellyDeviceProfile {
|
||||
|
||||
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);
|
||||
|
||||
@@ -21,89 +21,86 @@ import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2RpcSocket;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.ConfigurationPolicy;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link ShellyEventServlet} implements a servlet. which is called by the Shelly device to signnal events (button,
|
||||
* relay output, sensor data). The binding automatically sets those vent urls on startup (when not disabled in the thing
|
||||
* config).
|
||||
* {@link Shelly2RpcServlet} implements the WebSocket callback for Gen2 devices
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@WebServlet(name = "ShellyEventServlet", urlPatterns = { SHELLY1_CALLBACK_URI, SHELLY2_CALLBACK_URI })
|
||||
@Component(service = HttpServlet.class, configurationPolicy = ConfigurationPolicy.OPTIONAL)
|
||||
public class ShellyEventServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 549582869577534569L;
|
||||
public class ShellyEventServlet extends WebSocketServlet {
|
||||
private static final long serialVersionUID = -1210354558091063207L;
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyEventServlet.class);
|
||||
|
||||
private final HttpService httpService;
|
||||
private final ShellyHandlerFactory handlerFactory;
|
||||
private final ShellyThingTable thingTable;
|
||||
|
||||
@Activate
|
||||
public ShellyEventServlet(@Reference HttpService httpService, @Reference ShellyHandlerFactory handlerFactory,
|
||||
Map<String, Object> config) {
|
||||
this.httpService = httpService;
|
||||
public ShellyEventServlet(@Reference ShellyHandlerFactory handlerFactory, @Reference ShellyThingTable thingTable) {
|
||||
this.handlerFactory = handlerFactory;
|
||||
try {
|
||||
httpService.registerServlet(SHELLY_CALLBACK_URI, this, null, httpService.createDefaultHttpContext());
|
||||
logger.debug("ShellyEventServlet started at '{}'", SHELLY_CALLBACK_URI);
|
||||
} catch (NamespaceException | ServletException | IllegalArgumentException e) {
|
||||
logger.warn("Could not start CallbackServlet", e);
|
||||
}
|
||||
this.thingTable = thingTable;
|
||||
logger.debug("Shelly EventServlet started at {} and {}", SHELLY1_CALLBACK_URI, SHELLY2_CALLBACK_URI);
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
httpService.unregister(SHELLY_CALLBACK_URI);
|
||||
logger.debug("ShellyEventServlet stopped");
|
||||
logger.debug("ShellyEventServlet: Stopping");
|
||||
}
|
||||
|
||||
/**
|
||||
* Servlet handler. Shelly1: http request, Shelly2: WebSocket call
|
||||
*/
|
||||
@Override
|
||||
protected void service(@Nullable HttpServletRequest request, @Nullable HttpServletResponse resp)
|
||||
protected void service(HttpServletRequest request, HttpServletResponse resp)
|
||||
throws ServletException, IOException, IllegalArgumentException {
|
||||
String path = "";
|
||||
String deviceName = "";
|
||||
String index = "";
|
||||
String type = "";
|
||||
String path = getString(request.getRequestURI()).toLowerCase();
|
||||
|
||||
if ((request == null) || (resp == null)) {
|
||||
logger.debug("request or resp must not be null!");
|
||||
if (path.equals(SHELLY2_CALLBACK_URI)) { // Shelly2 WebSocket
|
||||
super.service(request, resp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Shelly1: http events, URL looks like
|
||||
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/relay/n?xxxxx or
|
||||
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/roller/n?xxxxx or
|
||||
// <ip address>:<remote port>/shelly/event/shellyht-XXXXXX/sensordata?hum=53,temp=26.50
|
||||
String deviceName = "";
|
||||
String index = "";
|
||||
String type = "";
|
||||
try {
|
||||
path = getString(request.getRequestURI()).toLowerCase();
|
||||
String ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
if (ipAddress == null) {
|
||||
ipAddress = request.getRemoteAddr();
|
||||
}
|
||||
String ipAddress = request.getRemoteAddr();
|
||||
Map<String, String[]> parameters = request.getParameterMap();
|
||||
logger.debug("CallbackServlet: {} Request from {}:{}{}?{}", request.getProtocol(), ipAddress,
|
||||
logger.debug("ShellyEventServlet: {} Request from {}:{}{}?{}", request.getProtocol(), ipAddress,
|
||||
request.getRemotePort(), path, parameters.toString());
|
||||
if (!path.toLowerCase().startsWith(SHELLY_CALLBACK_URI) || !path.contains("/event/shelly")) {
|
||||
logger.warn("CallbackServlet received unknown request: path = {}", path);
|
||||
if (!path.toLowerCase().startsWith(SHELLY1_CALLBACK_URI) || !path.contains("/event/shelly")) {
|
||||
logger.warn("ShellyEventServlet received unknown request: path = {}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
// URL looks like
|
||||
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/relay/n?xxxxx or
|
||||
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/roller/n?xxxxx or
|
||||
// <ip address>:<remote port>/shelly/event/shellyht-XXXXXX/sensordata?hum=53,temp=26.50
|
||||
deviceName = substringBetween(path, "/event/", "/").toLowerCase();
|
||||
if (path.contains("/" + EVENT_TYPE_RELAY + "/") || path.contains("/" + EVENT_TYPE_ROLLER + "/")
|
||||
|| path.contains("/" + EVENT_TYPE_LIGHT + "/")) {
|
||||
@@ -114,8 +111,8 @@ public class ShellyEventServlet extends HttpServlet {
|
||||
type = substringAfterLast(path, "/").toLowerCase();
|
||||
}
|
||||
logger.trace("{}: Process event of type type={}, index={}", deviceName, type, index);
|
||||
Map<String, String> parms = new TreeMap<>();
|
||||
|
||||
Map<String, String> parms = new TreeMap<>();
|
||||
for (Map.Entry<String, String[]> p : parameters.entrySet()) {
|
||||
parms.put(p.getKey(), p.getValue()[0]);
|
||||
|
||||
@@ -129,4 +126,39 @@ public class ShellyEventServlet extends HttpServlet {
|
||||
resp.getWriter().write("");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @Override
|
||||
* public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
* {
|
||||
* response.getWriter().println("HTTP GET method not implemented.");
|
||||
* }
|
||||
*/
|
||||
/**
|
||||
* WebSocket: register Shelly2RpcSocket class
|
||||
*/
|
||||
@Override
|
||||
public void configure(@Nullable WebSocketServletFactory factory) {
|
||||
if (factory != null) {
|
||||
factory.getPolicy().setIdleTimeout(15000);
|
||||
factory.setCreator(new Shelly2WebSocketCreator(thingTable));
|
||||
factory.register(Shelly2RpcSocket.class);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Shelly2WebSocketCreator implements WebSocketCreator {
|
||||
private final Logger logger = LoggerFactory.getLogger(Shelly2WebSocketCreator.class);
|
||||
|
||||
private final ShellyThingTable thingTable;
|
||||
|
||||
public Shelly2WebSocketCreator(ShellyThingTable thingTable) {
|
||||
this.thingTable = thingTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createWebSocket(@Nullable ServletUpgradeRequest req, @Nullable ServletUpgradeResponse resp) {
|
||||
logger.debug("WebSocket: Create socket from servlet");
|
||||
return new Shelly2RpcSocket(thingTable, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||
import org.slf4j.Logger;
|
||||
@@ -64,7 +65,6 @@ public class ShellyHttpClient {
|
||||
public ShellyHttpClient(String thingName, ShellyThingInterface thing) {
|
||||
this(thingName, thing.getThingConfig(), thing.getHttpClient());
|
||||
this.profile = thing.getProfile();
|
||||
profile.initFromThingType(thingName);
|
||||
}
|
||||
|
||||
public ShellyHttpClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
|
||||
@@ -111,8 +111,9 @@ public class ShellyHttpClient {
|
||||
}
|
||||
return apiResult.response; // successful
|
||||
} catch (ShellyApiException e) {
|
||||
if ((!e.isTimeout() && !apiResult.isHttpServerError()) && !apiResult.isNotFound() || profile.hasBattery
|
||||
|| (retries == 0)) {
|
||||
if (e.isConnectionError()
|
||||
|| (!e.isTimeout() && !apiResult.isHttpServerError()) && !apiResult.isNotFound()
|
||||
|| profile.hasBattery || (retries == 0)) {
|
||||
// Sensor in sleep mode or API exception for non-battery device or retry counter expired
|
||||
throw e; // non-timeout exception
|
||||
}
|
||||
@@ -154,6 +155,16 @@ public class ShellyHttpClient {
|
||||
String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim();
|
||||
logger.trace("{}: HTTP Response {}: {}", thingName, contentResponse.getStatus(), response);
|
||||
|
||||
if (response.contains("\"error\":{")) { // Gen2
|
||||
Shelly2RpcBaseMessage message = gson.fromJson(response, Shelly2RpcBaseMessage.class);
|
||||
if (message != null && message.error != null) {
|
||||
apiResult.httpCode = message.error.code;
|
||||
apiResult.response = message.error.message;
|
||||
if (getInteger(message.error.code) == HttpStatus.UNAUTHORIZED_401) {
|
||||
apiResult.authResponse = getString(message.error.message).replaceAll("\\\"", "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
HttpFields headers = contentResponse.getHeaders();
|
||||
String auth = headers.get(HttpHeader.WWW_AUTHENTICATE);
|
||||
if (!getString(auth).isEmpty()) {
|
||||
@@ -171,7 +182,7 @@ public class ShellyHttpClient {
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) {
|
||||
ShellyApiException ex = new ShellyApiException(apiResult, e);
|
||||
if (!ex.isTimeout()) { // will be handled by the caller
|
||||
if (!ex.isConnectionError() && !ex.isTimeout()) { // will be handled by the caller
|
||||
logger.trace("{}: API call returned exception", thingName, ex);
|
||||
}
|
||||
throw ex;
|
||||
|
||||
@@ -283,6 +283,7 @@ public class Shelly1ApiJsonDTO {
|
||||
public Boolean enabled;
|
||||
public String ssid;
|
||||
public String key;
|
||||
public Boolean rangeExtender; // Gen2 only
|
||||
}
|
||||
|
||||
public static class ShellySettingsWiFiNetwork {
|
||||
@@ -605,6 +606,7 @@ public class Shelly1ApiJsonDTO {
|
||||
@SerializedName("max_power")
|
||||
public Double maxPower;
|
||||
public Boolean calibrated;
|
||||
|
||||
public Double voltage; // AC voltage for Shelly 2.5
|
||||
@SerializedName("supply_voltage")
|
||||
public Long supplyVoltage; // Shelly 1PM/1L: 0=110V, 1=220V
|
||||
@@ -675,6 +677,10 @@ public class Shelly1ApiJsonDTO {
|
||||
|
||||
@SerializedName("sleep_time") // Shelly Motion
|
||||
public Integer sleepTime;
|
||||
|
||||
// Gen2
|
||||
public Boolean ethernet;
|
||||
public Boolean bluetooth;
|
||||
}
|
||||
|
||||
public static class ShellySettingsAttributes {
|
||||
@@ -701,7 +707,8 @@ public class Shelly1ApiJsonDTO {
|
||||
public String name; // FW 1.8: Symbolic Device name is configurable
|
||||
|
||||
@SerializedName("wifi_sta")
|
||||
public ShellySettingsWiFiNetwork wifiSta = new ShellySettingsWiFiNetwork();
|
||||
public ShellySettingsWiFiNetwork wifiSta = new ShellySettingsWiFiNetwork(); // WiFi client configuration. See
|
||||
// /settings/sta for details
|
||||
public ShellyStatusCloud cloud = new ShellyStatusCloud();
|
||||
public ShellyStatusMqtt mqtt = new ShellyStatusMqtt();
|
||||
|
||||
@@ -715,13 +722,14 @@ public class Shelly1ApiJsonDTO {
|
||||
public Integer cfgChangedCount; // FW 1.8
|
||||
@SerializedName("actions_stats")
|
||||
public ShellyActionsStats astats;
|
||||
public Double voltage; // Shelly 2.5
|
||||
|
||||
public Integer input; // RGBW2 has no JSON array
|
||||
public ArrayList<ShellySettingsRelay> relays;
|
||||
public ArrayList<ShellyRollerStatus> rollers;
|
||||
public ArrayList<ShellyShortLightStatus> dimmers;
|
||||
public Double voltage; // Shelly 2.5
|
||||
public Integer input; // RGBW2 has no JSON array
|
||||
public ArrayList<ShellyInputState> inputs;
|
||||
public ArrayList<ShellyShortLightStatus> dimmers;
|
||||
public ArrayList<ShellyRollerStatus> rollers;
|
||||
public ArrayList<ShellySettingsLight> lights;
|
||||
public ArrayList<ShellySettingsMeter> meters;
|
||||
public ArrayList<ShellySettingsEMeter> emeters;
|
||||
@SerializedName("ext_temperature")
|
||||
@@ -743,7 +751,6 @@ public class Shelly1ApiJsonDTO {
|
||||
public ArrayList<ShellyThermnostat> thermostats;
|
||||
|
||||
public ShellySettingsUpdate update = new ShellySettingsUpdate();
|
||||
|
||||
@SerializedName("ram_total")
|
||||
public Long ramTotal;
|
||||
@SerializedName("ram_free")
|
||||
@@ -798,7 +805,6 @@ public class Shelly1ApiJsonDTO {
|
||||
public Boolean ison; // Whether output channel is on or off
|
||||
public String mode; // color or white - valid only for Bulb and RGBW2 even Dimmer returns it also
|
||||
public Integer brightness; // brightness: 0.100%
|
||||
|
||||
@SerializedName("has_timer")
|
||||
public Boolean hasTimer;
|
||||
}
|
||||
@@ -914,6 +920,7 @@ public class Shelly1ApiJsonDTO {
|
||||
|
||||
public static class ShellyStatusSensor {
|
||||
// https://shelly-api-docs.shelly.cloud/#h-amp-t-settings
|
||||
|
||||
public static class ShellySensorHum {
|
||||
public Double value; // relative humidity in %
|
||||
}
|
||||
@@ -964,6 +971,7 @@ public class Shelly1ApiJsonDTO {
|
||||
|
||||
public static class ShellyExtTemperature {
|
||||
public static class ShellyShortTemp {
|
||||
public String hwID; // e.g. "2882379497020381",
|
||||
public Double tC; // temperature in deg C
|
||||
public Double tF; // temperature in deg F
|
||||
}
|
||||
|
||||
@@ -152,8 +152,7 @@ public class Shelly1CoIoTVersion1 extends Shelly1CoIoTProtocol implements Shelly
|
||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.AMPERE));
|
||||
break;
|
||||
case "pf":
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR,
|
||||
toQuantityType(getDecimal(s.value), Units.PERCENT));
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value));
|
||||
break;
|
||||
case "position":
|
||||
// work around: Roller reports 101% instead max 100
|
||||
|
||||
@@ -84,8 +84,6 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||
private boolean updatesRequested = false;
|
||||
private int coiotPort = COIOT_PORT;
|
||||
|
||||
private long coiotMessages = 0;
|
||||
private long coiotErrors = 0;
|
||||
private int lastSerial = -1;
|
||||
private String lastPayload = "";
|
||||
private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
|
||||
@@ -164,7 +162,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||
@Override
|
||||
public void processResponse(@Nullable Response response) {
|
||||
if (response == null) {
|
||||
coiotErrors++;
|
||||
thingHandler.incProtErrors();
|
||||
return; // other device instance
|
||||
}
|
||||
ResponseCode code = response.getCode();
|
||||
@@ -172,7 +170,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||
// error handling
|
||||
logger.debug("{}: Unknown Response Code {} received, payload={}", thingName, code,
|
||||
response.getPayloadString());
|
||||
coiotErrors++;
|
||||
thingHandler.incProtErrors();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -205,14 +203,14 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||
String uri = "";
|
||||
int serial = -1;
|
||||
try {
|
||||
coiotMessages++;
|
||||
thingHandler.incProtMessages();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}: CoIoT Message from {} (MID={}): {}", thingName,
|
||||
response.getSourceContext().getPeerAddress(), response.getMID(), response.getPayloadString());
|
||||
}
|
||||
if (response.isCanceled() || response.isDuplicate() || response.isRejected()) {
|
||||
logger.debug("{} ({}): Packet was canceled, rejected or is a duplicate -> discard", thingName, devId);
|
||||
coiotErrors++;
|
||||
thingHandler.incProtErrors();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -285,7 +283,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Unable to process CoIoT message: {}", thingName, e.toString());
|
||||
coiotErrors++;
|
||||
thingHandler.incProtErrors();
|
||||
}
|
||||
|
||||
if (!updatesRequested) {
|
||||
@@ -296,7 +294,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||
} catch (JsonSyntaxException | IllegalArgumentException | NullPointerException e) {
|
||||
logger.debug("{}: Unable to process CoIoT Message for payload={}", thingName, payload, e);
|
||||
resetSerial();
|
||||
coiotErrors++;
|
||||
thingHandler.incProtErrors();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,6 +498,7 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||
// Aggregate Meter Data from different Coap updates
|
||||
int i = 1;
|
||||
double totalCurrent = 0.0;
|
||||
@SuppressWarnings("unused")
|
||||
double totalKWH = 0.0;
|
||||
boolean updateMeter = false;
|
||||
while (i <= thingHandler.getProfile().numMeters) {
|
||||
@@ -663,14 +662,6 @@ public class Shelly1CoapHandler implements Shelly1CoapListener {
|
||||
coiotBound = false;
|
||||
}
|
||||
|
||||
public long getMessageCount() {
|
||||
return coiotMessages;
|
||||
}
|
||||
|
||||
public long getErrorCount() {
|
||||
return coiotErrors;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
stop();
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||
String json = "";
|
||||
try {
|
||||
json = httpRequest(SHELLY_URL_STATUS);
|
||||
|
||||
// Dimmer2 returns invalid json type for loaderror :-(
|
||||
json = json.replace("\"loaderror\":0,", "\"loaderror\":false,")
|
||||
.replace("\"loaderror\":1,", "\"loaderror\":true,")
|
||||
@@ -223,18 +224,23 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||
// SHelly H&T uses external_power, Sense uses charger
|
||||
status.charger = profile.settings.externalPower != 0;
|
||||
}
|
||||
if (status.tmp != null && status.tmp.tC == null && status.tmp.value != null) { // Motion is is missing tC and tF
|
||||
status.tmp.tC = getString(status.tmp.units).toUpperCase().equals(SHELLY_TEMP_FAHRENHEIT)
|
||||
? ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(status.tmp.value).doubleValue()
|
||||
: status.tmp.value;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimer(int index, String timerName, int value) throws ShellyApiException {
|
||||
public void setAutoTimer(int index, String timerName, double value) throws ShellyApiException {
|
||||
String type = SHELLY_CLASS_RELAY;
|
||||
if (profile.isRoller) {
|
||||
type = SHELLY_CLASS_ROLLER;
|
||||
} else if (profile.isLight) {
|
||||
type = SHELLY_CLASS_LIGHT;
|
||||
}
|
||||
String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "=" + value;
|
||||
String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "=" + (int) value;
|
||||
httpRequest(uri);
|
||||
}
|
||||
|
||||
@@ -351,11 +357,27 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||
return callApi(SHELLY_URL_SETTINGS + "?ap_roaming_enabled=" + (enable ? "true" : "false"), String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setWiFiRangeExtender(boolean enable) throws ShellyApiException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setEthernet(boolean enable) throws ShellyApiException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBluetooth(boolean enable) throws ShellyApiException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resetStaCache() throws ShellyApiException { // FW 1.10+: Reset cached STA/AP list and to a rescan
|
||||
return callApi("/sta_cache_reset", String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellySettingsUpdate firmwareUpdate(String uri) throws ShellyApiException {
|
||||
return callApi("/ota?" + uri, ShellySettingsUpdate.class);
|
||||
}
|
||||
@@ -559,7 +581,7 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||
// H&T adds the type=xx to report_url itself, so we need to ommit here
|
||||
String eclass = profile.isSensor ? EVENT_TYPE_SENSORDATA : eventType;
|
||||
String urlParm = eventType.contains("temp") || profile.isHT ? "" : "?type=" + eventType;
|
||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/"
|
||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY1_CALLBACK_URI + "/"
|
||||
+ profile.thingName + "/" + eclass + urlParm;
|
||||
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
||||
String testUrl = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\"";
|
||||
@@ -581,7 +603,7 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||
throws ShellyApiException {
|
||||
for (String eventType : eventTypes) {
|
||||
if (profile.containsEventUrl(eventType)) {
|
||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/"
|
||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY1_CALLBACK_URI + "/"
|
||||
+ profile.thingName + "/" + deviceClass + "/" + index + "?type=" + eventType;
|
||||
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
||||
String test = "\"" + mkEventUrl(eventType) + "\":\"" + callBackUrl + "\"";
|
||||
@@ -713,4 +735,8 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa
|
||||
public int getTimeoutsRecovered() {
|
||||
return timeoutsRecovered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,548 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.shelly.internal.api2;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.CHANNEL_INPUT;
|
||||
import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyHttpClient;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyFavPos;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorTmp;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsEMeter;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsMeter;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRoller;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorBat;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellySensorHum;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthRequest;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthResponse;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigCover;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigInput;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DevConfigSwitch;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2CoverStatus;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusHumidity;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusPower;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult.Shelly2DeviceStatusTempId;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2InputStatus;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RelayStatus;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyComponents;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link Shelly2ApiClient} Low level part of the RPC API
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Shelly2ApiClient extends ShellyHttpClient {
|
||||
private final Logger logger = LoggerFactory.getLogger(Shelly2ApiClient.class);
|
||||
protected final Random random = new Random();
|
||||
protected final ShellyStatusRelay relayStatus = new ShellyStatusRelay();
|
||||
protected final ShellyStatusSensor sensorData = new ShellyStatusSensor();
|
||||
protected final ArrayList<ShellyRollerStatus> rollerStatus = new ArrayList<>();
|
||||
protected @Nullable ShellyThingInterface thing;
|
||||
protected @Nullable Shelly2AuthRequest authReq;
|
||||
|
||||
public Shelly2ApiClient(String thingName, ShellyThingInterface thing) {
|
||||
super(thingName, thing);
|
||||
this.thing = thing;
|
||||
}
|
||||
|
||||
public Shelly2ApiClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
|
||||
super(thingName, config, httpClient);
|
||||
}
|
||||
|
||||
protected static final Map<String, String> MAP_INMODE_BTNTYPE = new HashMap<>();
|
||||
static {
|
||||
MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_MOMENTARY, SHELLY_BTNT_MOMENTARY);
|
||||
MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FLIP, SHELLY_BTNT_TOGGLE);
|
||||
MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_FOLLOW, SHELLY_BTNT_EDGE);
|
||||
MAP_INMODE_BTNTYPE.put(SHELLY2_BTNT_DETACHED, SHELLY_BTNT_MOMENTARY);
|
||||
}
|
||||
|
||||
protected static final Map<String, String> MAP_INPUT_EVENT_TYPE = new HashMap<>();
|
||||
static {
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_1PUSH, SHELLY_BTNEVENT_1SHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_2PUSH, SHELLY_BTNEVENT_2SHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_3PUSH, SHELLY_BTNEVENT_3SHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LPUSH, SHELLY_BTNEVENT_LONGPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_LSPUSH, SHELLY_BTNEVENT_LONGSHORTPUSH);
|
||||
MAP_INPUT_EVENT_TYPE.put(SHELLY2_EVENT_SLPUSH, SHELLY_BTNEVENT_SHORTLONGPUSH);
|
||||
}
|
||||
|
||||
protected static final Map<String, String> MAP_INPUT_EVENT_ID = new HashMap<>();
|
||||
static {
|
||||
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNUP, SHELLY_EVENT_BTN_OFF);
|
||||
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_BTNDOWN, SHELLY_EVENT_BTN_ON);
|
||||
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_1PUSH, SHELLY_EVENT_SHORTPUSH);
|
||||
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_2PUSH, SHELLY_EVENT_DOUBLE_SHORTPUSH);
|
||||
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_3PUSH, SHELLY_EVENT_TRIPLE_SHORTPUSH);
|
||||
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LPUSH, SHELLY_EVENT_LONGPUSH);
|
||||
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_LSPUSH, SHELLY_EVENT_LONG_SHORTPUSH);
|
||||
MAP_INPUT_EVENT_ID.put(SHELLY2_EVENT_SLPUSH, SHELLY_EVENT_SHORT_LONGTPUSH);
|
||||
}
|
||||
|
||||
protected static final Map<String, String> MAP_INPUT_MODE = new HashMap<>();
|
||||
static {
|
||||
MAP_INPUT_MODE.put(SHELLY2_RMODE_SINGLE, SHELLY_INP_MODE_ONEBUTTON);
|
||||
MAP_INPUT_MODE.put(SHELLY2_RMODE_DUAL, SHELLY_INP_MODE_OPENCLOSE);
|
||||
MAP_INPUT_MODE.put(SHELLY2_RMODE_DETACHED, SHELLY_INP_MODE_ONEBUTTON);
|
||||
}
|
||||
|
||||
protected static final Map<String, String> MAP_ROLLER_STATE = new HashMap<>();
|
||||
static {
|
||||
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPEN, SHELLY_RSTATE_OPEN);
|
||||
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSED, SHELLY_RSTATE_CLOSE);
|
||||
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_OPENING, SHELLY2_RSTATE_OPENING); // Gen2-only
|
||||
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CLOSING, SHELLY2_RSTATE_CLOSING); // Gen2-only
|
||||
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP);
|
||||
MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only
|
||||
}
|
||||
|
||||
protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile,
|
||||
Shelly2GetConfigResult dc) {
|
||||
if (dc.switch0 == null) {
|
||||
return null;
|
||||
}
|
||||
ArrayList<@Nullable ShellySettingsRelay> relays = new ArrayList<>();
|
||||
addRelaySettings(relays, dc.switch0);
|
||||
addRelaySettings(relays, dc.switch1);
|
||||
addRelaySettings(relays, dc.switch2);
|
||||
addRelaySettings(relays, dc.switch3);
|
||||
return relays;
|
||||
}
|
||||
|
||||
private void addRelaySettings(ArrayList<@Nullable ShellySettingsRelay> relays,
|
||||
@Nullable Shelly2DevConfigSwitch cs) {
|
||||
if (cs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ShellySettingsRelay rsettings = new ShellySettingsRelay();
|
||||
rsettings.name = cs.name;
|
||||
rsettings.ison = false;
|
||||
rsettings.autoOn = getBool(cs.autoOn) ? cs.autoOnDelay : 0;
|
||||
rsettings.autoOff = getBool(cs.autoOff) ? cs.autoOffDelay : 0;
|
||||
rsettings.hasTimer = false;
|
||||
rsettings.btnType = mapValue(MAP_INMODE_BTNTYPE, getString(cs.mode).toLowerCase());
|
||||
relays.add(rsettings);
|
||||
}
|
||||
|
||||
protected boolean fillDeviceStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult result,
|
||||
boolean channelUpdate) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
|
||||
updated |= updateInputStatus(status, result, channelUpdate);
|
||||
updated |= updateRelayStatus(status, result.switch0, channelUpdate);
|
||||
updated |= updateRelayStatus(status, result.switch1, channelUpdate);
|
||||
updated |= updateRelayStatus(status, result.switch2, channelUpdate);
|
||||
updated |= updateRelayStatus(status, result.switch3, channelUpdate);
|
||||
updated |= updateRollerStatus(status, result.cover0, channelUpdate);
|
||||
if (channelUpdate) {
|
||||
updated |= ShellyComponents.updateMeters(getThing(), status);
|
||||
}
|
||||
|
||||
updateHumidityStatus(sensorData, result.humidity0);
|
||||
updateTemperatureStatus(sensorData, result.temperature0);
|
||||
updateBatteryStatus(sensorData, result.devicepower0);
|
||||
updated |= ShellyComponents.updateSensors(getThing(), status);
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private boolean updateRelayStatus(ShellySettingsStatus status, @Nullable Shelly2RelayStatus rs,
|
||||
boolean channelUpdate) throws ShellyApiException {
|
||||
if (rs == null) {
|
||||
return false;
|
||||
}
|
||||
ShellyDeviceProfile profile = getProfile();
|
||||
if (rs.id >= profile.numRelays) {
|
||||
throw new IllegalArgumentException("Update for invalid relay index");
|
||||
}
|
||||
|
||||
ShellySettingsRelay rstatus = status.relays.get(rs.id);
|
||||
ShellyShortStatusRelay sr = relayStatus.relays.get(rs.id);
|
||||
sr.isValid = rstatus.isValid = true;
|
||||
sr.name = rstatus.name = status.name;
|
||||
if (rs.output != null) {
|
||||
sr.ison = rstatus.ison = getBool(rs.output);
|
||||
}
|
||||
if (getDouble(rs.timerStartetAt) > 0) {
|
||||
int duration = (int) (now() - rs.timerStartetAt);
|
||||
sr.timerRemaining = duration;
|
||||
}
|
||||
if (rs.temperature != null) {
|
||||
status.tmp.isValid = true;
|
||||
status.tmp.tC = rs.temperature.tC;
|
||||
status.tmp.tF = rs.temperature.tF;
|
||||
status.tmp.units = "C";
|
||||
sr.temperature = getDouble(rs.temperature.tC);
|
||||
if (status.temperature == null || getDouble(rs.temperature.tC) > status.temperature) {
|
||||
status.temperature = sr.temperature;
|
||||
}
|
||||
} else {
|
||||
status.tmp.isValid = false;
|
||||
}
|
||||
if (rs.voltage != null) {
|
||||
if (status.voltage == null || rs.voltage > status.voltage) {
|
||||
status.voltage = rs.voltage;
|
||||
}
|
||||
}
|
||||
if (rs.errors != null) {
|
||||
for (String error : rs.errors) {
|
||||
sr.overpower = rstatus.overpower = SHELLY2_ERROR_OVERPOWER.equals(error);
|
||||
status.overload = SHELLY2_ERROR_OVERVOLTAGE.equals(error);
|
||||
status.overtemperature = SHELLY2_ERROR_OVERTEMP.equals(error);
|
||||
}
|
||||
sr.overtemperature = status.overtemperature;
|
||||
}
|
||||
|
||||
ShellySettingsMeter sm = new ShellySettingsMeter();
|
||||
ShellySettingsEMeter emeter = status.emeters.get(rs.id);
|
||||
sm.isValid = emeter.isValid = true;
|
||||
if (rs.apower != null) {
|
||||
sm.power = emeter.power = rs.apower;
|
||||
}
|
||||
if (rs.aenergy != null) {
|
||||
sm.total = emeter.total = rs.aenergy.total;
|
||||
sm.counters = rs.aenergy.byMinute;
|
||||
sm.timestamp = rs.aenergy.minuteTs;
|
||||
}
|
||||
if (rs.voltage != null) {
|
||||
emeter.voltage = rs.voltage;
|
||||
}
|
||||
if (rs.current != null) {
|
||||
emeter.current = rs.current;
|
||||
}
|
||||
if (rs.pf != null) {
|
||||
emeter.pf = rs.pf;
|
||||
}
|
||||
|
||||
// Update internal structures
|
||||
status.relays.set(rs.id, rstatus);
|
||||
status.meters.set(rs.id, sm);
|
||||
status.emeters.set(rs.id, emeter);
|
||||
relayStatus.relays.set(rs.id, sr);
|
||||
relayStatus.meters.set(rs.id, sm);
|
||||
|
||||
return channelUpdate ? ShellyComponents.updateRelay((ShellyBaseHandler) getThing(), status, rs.id) : false;
|
||||
}
|
||||
|
||||
protected @Nullable ArrayList<@Nullable ShellySettingsRoller> fillRollerSettings(ShellyDeviceProfile profile,
|
||||
Shelly2GetConfigResult dc) {
|
||||
if (dc.cover0 == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ArrayList<@Nullable ShellySettingsRoller> rollers = new ArrayList<>();
|
||||
|
||||
addRollerSettings(rollers, dc.cover0);
|
||||
fillRollerFavorites(profile, dc);
|
||||
return rollers;
|
||||
}
|
||||
|
||||
private void addRollerSettings(ArrayList<@Nullable ShellySettingsRoller> rollers,
|
||||
@Nullable Shelly2DevConfigCover coverConfig) {
|
||||
if (coverConfig == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ShellySettingsRoller settings = new ShellySettingsRoller();
|
||||
settings.isValid = true;
|
||||
settings.defaultState = coverConfig.initialState;
|
||||
settings.inputMode = mapValue(MAP_INPUT_MODE, coverConfig.inMode);
|
||||
settings.btnReverse = getBool(coverConfig.invertDirections) ? 1 : 0;
|
||||
settings.swapInputs = coverConfig.swapInputs;
|
||||
settings.maxtime = 0.0; // n/a
|
||||
settings.maxtimeOpen = coverConfig.maxtimeOpen;
|
||||
settings.maxtimeClose = coverConfig.maxtimeClose;
|
||||
if (coverConfig.safetySwitch != null) {
|
||||
settings.safetySwitch = coverConfig.safetySwitch.enable;
|
||||
settings.safetyAction = coverConfig.safetySwitch.action;
|
||||
}
|
||||
if (coverConfig.obstructionDetection != null) {
|
||||
settings.obstacleAction = coverConfig.obstructionDetection.action;
|
||||
settings.obstacleDelay = coverConfig.obstructionDetection.holdoff.intValue();
|
||||
settings.obstaclePower = coverConfig.obstructionDetection.powerThr;
|
||||
}
|
||||
rollers.add(settings);
|
||||
}
|
||||
|
||||
private void fillRollerFavorites(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) {
|
||||
if (dc.sys.uiData.cover != null) {
|
||||
String[] favorites = dc.sys.uiData.cover.split(",");
|
||||
profile.settings.favorites = new ArrayList<>();
|
||||
for (int i = 0; i < favorites.length; i++) {
|
||||
ShellyFavPos fav = new ShellyFavPos();
|
||||
fav.pos = Integer.parseInt(favorites[i]);
|
||||
fav.name = fav.pos + "%";
|
||||
profile.settings.favorites.add(fav);
|
||||
}
|
||||
profile.settings.favoritesEnabled = profile.settings.favorites.size() > 0;
|
||||
logger.debug("{}: Roller Favorites loaded: {}", thingName,
|
||||
profile.settings.favoritesEnabled ? profile.settings.favorites.size() : "none");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateRollerStatus(ShellySettingsStatus status, @Nullable Shelly2CoverStatus cs,
|
||||
boolean updateChannels) throws ShellyApiException {
|
||||
if (cs == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ShellyRollerStatus rs = status.rollers.get(cs.id);
|
||||
ShellySettingsMeter sm = status.meters.get(cs.id);
|
||||
ShellySettingsEMeter emeter = status.emeters.get(cs.id);
|
||||
rs.isValid = sm.isValid = emeter.isValid = true;
|
||||
if (cs.state != null) {
|
||||
if (!getString(rs.state).equals(cs.state)) {
|
||||
logger.debug("{}: Roller status changed from {} to {}, updateChannels={}", thingName, rs.state,
|
||||
mapValue(MAP_ROLLER_STATE, cs.state), updateChannels);
|
||||
}
|
||||
rs.state = mapValue(MAP_ROLLER_STATE, cs.state);
|
||||
rs.calibrating = SHELLY2_RSTATE_CALIB.equals(cs.state);
|
||||
}
|
||||
if (cs.currentPos != null) {
|
||||
rs.currentPos = cs.currentPos;
|
||||
}
|
||||
if (cs.moveStartedAt != null) {
|
||||
rs.duration = (int) (now() - cs.moveStartedAt.longValue());
|
||||
}
|
||||
if (cs.temperature != null && cs.temperature.tC > getDouble(status.temperature)) {
|
||||
status.temperature = status.tmp.tC = getDouble(cs.temperature.tC);
|
||||
}
|
||||
if (cs.apower != null) {
|
||||
rs.power = sm.power = emeter.power = cs.apower;
|
||||
}
|
||||
if (cs.aenergy != null) {
|
||||
sm.total = emeter.total = cs.aenergy.total;
|
||||
sm.counters = cs.aenergy.byMinute;
|
||||
sm.timestamp = (long) cs.aenergy.minuteTs;
|
||||
}
|
||||
if (cs.voltage != null) {
|
||||
emeter.voltage = cs.voltage;
|
||||
}
|
||||
if (cs.current != null) {
|
||||
emeter.current = cs.current;
|
||||
}
|
||||
if (cs.pf != null) {
|
||||
emeter.pf = cs.pf;
|
||||
}
|
||||
|
||||
rollerStatus.set(cs.id, rs);
|
||||
status.rollers.set(cs.id, rs);
|
||||
relayStatus.meters.set(cs.id, sm);
|
||||
status.meters.set(cs.id, sm);
|
||||
status.emeters.set(cs.id, emeter);
|
||||
|
||||
postAlarms(cs.errors);
|
||||
if (rs.calibrating != null && rs.calibrating) {
|
||||
getThing().postEvent(SHELLY_EVENT_ROLLER_CALIB, false);
|
||||
}
|
||||
|
||||
return updateChannels ? ShellyComponents.updateRoller((ShellyBaseHandler) getThing(), rs, cs.id) : false;
|
||||
}
|
||||
|
||||
protected void updateHumidityStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusHumidity value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (sdata.hum == null) {
|
||||
sdata.hum = new ShellySensorHum();
|
||||
}
|
||||
sdata.hum.value = getDouble(value.rh);
|
||||
}
|
||||
|
||||
protected void updateTemperatureStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusTempId value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (sdata.tmp == null) {
|
||||
sdata.tmp = new ShellySensorTmp();
|
||||
}
|
||||
sdata.tmp.isValid = true;
|
||||
sdata.tmp.units = SHELLY_TEMP_CELSIUS;
|
||||
sdata.tmp.tC = value.tC;
|
||||
sdata.tmp.tF = value.tF;
|
||||
}
|
||||
|
||||
protected void updateBatteryStatus(ShellyStatusSensor sdata, @Nullable Shelly2DeviceStatusPower value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (sdata.bat == null) {
|
||||
sdata.bat = new ShellySensorBat();
|
||||
}
|
||||
|
||||
if (value.battery != null) {
|
||||
sdata.bat.voltage = value.battery.volt;
|
||||
sdata.bat.value = value.battery.percent;
|
||||
}
|
||||
if (value.external != null) {
|
||||
sdata.charger = value.external.present;
|
||||
}
|
||||
}
|
||||
|
||||
private void postAlarms(@Nullable ArrayList<@Nullable String> errors) throws ShellyApiException {
|
||||
if (errors != null) {
|
||||
for (String e : errors) {
|
||||
if (e != null) {
|
||||
getThing().postEvent(e, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected @Nullable ArrayList<ShellySettingsInput> fillInputSettings(ShellyDeviceProfile profile,
|
||||
Shelly2GetConfigResult dc) {
|
||||
if (dc.input0 == null) {
|
||||
return null; // device has no input
|
||||
}
|
||||
|
||||
ArrayList<ShellySettingsInput> inputs = new ArrayList<>();
|
||||
addInputSettings(inputs, dc.input0);
|
||||
addInputSettings(inputs, dc.input1);
|
||||
addInputSettings(inputs, dc.input2);
|
||||
addInputSettings(inputs, dc.input3);
|
||||
return inputs;
|
||||
}
|
||||
|
||||
private void addInputSettings(ArrayList<ShellySettingsInput> inputs, @Nullable Shelly2DevConfigInput ic) {
|
||||
if (ic == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ShellySettingsInput settings = new ShellySettingsInput();
|
||||
settings.btnType = getString(ic.type).equalsIgnoreCase(SHELLY2_INPUTT_BUTTON) ? SHELLY_BTNT_MOMENTARY
|
||||
: SHELLY_BTNT_EDGE;
|
||||
inputs.add(settings);
|
||||
}
|
||||
|
||||
protected boolean updateInputStatus(ShellySettingsStatus status, Shelly2DeviceStatusResult ds,
|
||||
boolean updateChannels) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
updated |= addInputStatus(ds.input0, updateChannels);
|
||||
updated |= addInputStatus(ds.input1, updateChannels);
|
||||
updated |= addInputStatus(ds.input2, updateChannels);
|
||||
updated |= addInputStatus(ds.input3, updateChannels);
|
||||
status.inputs = relayStatus.inputs;
|
||||
return updated;
|
||||
}
|
||||
|
||||
private boolean addInputStatus(@Nullable Shelly2InputStatus is, boolean updateChannels) throws ShellyApiException {
|
||||
if (is == null) {
|
||||
return false;
|
||||
}
|
||||
ShellyDeviceProfile profile = getProfile();
|
||||
if (is.id == null || is.id > profile.numInputs) {
|
||||
logger.debug("{}: Invalid input id: {}", thingName, is.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
String group = profile.getInputGroup(is.id);
|
||||
ShellyInputState input = relayStatus.inputs.size() > is.id ? relayStatus.inputs.get(is.id)
|
||||
: new ShellyInputState();
|
||||
boolean updated = false;
|
||||
input.input = getBool(is.state) ? 1 : 0; // old format Integer, new one Boolean
|
||||
if (input.event == null && profile.inButtonMode(is.id)) {
|
||||
input.event = "";
|
||||
input.eventCount = 0;
|
||||
}
|
||||
relayStatus.inputs.set(is.id, input);
|
||||
if (updateChannels) {
|
||||
updated |= updateChannel(group, CHANNEL_INPUT + profile.getInputSuffix(is.id), getOnOff(getBool(is.state)));
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
protected Shelly2RpcBaseMessage buildRequest(String method, @Nullable Object params) throws ShellyApiException {
|
||||
Shelly2RpcBaseMessage request = new Shelly2RpcBaseMessage();
|
||||
request.id = Math.abs(random.nextInt());
|
||||
request.src = thingName;
|
||||
request.method = !method.contains(".") ? SHELLYRPC_METHOD_CLASS_SHELLY + "." + method : method;
|
||||
request.params = params;
|
||||
request.auth = authReq;
|
||||
return request;
|
||||
}
|
||||
|
||||
protected Shelly2AuthRequest buildAuthRequest(Shelly2AuthResponse authParm, String user, String realm,
|
||||
String password) throws ShellyApiException {
|
||||
Shelly2AuthRequest authReq = new Shelly2AuthRequest();
|
||||
authReq.username = "admin";
|
||||
authReq.realm = realm;
|
||||
authReq.nonce = authParm.nonce;
|
||||
authReq.cnonce = (long) Math.floor(Math.random() * 10e8);
|
||||
authReq.nc = authParm.nc != null ? authParm.nc : 1;
|
||||
authReq.authType = SHELLY2_AUTHTTYPE_DIGEST;
|
||||
authReq.algorithm = SHELLY2_AUTHALG_SHA256;
|
||||
String ha1 = sha256(authReq.username + ":" + authReq.realm + ":" + password);
|
||||
String ha2 = SHELLY2_AUTH_NOISE;
|
||||
authReq.response = sha256(
|
||||
ha1 + ":" + authReq.nonce + ":" + authReq.nc + ":" + authReq.cnonce + ":" + "auth" + ":" + ha2);
|
||||
return authReq;
|
||||
}
|
||||
|
||||
protected String mapValue(Map<String, String> map, @Nullable String key) {
|
||||
String value;
|
||||
boolean known = key != null && !key.isEmpty() && map.containsKey(key);
|
||||
value = known ? getString(map.get(key)) : "";
|
||||
logger.trace("{}: API value {} was mapped to {}", thingName, key, known ? value : "UNKNOWN");
|
||||
return value;
|
||||
}
|
||||
|
||||
protected boolean updateChannel(String group, String channel, State value) throws ShellyApiException {
|
||||
return getThing().updateChannel(group, channel, value);
|
||||
}
|
||||
|
||||
protected ShellyThingInterface getThing() throws ShellyApiException {
|
||||
ShellyThingInterface t = thing;
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
throw new ShellyApiException("Thing/profile not initialized!");
|
||||
}
|
||||
|
||||
ShellyDeviceProfile getProfile() throws ShellyApiException {
|
||||
if (thing != null) {
|
||||
return thing.getProfile();
|
||||
}
|
||||
throw new ShellyApiException("Unable to get profile, thing not initialized!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.shelly.internal.api2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage.Shelly2RpcMessageError;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* {@link Shelly2ApiJsonDTO} wraps the Shelly REST API and provides various low level function to access the device api
|
||||
* (not
|
||||
* cloud api).
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
public class Shelly2ApiJsonDTO {
|
||||
public static final String SHELLYRPC_METHOD_CLASS_SHELLY = "Shelly";
|
||||
public static final String SHELLYRPC_METHOD_CLASS_SWITCH = "Switch";
|
||||
|
||||
public static final String SHELLYRPC_METHOD_GETDEVCONFIG = "GetDeviceInfo";
|
||||
public static final String SHELLYRPC_METHOD_GETSYSCONFIG = "GetSysConfig"; // only sys
|
||||
public static final String SHELLYRPC_METHOD_GETCONFIG = "GetConfig"; // sys + components
|
||||
public static final String SHELLYRPC_METHOD_GETSYSSTATUS = "GetSysStatus"; // only sys
|
||||
public static final String SHELLYRPC_METHOD_GETSTATUS = "GetStatus"; // sys + components
|
||||
public static final String SHELLYRPC_METHOD_REBOOT = "Shelly.Reboot";
|
||||
public static final String SHELLYRPC_METHOD_RESET = "Shelly.FactoryReset";
|
||||
public static final String SHELLYRPC_METHOD_CHECKUPD = "Shelly.CheckForUpdate";
|
||||
public static final String SHELLYRPC_METHOD_UPDATE = "Shelly.Update";
|
||||
public static final String SHELLYRPC_METHOD_AUTHSET = "Shelly.SetAuth";
|
||||
public static final String SHELLYRPC_METHOD_SWITCH_STATUS = "Switch.GetStatus";
|
||||
public static final String SHELLYRPC_METHOD_SWITCH_SET = "Switch.Set";
|
||||
public static final String SHELLYRPC_METHOD_SWITCH_SETCONFIG = "Switch.SetConfig";
|
||||
public static final String SHELLYRPC_METHOD_COVER_SETPOS = "Cover.GoToPosition";
|
||||
public static final String SHELLY2_COVER_CMD_OPEN = "Open";
|
||||
public static final String SHELLY2_COVER_CMD_CLOSE = "Close";
|
||||
public static final String SHELLY2_COVER_CMD_STOP = "Stop";
|
||||
public static final String SHELLYRPC_METHOD_WIFIGETCONG = "Wifi.GetConfig";
|
||||
public static final String SHELLYRPC_METHOD_WIFISETCONG = "Wifi.SetConfig";
|
||||
public static final String SHELLYRPC_METHOD_ETHGETCONG = "Eth.GetConfig";
|
||||
public static final String SHELLYRPC_METHOD_ETHSETCONG = "Eth.SetConfig";
|
||||
public static final String SHELLYRPC_METHOD_BLEGETCONG = "BLE.GetConfig";
|
||||
public static final String SHELLYRPC_METHOD_BLESETCONG = "BLE.SetConfig";
|
||||
public static final String SHELLYRPC_METHOD_CLOUDSET = "Cloud.SetConfig";
|
||||
public static final String SHELLYRPC_METHOD_WSGETCONFIG = "WS.GetConfig";
|
||||
public static final String SHELLYRPC_METHOD_WSSETCONFIG = "WS.SetConfig";
|
||||
|
||||
public static final String SHELLYRPC_METHOD_NOTIFYSTATUS = "NotifyStatus"; // inbound status
|
||||
public static final String SHELLYRPC_METHOD_NOTIFYFULLSTATUS = "NotifyFullStatus"; // inbound status from bat device
|
||||
public static final String SHELLYRPC_METHOD_NOTIFYEVENT = "NotifyEvent"; // inbound event
|
||||
|
||||
// Component types
|
||||
public static final String SHELLY2_PROFILE_RELAY = "switch";
|
||||
public static final String SHELLY2_PROFILE_ROLLER = "cover";
|
||||
|
||||
// Button types/modes
|
||||
public static final String SHELLY2_BTNT_MOMENTARY = "momentary";
|
||||
public static final String SHELLY2_BTNT_FLIP = "flip";
|
||||
public static final String SHELLY2_BTNT_FOLLOW = "follow";
|
||||
public static final String SHELLY2_BTNT_DETACHED = "detached";
|
||||
|
||||
// Input types
|
||||
public static final String SHELLY2_INPUTT_SWITCH = "switch";
|
||||
public static final String SHELLY2_INPUTT_BUTTON = "button";
|
||||
|
||||
// Switcm modes
|
||||
public static final String SHELLY2_API_MODE_DETACHED = "detached";
|
||||
public static final String SHELLY2_API_MODE_FOLLOW = "follow";
|
||||
|
||||
// Initial switch states
|
||||
public static final String SHELLY2_API_ISTATE_ON = "on";
|
||||
public static final String SHELLY2_API_ISTATE_OFF = "off";
|
||||
public static final String SHELLY2_API_ISTATE_FOLLOWLAST = "restore_last";
|
||||
public static final String SHELLY2_API_ISTATE_MATCHINPUT = "match_input";
|
||||
|
||||
// Cover/Roller modes
|
||||
public static final String SHELLY2_RMODE_SINGLE = "single";
|
||||
public static final String SHELLY2_RMODE_DUAL = "dual";
|
||||
public static final String SHELLY2_RMODE_DETACHED = "detached";
|
||||
|
||||
public static final String SHELLY2_RSTATE_OPENING = "opening";
|
||||
public static final String SHELLY2_RSTATE_OPEN = "open";
|
||||
public static final String SHELLY2_RSTATE_CLOSING = "closing";
|
||||
public static final String SHELLY2_RSTATE_CLOSED = "closed";
|
||||
public static final String SHELLY2_RSTATE_STOPPED = "stopped";
|
||||
public static final String SHELLY2_RSTATE_CALIB = "calibrating";
|
||||
|
||||
// Event notifications
|
||||
public static final String SHELLY2_EVENT_BTNUP = "btn_up";
|
||||
public static final String SHELLY2_EVENT_BTNDOWN = "btn_down";
|
||||
public static final String SHELLY2_EVENT_1PUSH = "single_push";
|
||||
public static final String SHELLY2_EVENT_2PUSH = "double_push";
|
||||
public static final String SHELLY2_EVENT_3PUSH = "triple_push";
|
||||
public static final String SHELLY2_EVENT_LPUSH = "long_push";
|
||||
public static final String SHELLY2_EVENT_SLPUSH = "short_long_push";
|
||||
public static final String SHELLY2_EVENT_LSPUSH = "long_short_push";
|
||||
|
||||
public static final String SHELLY2_EVENT_SLEEP = "sleep";
|
||||
public static final String SHELLY2_EVENT_CFGCHANGED = "config_changed";
|
||||
public static final String SHELLY2_EVENT_OTASTART = "ota_begin";
|
||||
public static final String SHELLY2_EVENT_OTAPROGRESS = "ota_progress";
|
||||
public static final String SHELLY2_EVENT_OTADONE = "ota_success";
|
||||
public static final String SHELLY2_EVENT_WIFICONNFAILED = "sta_connect_fail";
|
||||
public static final String SHELLY2_EVENT_WIFIDISCONNECTED = "sta_disconnected";
|
||||
|
||||
// Error Codes
|
||||
public static final String SHELLY2_ERROR_OVERPOWER = "overpower";
|
||||
public static final String SHELLY2_ERROR_OVERTEMP = "overtemp";
|
||||
public static final String SHELLY2_ERROR_OVERVOLTAGE = "overvoltage";
|
||||
|
||||
// Wakeup reasons (e.g. Plus HT)
|
||||
public static final String SHELLY2_WAKEUPO_BOOT_POWERON = "poweron";
|
||||
public static final String SHELLY2_WAKEUPO_BOOT_RESTART = "software_restart";
|
||||
public static final String SHELLY2_WAKEUPO_BOOT_WAKEUP = "deepsleep_wake";
|
||||
public static final String SHELLY2_WAKEUPO_BOOT_INTERNAL = "internal";
|
||||
public static final String SHELLY2_WAKEUPO_BOOT_UNKNOWN = "unknown";
|
||||
|
||||
public static final String SHELLY2_WAKEUPOCAUSE_BUTTON = "button";
|
||||
public static final String SHELLY2_WAKEUPOCAUSE_USB = "usb";
|
||||
public static final String SHELLY2_WAKEUPOCAUSE_PERIODIC = "periodic";
|
||||
public static final String SHELLY2_WAKEUPOCAUSE_UPDATE = "status_update";
|
||||
public static final String SHELLY2_WAKEUPOCAUSE_UNDEFINED = "undefined";
|
||||
|
||||
public class Shelly2DevConfigBle {
|
||||
public Boolean enable;
|
||||
}
|
||||
|
||||
public class Shelly2DevConfigEth {
|
||||
public Boolean enable;
|
||||
public String ipv4mode;
|
||||
public String ip;
|
||||
public String netmask;
|
||||
public String gw;
|
||||
public String nameserver;
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceSettings {
|
||||
public String name;
|
||||
public String id;
|
||||
public String mac;
|
||||
public String model;
|
||||
public Integer gen;
|
||||
@SerializedName("fw_id")
|
||||
public String firmware;
|
||||
public String ver;
|
||||
public String app;
|
||||
@SerializedName("auth_en")
|
||||
public Boolean authEnable;
|
||||
@SerializedName("auth_domain")
|
||||
public String authDomain;
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceConfigAp {
|
||||
public static class Shelly2DeviceConfigApRE {
|
||||
public Boolean enable;
|
||||
}
|
||||
|
||||
public Boolean enable;
|
||||
public String ssid;
|
||||
public String password;
|
||||
@SerializedName("is_open")
|
||||
public Boolean isOpen;
|
||||
@SerializedName("range_extender")
|
||||
Shelly2DeviceConfigApRE rangeExtender;
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceConfig {
|
||||
public class Shelly2DeviceConfigSys {
|
||||
public class Shelly2DeviceConfigDevice {
|
||||
public String name;
|
||||
public String mac;
|
||||
@SerializedName("fw_id")
|
||||
public String fwId;
|
||||
public String profile;
|
||||
@SerializedName("eco_mode")
|
||||
public Boolean ecoMode;
|
||||
public Boolean discoverable;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigLocation {
|
||||
public String tz;
|
||||
public Double lat;
|
||||
public Double lon;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigSntp {
|
||||
public String server;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigSleep {
|
||||
@SerializedName("wakeup_period")
|
||||
public Integer wakeupPeriod;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigDebug {
|
||||
public class Shelly2DeviceConfigDebugMqtt {
|
||||
public Boolean enable;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigDebugWebSocket {
|
||||
public Boolean enable;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigDebugUdp {
|
||||
public String addr;
|
||||
}
|
||||
|
||||
public Shelly2DeviceConfigDebugMqtt mqtt;
|
||||
public Shelly2DeviceConfigDebugWebSocket websocket;
|
||||
public Shelly2DeviceConfigDebugUdp udp;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigUiData {
|
||||
public String cover; // hold comma seperated list of roller favorites
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigRpcUdp {
|
||||
@SerializedName("dst_addr")
|
||||
public String dstAddr;
|
||||
@SerializedName("listenPort")
|
||||
public String listenPort;
|
||||
}
|
||||
|
||||
@SerializedName("cfg_rev")
|
||||
public Integer cfgRevision;
|
||||
public Shelly2DeviceConfigDevice device;
|
||||
public Shelly2DeviceConfigLocation location;
|
||||
public Shelly2DeviceConfigSntp sntp;
|
||||
public Shelly2DeviceConfigSleep sleep;
|
||||
public Shelly2DeviceConfigDebug debug;
|
||||
@SerializedName("ui_data")
|
||||
public Shelly2DeviceConfigUiData uiData;
|
||||
@SerializedName("rpc_udp")
|
||||
public Shelly2DeviceConfigRpcUdp rpcUdp;
|
||||
}
|
||||
|
||||
public class Shelly2DevConfigInput {
|
||||
public String id;
|
||||
public String name;
|
||||
public String type;
|
||||
public Boolean invert;
|
||||
@SerializedName("factory_reset")
|
||||
public Boolean factoryReset;
|
||||
}
|
||||
|
||||
public class Shelly2DevConfigSwitch {
|
||||
public String id;
|
||||
public String name;
|
||||
|
||||
@SerializedName("in_mode")
|
||||
public String mode;
|
||||
|
||||
@SerializedName("initial_state")
|
||||
public String initialState;
|
||||
@SerializedName("auto_on")
|
||||
public Boolean autoOn;
|
||||
@SerializedName("auto_on_delay")
|
||||
public Double autoOnDelay;
|
||||
@SerializedName("auto_off")
|
||||
public Boolean autoOff;
|
||||
@SerializedName("auto_off_delay")
|
||||
public Double autoOffDelay;
|
||||
@SerializedName("power_limit")
|
||||
public Integer powerLimit;
|
||||
@SerializedName("voltage_limit")
|
||||
public Integer voltageLimit;
|
||||
@SerializedName("current_limit")
|
||||
public Double currentLimit;
|
||||
}
|
||||
|
||||
public class Shelly2DevConfigCover {
|
||||
public class Shelly2DeviceConfigCoverMotor {
|
||||
@SerializedName("idle_power_thr")
|
||||
public Double idle_powerThr;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigCoverSafetySwitch {
|
||||
public Boolean enable;
|
||||
public String direction;
|
||||
public String action;
|
||||
@SerializedName("allowed_move")
|
||||
public String allowedMove;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigCoverObstructionDetection {
|
||||
public Boolean enable;
|
||||
public String direction;
|
||||
public String action;
|
||||
@SerializedName("power_thr")
|
||||
public Integer powerThr;
|
||||
public Double holdoff;
|
||||
}
|
||||
|
||||
public String id;
|
||||
public String name;
|
||||
public Shelly2DeviceConfigCoverMotor motor;
|
||||
@SerializedName("maxtime_open")
|
||||
public Double maxtimeOpen;
|
||||
@SerializedName("maxtime_close")
|
||||
public Double maxtimeClose;
|
||||
@SerializedName("initial_state")
|
||||
public String initialState;
|
||||
@SerializedName("invert_directions")
|
||||
public Boolean invertDirections;
|
||||
@SerializedName("in_mode")
|
||||
public String inMode;
|
||||
@SerializedName("swap_inputs")
|
||||
public Boolean swapInputs;
|
||||
@SerializedName("safety_switch")
|
||||
public Shelly2DeviceConfigCoverSafetySwitch safetySwitch;
|
||||
@SerializedName("power_limit")
|
||||
public Integer powerLimit;
|
||||
@SerializedName("voltage_limit")
|
||||
public Integer voltageLimit;
|
||||
@SerializedName("current_limit")
|
||||
public Double currentLimit;
|
||||
@SerializedName("obstruction_detection")
|
||||
public Shelly2DeviceConfigCoverObstructionDetection obstructionDetection;
|
||||
}
|
||||
|
||||
public static class Shelly2GetConfigResult {
|
||||
|
||||
public class Shelly2DevConfigCloud {
|
||||
public Boolean enable;
|
||||
public String server;
|
||||
}
|
||||
|
||||
public class Shelly2DevConfigMqtt {
|
||||
public Boolean enable;
|
||||
public String server;
|
||||
public String user;
|
||||
@SerializedName("topic_prefix:0")
|
||||
public String topicPrefix;
|
||||
@SerializedName("rpc_ntf")
|
||||
public String rpcNtf;
|
||||
@SerializedName("status_ntf")
|
||||
public String statusNtf;
|
||||
}
|
||||
|
||||
public Shelly2DevConfigBle ble;
|
||||
public Shelly2DevConfigEth eth;
|
||||
public Shelly2DevConfigCloud cloud;
|
||||
public Shelly2DevConfigMqtt mqtt;
|
||||
public Shelly2DeviceConfigSys sys;
|
||||
public Shelly2DeviceConfigWiFi wifi;
|
||||
|
||||
@SerializedName("input:0")
|
||||
public Shelly2DevConfigInput input0;
|
||||
@SerializedName("input:1")
|
||||
public Shelly2DevConfigInput input1;
|
||||
@SerializedName("input:2")
|
||||
public Shelly2DevConfigInput input2;
|
||||
@SerializedName("input:3")
|
||||
public Shelly2DevConfigInput input3;
|
||||
|
||||
@SerializedName("switch:0")
|
||||
public Shelly2DevConfigSwitch switch0;
|
||||
@SerializedName("switch:1")
|
||||
public Shelly2DevConfigSwitch switch1;
|
||||
@SerializedName("switch:2")
|
||||
public Shelly2DevConfigSwitch switch2;
|
||||
@SerializedName("switch:3")
|
||||
public Shelly2DevConfigSwitch switch3;
|
||||
|
||||
@SerializedName("cover:0")
|
||||
public Shelly2DevConfigCover cover0;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigSta {
|
||||
public String ssid;
|
||||
public String password;
|
||||
@SerializedName("is_open")
|
||||
public Boolean isOpen;
|
||||
public Boolean enable;
|
||||
public String ipv4mode;
|
||||
public String ip;
|
||||
public String netmask;
|
||||
public String gw;
|
||||
public String nameserver;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigRoam {
|
||||
@SerializedName("rssi_thr")
|
||||
public Integer rssiThr;
|
||||
public Integer interval;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceConfigWiFi {
|
||||
public Shelly2DeviceConfigAp ap;
|
||||
public Shelly2DeviceConfigSta sta;
|
||||
public Shelly2DeviceConfigSta sta1;
|
||||
public Shelly2DeviceConfigRoam roam;
|
||||
}
|
||||
|
||||
public String id;
|
||||
public String src;
|
||||
public Shelly2GetConfigResult result;
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceStatus {
|
||||
public class Shelly2InputStatus {
|
||||
public Integer id;
|
||||
public Boolean state;
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceStatusResult {
|
||||
public class Shelly2DeviceStatusBle {
|
||||
|
||||
}
|
||||
|
||||
public class Shelly2DeviceStatusCloud {
|
||||
public Boolean connected;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceStatusMqqt {
|
||||
public Boolean connected;
|
||||
}
|
||||
|
||||
public class Shelly2CoverStatus {
|
||||
public Integer id;
|
||||
public String source;
|
||||
public String state;
|
||||
public Double apower;
|
||||
public Double voltage;
|
||||
public Double current;
|
||||
public Double pf;
|
||||
public Shelly2Energy aenergy;
|
||||
@SerializedName("current_pos")
|
||||
public Integer currentPos;
|
||||
@SerializedName("target_pos")
|
||||
public Integer targetPos;
|
||||
@SerializedName("move_timeout")
|
||||
public Double moveTimeout;
|
||||
@SerializedName("move_started_at")
|
||||
public Double moveStartedAt;
|
||||
@SerializedName("pos_control")
|
||||
public Boolean posControl;
|
||||
public Shelly2DeviceStatusTemp temperature;
|
||||
public ArrayList<String> errors;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceStatusHumidity {
|
||||
public Integer id;
|
||||
public Double rh;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceStatusTempId extends Shelly2DeviceStatusTemp {
|
||||
public Integer id;
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceStatusPower {
|
||||
public static class Shelly2DeviceStatusBattery {
|
||||
@SerializedName("V")
|
||||
public Double volt;
|
||||
public Double percent;
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceStatusCharger {
|
||||
public Boolean present;
|
||||
}
|
||||
|
||||
public Integer id;
|
||||
public Shelly2DeviceStatusBattery battery;
|
||||
public Shelly2DeviceStatusCharger external;
|
||||
}
|
||||
|
||||
public Shelly2DeviceStatusBle ble;
|
||||
public Shelly2DeviceStatusCloud cloud;
|
||||
public Shelly2DeviceStatusMqqt mqtt;
|
||||
public Shelly2DeviceStatusSys sys;
|
||||
public Shelly2DeviceStatusSysWiFi wifi;
|
||||
|
||||
@SerializedName("input:0")
|
||||
public Shelly2InputStatus input0;
|
||||
@SerializedName("input:1")
|
||||
public Shelly2InputStatus input1;
|
||||
@SerializedName("input:2")
|
||||
public Shelly2InputStatus input2;
|
||||
@SerializedName("input:3")
|
||||
public Shelly2InputStatus input3;
|
||||
|
||||
@SerializedName("switch:0")
|
||||
public Shelly2RelayStatus switch0;
|
||||
@SerializedName("switch:1")
|
||||
public Shelly2RelayStatus switch1;
|
||||
@SerializedName("switch:2")
|
||||
public Shelly2RelayStatus switch2;
|
||||
@SerializedName("switch:3")
|
||||
public Shelly2RelayStatus switch3;
|
||||
|
||||
@SerializedName("cover:0")
|
||||
public Shelly2CoverStatus cover0;
|
||||
|
||||
@SerializedName("humidity:0")
|
||||
public Shelly2DeviceStatusHumidity humidity0;
|
||||
@SerializedName("temperature:0")
|
||||
public Shelly2DeviceStatusTempId temperature0;
|
||||
@SerializedName("devicepower:0")
|
||||
public Shelly2DeviceStatusPower devicepower0;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceStatusSys {
|
||||
public class Shelly2DeviceStatusSysAvlUpdate {
|
||||
public class Shelly2DeviceStatusSysUpdate {
|
||||
public String version;
|
||||
}
|
||||
|
||||
public Shelly2DeviceStatusSysUpdate stable;
|
||||
public Shelly2DeviceStatusSysUpdate beta;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceStatusWakeup {
|
||||
public String boot;
|
||||
public String cause;
|
||||
}
|
||||
|
||||
public String mac;
|
||||
@SerializedName("restart_required")
|
||||
public Boolean restartRequired;
|
||||
public String time;
|
||||
public Long unixtime;
|
||||
public Long uptime;
|
||||
@SerializedName("ram_size")
|
||||
public Long ramSize;
|
||||
@SerializedName("ram_free")
|
||||
public Long ramFree;
|
||||
@SerializedName("fs_size")
|
||||
public Long fsSize;
|
||||
@SerializedName("fs_free")
|
||||
public Long fsFree;
|
||||
@SerializedName("cfg_rev")
|
||||
public Integer cfg_rev;
|
||||
@SerializedName("available_updates")
|
||||
public Shelly2DeviceStatusSysAvlUpdate availableUpdates;
|
||||
@SerializedName("webhook_rev")
|
||||
public Integer webHookRev;
|
||||
@SerializedName("wakeup_reason")
|
||||
public Shelly2DeviceStatusWakeup wakeUpReason;
|
||||
@SerializedName("wakeup_period")
|
||||
public Integer wakeupPeriod;
|
||||
}
|
||||
|
||||
public class Shelly2DeviceStatusSysWiFi {
|
||||
@SerializedName("sta_ip")
|
||||
public String staIP;
|
||||
public String status;
|
||||
public String ssid;
|
||||
public Integer rssi;
|
||||
@SerializedName("ap_client_count")
|
||||
public Integer apClientCount;
|
||||
}
|
||||
|
||||
public String id;
|
||||
public String src;
|
||||
public Shelly2DeviceStatusResult result;
|
||||
}
|
||||
|
||||
public static class Shelly2RelayStatus {
|
||||
public Integer id;
|
||||
public String source;
|
||||
public Boolean output;
|
||||
@SerializedName("timer_started_at")
|
||||
public Double timerStartetAt;
|
||||
@SerializedName("timer_duration")
|
||||
public Integer timerDuration;
|
||||
public Double apower;
|
||||
public Double voltage;
|
||||
public Double current;
|
||||
public Double pf;
|
||||
public Shelly2Energy aenergy;
|
||||
public Shelly2DeviceStatusTemp temperature;
|
||||
public String[] errors;
|
||||
}
|
||||
|
||||
public static class Shelly2DeviceStatusTemp {
|
||||
public Double tC;
|
||||
public Double tF;
|
||||
}
|
||||
|
||||
public static class Shelly2Energy {
|
||||
// "switch:1":{"id":1,"aenergy":{"total":0.003,"by_minute":[0.000,0.000,0.000],"minute_ts":1619910239}}}}
|
||||
public Double total;
|
||||
@SerializedName("by_minute")
|
||||
public Double[] byMinute;
|
||||
@SerializedName("minute_ts")
|
||||
public Long minuteTs;
|
||||
}
|
||||
|
||||
public static class Shelly2ConfigParms {
|
||||
public String name;
|
||||
public Boolean enable;
|
||||
public String server;
|
||||
@SerializedName("ssl_ca")
|
||||
public String sslCA;
|
||||
|
||||
// WiFi.SetConfig
|
||||
public Shelly2DeviceConfigAp ap;
|
||||
|
||||
// Switch.SetConfig
|
||||
@SerializedName("auto_on")
|
||||
public Boolean autoOn;
|
||||
@SerializedName("auto_on_delay")
|
||||
public Double autoOnDelay;
|
||||
@SerializedName("auto_off")
|
||||
public Boolean autoOff;
|
||||
@SerializedName("auto_off_delay")
|
||||
public Double autoOffDelay;
|
||||
}
|
||||
|
||||
public static class Shelly2RpcRequest {
|
||||
public Integer id = 0;
|
||||
public String method;
|
||||
|
||||
public static class Shelly2RpcRequestParams {
|
||||
public Integer id = 1;
|
||||
|
||||
// Cover
|
||||
public Integer pos;
|
||||
public Boolean on;
|
||||
|
||||
// Shelly.SetAuth
|
||||
public String user;
|
||||
public String realm;
|
||||
public String ha1;
|
||||
|
||||
// Shelly.Update
|
||||
public String stage;
|
||||
public String url;
|
||||
|
||||
// Cloud.SetConfig
|
||||
public Shelly2ConfigParms config;
|
||||
|
||||
public Shelly2RpcRequestParams withConfig() {
|
||||
config = new Shelly2ConfigParms();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
|
||||
|
||||
public Shelly2RpcRequest() {
|
||||
}
|
||||
|
||||
public Shelly2RpcRequest withMethod(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shelly2RpcRequest withId(int id) {
|
||||
params.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Shelly2RpcRequest withPos(int pos) {
|
||||
params.pos = pos;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Shelly2WsConfigResponse {
|
||||
public Integer id;
|
||||
public String src;
|
||||
|
||||
public static class Shelly2WsConfigResult {
|
||||
@SerializedName("restart_required")
|
||||
public Boolean restartRequired;
|
||||
}
|
||||
|
||||
public Shelly2WsConfigResult result;
|
||||
}
|
||||
|
||||
public static class Shelly2RpcBaseMessage {
|
||||
// Basic message format, e.g.
|
||||
// {"id":1,"src":"localweb528","method":"Shelly.GetConfig"}
|
||||
public class Shelly2RpcMessageError {
|
||||
public Integer code;
|
||||
public String message;
|
||||
}
|
||||
|
||||
public Integer id;
|
||||
public String src;
|
||||
public String dst;
|
||||
public String method;
|
||||
public Object params;
|
||||
public Object result;
|
||||
public Shelly2AuthRequest auth;
|
||||
public Shelly2RpcMessageError error;
|
||||
}
|
||||
|
||||
public static class Shelly2RpcNotifyStatus {
|
||||
public static class Shelly2NotifyStatus extends Shelly2DeviceStatusResult {
|
||||
public Double ts;
|
||||
}
|
||||
|
||||
public Integer id;
|
||||
public String src;
|
||||
public String dst;
|
||||
public String method;
|
||||
public Shelly2NotifyStatus params;
|
||||
public Shelly2NotifyStatus result;
|
||||
public Shelly2RpcMessageError error;
|
||||
}
|
||||
|
||||
public static String SHELLY2_AUTHTTYPE_DIGEST = "digest";
|
||||
public static String SHELLY2_AUTHTTYPE_STRING = "string";
|
||||
public static String SHELLY2_AUTHALG_SHA256 = "SHA-256";
|
||||
// = ':auth:'+HexHash("dummy_method:dummy_uri");
|
||||
public static String SHELLY2_AUTH_NOISE = "6370ec69915103833b5222b368555393393f098bfbfbb59f47e0590af135f062";
|
||||
|
||||
public static class Shelly2AuthRequest {
|
||||
public String username;
|
||||
public Long nonce;
|
||||
public Long cnonce;
|
||||
public Integer nc;
|
||||
public String realm;
|
||||
public String algorithm;
|
||||
public String response;
|
||||
@SerializedName("auth_type")
|
||||
public String authType;
|
||||
}
|
||||
|
||||
public static class Shelly2AuthResponse { // on 401 message contains the auth info
|
||||
@SerializedName("auth_type")
|
||||
public String authType;
|
||||
public Long nonce;
|
||||
public Integer nc;
|
||||
public String realm;
|
||||
public String algorithm;
|
||||
}
|
||||
|
||||
public class Shelly2NotifyEvent {
|
||||
public Integer id;
|
||||
public Double ts;
|
||||
public String component;
|
||||
public String event;
|
||||
public String msg;
|
||||
public Integer reason;
|
||||
@SerializedName("cfg_rev")
|
||||
public Integer cfgRev;
|
||||
}
|
||||
|
||||
public class Shelly2NotifyEventData {
|
||||
public Double ts;
|
||||
public ArrayList<Shelly2NotifyEvent> events;
|
||||
}
|
||||
|
||||
public static class Shelly2RpcNotifyEvent {
|
||||
public Double ts;
|
||||
Shelly2NotifyEventData params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,931 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.shelly.internal.api2;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiResult;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySensorSleepMode;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsEMeter;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLogin;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsMeter;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay;
|
||||
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.ShellySettingsWiFiNetwork;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthResponse;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2ConfigParms;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DeviceConfigSta;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2GetConfigResult;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfigAp;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfigAp.Shelly2DeviceConfigApRE;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceSettings;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusResult;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceStatus.Shelly2DeviceStatusSys.Shelly2DeviceStatusSysAvlUpdate;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2NotifyEvent;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus.Shelly2NotifyStatus;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcRequest;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcRequest.Shelly2RpcRequestParams;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2WsConfigResponse;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2WsConfigResponse.Shelly2WsConfigResult;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link Shelly2ApiRpc} implements Gen2 RPC interface
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterface, Shelly2RpctInterface {
|
||||
private final Logger logger = LoggerFactory.getLogger(Shelly2ApiRpc.class);
|
||||
private final @Nullable ShellyThingTable thingTable;
|
||||
|
||||
private boolean initialized = false;
|
||||
private boolean discovery = false;
|
||||
private Shelly2RpcSocket rpcSocket = new Shelly2RpcSocket();
|
||||
private Shelly2AuthResponse authInfo = new Shelly2AuthResponse();
|
||||
|
||||
/**
|
||||
* Regular constructor - called by Thing handler
|
||||
*
|
||||
* @param thingName Symbolic thing name
|
||||
* @param thing Thing Handler (ThingHandlerInterface)
|
||||
*/
|
||||
public Shelly2ApiRpc(String thingName, ShellyThingTable thingTable, ShellyThingInterface thing) {
|
||||
super(thingName, thing);
|
||||
this.thingName = thingName;
|
||||
this.thing = thing;
|
||||
this.thingTable = thingTable;
|
||||
try {
|
||||
getProfile().initFromThingType(thing.getThingType());
|
||||
} catch (ShellyApiException e) {
|
||||
logger.info("{}: Shelly2 API initialization failed!", thingName, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple initialization - called by discovery handler
|
||||
*
|
||||
* @param thingName Symbolic thing name
|
||||
* @param config Thing Configuration
|
||||
* @param httpClient HTTP Client to be passed to ShellyHttpClient
|
||||
*/
|
||||
public Shelly2ApiRpc(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
|
||||
super(thingName, config, httpClient);
|
||||
this.thingName = thingName;
|
||||
this.thingTable = null;
|
||||
this.discovery = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws ShellyApiException {
|
||||
if (!initialized) {
|
||||
rpcSocket = new Shelly2RpcSocket(thingName, thingTable, config.deviceIp);
|
||||
rpcSocket.addMessageHandler(this);
|
||||
initialized = true;
|
||||
} else {
|
||||
if (rpcSocket.isConnected()) {
|
||||
logger.debug("{}: Disconnect Rpc Socket on initialize", thingName);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException {
|
||||
ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile();
|
||||
|
||||
Shelly2GetConfigResult dc = apiRequest(SHELLYRPC_METHOD_GETCONFIG, null, Shelly2GetConfigResult.class);
|
||||
profile.isGen2 = true;
|
||||
profile.settingsJson = gson.toJson(dc);
|
||||
profile.thingName = thingName;
|
||||
profile.settings.name = profile.status.name = dc.sys.device.name;
|
||||
profile.name = getString(profile.settings.name);
|
||||
profile.settings.timezone = getString(dc.sys.location.tz);
|
||||
profile.settings.discoverable = getBool(dc.sys.device.discoverable);
|
||||
if (dc.wifi != null && dc.wifi.ap != null && dc.wifi.ap.rangeExtender != null) {
|
||||
profile.settings.wifiAp.rangeExtender = getBool(dc.wifi.ap.rangeExtender.enable);
|
||||
}
|
||||
if (dc.cloud != null) {
|
||||
profile.settings.cloud.enabled = getBool(dc.cloud.enable);
|
||||
}
|
||||
if (dc.mqtt != null) {
|
||||
profile.settings.mqtt.enable = getBool(dc.mqtt.enable);
|
||||
}
|
||||
if (dc.sys.sntp != null) {
|
||||
profile.settings.sntp.server = dc.sys.sntp.server;
|
||||
}
|
||||
|
||||
profile.isRoller = dc.cover0 != null;
|
||||
profile.settings.relays = fillRelaySettings(profile, dc);
|
||||
profile.settings.inputs = fillInputSettings(profile, dc);
|
||||
profile.settings.rollers = fillRollerSettings(profile, dc);
|
||||
|
||||
profile.isEMeter = true;
|
||||
profile.numInputs = profile.settings.inputs != null ? profile.settings.inputs.size() : 0;
|
||||
profile.numRelays = profile.settings.relays != null ? profile.settings.relays.size() : 0;
|
||||
profile.numRollers = profile.settings.rollers != null ? profile.settings.rollers.size() : 0;
|
||||
profile.hasRelays = profile.numRelays > 0 || profile.numRollers > 0;
|
||||
profile.mode = "";
|
||||
if (profile.hasRelays) {
|
||||
profile.mode = profile.isRoller ? SHELLY_CLASS_ROLLER : SHELLY_CLASS_RELAY;
|
||||
}
|
||||
|
||||
ShellySettingsDevice device = getDeviceInfo();
|
||||
profile.settings.device = device;
|
||||
profile.hostname = device.hostname;
|
||||
profile.deviceType = device.type;
|
||||
profile.mac = device.mac;
|
||||
profile.auth = device.auth;
|
||||
if (config.serviceName.isEmpty()) {
|
||||
config.serviceName = getString(profile.hostname);
|
||||
}
|
||||
profile.fwDate = substringBefore(device.fw, "/");
|
||||
profile.fwVersion = substringBefore(ShellyDeviceProfile.extractFwVersion(device.fw.replace("/", "/v")), "-");
|
||||
profile.status.update.oldVersion = profile.fwVersion;
|
||||
profile.status.hasUpdate = profile.status.update.hasUpdate = false;
|
||||
|
||||
if (dc.eth != null) {
|
||||
profile.settings.ethernet = getBool(dc.eth.enable);
|
||||
}
|
||||
if (dc.ble != null) {
|
||||
profile.settings.bluetooth = getBool(dc.ble.enable);
|
||||
}
|
||||
|
||||
profile.settings.wifiSta = new ShellySettingsWiFiNetwork();
|
||||
profile.settings.wifiSta1 = new ShellySettingsWiFiNetwork();
|
||||
fillWiFiSta(dc.wifi.sta, profile.settings.wifiSta);
|
||||
fillWiFiSta(dc.wifi.sta1, profile.settings.wifiSta1);
|
||||
|
||||
if (profile.hasRelays) {
|
||||
profile.status.relays = new ArrayList<>();
|
||||
profile.status.meters = new ArrayList<>();
|
||||
profile.status.emeters = new ArrayList<>();
|
||||
relayStatus.relays = new ArrayList<>();
|
||||
relayStatus.meters = new ArrayList<>();
|
||||
profile.numMeters = profile.isRoller ? profile.numRollers : profile.numRelays;
|
||||
for (int i = 0; i < profile.numRelays; i++) {
|
||||
profile.status.relays.add(new ShellySettingsRelay());
|
||||
relayStatus.relays.add(new ShellyShortStatusRelay());
|
||||
}
|
||||
for (int i = 0; i < profile.numMeters; i++) {
|
||||
profile.status.meters.add(new ShellySettingsMeter());
|
||||
profile.status.emeters.add(new ShellySettingsEMeter());
|
||||
relayStatus.meters.add(new ShellySettingsMeter());
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.numInputs > 0) {
|
||||
profile.status.inputs = new ArrayList<>();
|
||||
relayStatus.inputs = new ArrayList<>();
|
||||
for (int i = 0; i < profile.numInputs; i++) {
|
||||
ShellyInputState input = new ShellyInputState();
|
||||
input.input = 0;
|
||||
input.event = "";
|
||||
input.eventCount = 0;
|
||||
profile.status.inputs.add(input);
|
||||
relayStatus.inputs.add(input);
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.isRoller) {
|
||||
profile.status.rollers = new ArrayList<>();
|
||||
for (int i = 0; i < profile.numRollers; i++) {
|
||||
ShellyRollerStatus rs = new ShellyRollerStatus();
|
||||
profile.status.rollers.add(rs);
|
||||
rollerStatus.add(rs);
|
||||
}
|
||||
}
|
||||
|
||||
profile.status.dimmers = profile.isDimmer ? new ArrayList<>() : null;
|
||||
profile.status.lights = profile.isBulb ? new ArrayList<>() : null;
|
||||
profile.status.thermostats = profile.isTRV ? new ArrayList<>() : null;
|
||||
|
||||
if (profile.hasBattery) {
|
||||
profile.settings.sleepMode = new ShellySensorSleepMode();
|
||||
profile.settings.sleepMode.unit = "m";
|
||||
profile.settings.sleepMode.period = dc.sys.sleep != null ? dc.sys.sleep.wakeupPeriod / 60 : 720;
|
||||
checkSetWsCallback();
|
||||
}
|
||||
|
||||
profile.initialized = true;
|
||||
if (!discovery) {
|
||||
getStatus(); // make sure profile.status is initialized (e.g,. relay/meter status)
|
||||
asyncApiRequest(SHELLYRPC_METHOD_GETSTATUS); // request periodic status updates from device
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
private void fillWiFiSta(@Nullable Shelly2DeviceConfigSta from, ShellySettingsWiFiNetwork to) {
|
||||
to.enabled = from != null && !getString(from.ssid).isEmpty();
|
||||
if (from != null) {
|
||||
to.ssid = from.ssid;
|
||||
to.ip = from.ip;
|
||||
to.mask = from.netmask;
|
||||
to.dns = from.nameserver;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSetWsCallback() throws ShellyApiException {
|
||||
Shelly2ConfigParms wsConfig = apiRequest(SHELLYRPC_METHOD_WSGETCONFIG, null, Shelly2ConfigParms.class);
|
||||
String url = "ws://" + config.localIp + ":" + config.localPort + "/shelly/wsevent";
|
||||
if (!getBool(wsConfig.enable) || !url.equalsIgnoreCase(getString(wsConfig.server))) {
|
||||
logger.debug("{}: A battery device was detected without correct callback, fix it", thingName);
|
||||
wsConfig.enable = true;
|
||||
wsConfig.server = url;
|
||||
Shelly2RpcRequest request = new Shelly2RpcRequest();
|
||||
request.id = 0;
|
||||
request.method = SHELLYRPC_METHOD_WSSETCONFIG;
|
||||
request.params.config = wsConfig;
|
||||
Shelly2WsConfigResponse response = apiRequest(SHELLYRPC_METHOD_WSSETCONFIG, request.params,
|
||||
Shelly2WsConfigResponse.class);
|
||||
if (response.result != null && response.result.restartRequired) {
|
||||
logger.info("{}: WebSocket callback was updated, device is restarting", thingName);
|
||||
getThing().getApi().deviceReboot();
|
||||
getThing().reinitializeThing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnect(String deviceIp, boolean connected) {
|
||||
if (thing == null && thingTable != null) {
|
||||
thing = thingTable.getThing(deviceIp);
|
||||
logger.debug("{}: Get thing from thingTable", thingName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyStatus(Shelly2RpcNotifyStatus message) {
|
||||
logger.debug("{}: NotifyStatus update received: {}", thingName, gson.toJson(message));
|
||||
try {
|
||||
ShellyThingInterface t = thing;
|
||||
if (t == null) {
|
||||
logger.debug("{}: No matching thing on NotifyStatus for {}, ignore (src={}, dst={}, discovery={})",
|
||||
thingName, thingName, message.src, message.dst, discovery);
|
||||
return;
|
||||
}
|
||||
if (!t.isThingOnline() && t.getThingStatusDetail() != ThingStatusDetail.CONFIGURATION_PENDING) {
|
||||
logger.debug("{}: Thing is not in online state/connectable, ignore NotifyStatus", thingName);
|
||||
return;
|
||||
}
|
||||
|
||||
getThing().incProtMessages();
|
||||
if (message.error != null) {
|
||||
if (message.error.code == HttpStatus.UNAUTHORIZED_401 && !getString(message.error.message).isEmpty()) {
|
||||
// Save nonce for notification
|
||||
Shelly2AuthResponse auth = gson.fromJson(message.error.message, Shelly2AuthResponse.class);
|
||||
if (auth != null && auth.realm == null) {
|
||||
logger.debug("{}: Authentication data received: {}", thingName, message.error.message);
|
||||
authInfo = auth;
|
||||
}
|
||||
} else {
|
||||
logger.debug("{}: Error status received - {} {}", thingName, message.error.code,
|
||||
message.error.message);
|
||||
incProtErrors();
|
||||
}
|
||||
}
|
||||
|
||||
Shelly2NotifyStatus params = message.params;
|
||||
if (params != null) {
|
||||
if (getThing().getThingStatusDetail() != ThingStatusDetail.FIRMWARE_UPDATING) {
|
||||
getThing().setThingOnline();
|
||||
}
|
||||
|
||||
boolean updated = false;
|
||||
ShellyDeviceProfile profile = getProfile();
|
||||
ShellySettingsStatus status = profile.status;
|
||||
if (params.sys != null) {
|
||||
if (getBool(params.sys.restartRequired)) {
|
||||
logger.warn("{}: Device requires restart to activate changes", thingName);
|
||||
}
|
||||
status.uptime = params.sys.uptime;
|
||||
}
|
||||
status.temperature = SHELLY_API_INVTEMP; // mark invalid
|
||||
updated |= fillDeviceStatus(status, message.params, true);
|
||||
if (getDouble(status.temperature) == SHELLY_API_INVTEMP) {
|
||||
// no device temp available
|
||||
status.temperature = null;
|
||||
} else {
|
||||
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS));
|
||||
}
|
||||
|
||||
profile.status = status;
|
||||
if (updated) {
|
||||
getThing().restartWatchdog();
|
||||
}
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Unable to process status update", thingName, e);
|
||||
incProtErrors();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyEvent(Shelly2RpcNotifyEvent message) {
|
||||
try {
|
||||
logger.debug("{}: NotifyEvent received: {}", thingName, gson.toJson(message));
|
||||
ShellyDeviceProfile profile = getProfile();
|
||||
|
||||
getThing().incProtMessages();
|
||||
getThing().restartWatchdog();
|
||||
|
||||
for (Shelly2NotifyEvent e : message.params.events) {
|
||||
switch (e.event) {
|
||||
case SHELLY2_EVENT_BTNUP:
|
||||
case SHELLY2_EVENT_BTNDOWN:
|
||||
String bgroup = getProfile().getInputGroup(e.id);
|
||||
updateChannel(bgroup, CHANNEL_INPUT + profile.getInputSuffix(e.id),
|
||||
getOnOff(SHELLY2_EVENT_BTNDOWN.equals(getString(e.event))));
|
||||
getThing().triggerButton(profile.getInputGroup(e.id), e.id,
|
||||
mapValue(MAP_INPUT_EVENT_ID, e.event));
|
||||
break;
|
||||
|
||||
case SHELLY2_EVENT_1PUSH:
|
||||
case SHELLY2_EVENT_2PUSH:
|
||||
case SHELLY2_EVENT_3PUSH:
|
||||
case SHELLY2_EVENT_LPUSH:
|
||||
case SHELLY2_EVENT_SLPUSH:
|
||||
case SHELLY2_EVENT_LSPUSH:
|
||||
if (e.id < profile.numInputs) {
|
||||
ShellyInputState input = relayStatus.inputs.get(e.id);
|
||||
input.event = getString(MAP_INPUT_EVENT_TYPE.get(e.event));
|
||||
input.eventCount = getInteger(input.eventCount) + 1;
|
||||
relayStatus.inputs.set(e.id, input);
|
||||
profile.status.inputs.set(e.id, input);
|
||||
|
||||
String group = getProfile().getInputGroup(e.id);
|
||||
updateChannel(group, CHANNEL_STATUS_EVENTTYPE + profile.getInputSuffix(e.id),
|
||||
getStringType(input.event));
|
||||
updateChannel(group, CHANNEL_STATUS_EVENTCOUNT + profile.getInputSuffix(e.id),
|
||||
getDecimal(input.eventCount));
|
||||
getThing().triggerButton(profile.getInputGroup(e.id), e.id,
|
||||
mapValue(MAP_INPUT_EVENT_ID, e.event));
|
||||
}
|
||||
break;
|
||||
case SHELLY2_EVENT_CFGCHANGED:
|
||||
logger.debug("{}: Configuration update detected, re-initialize", thingName);
|
||||
getThing().requestUpdates(1, true); // refresh config
|
||||
break;
|
||||
|
||||
case SHELLY2_EVENT_OTASTART:
|
||||
logger.debug("{}: Firmware update started: {}", thingName, getString(e.msg));
|
||||
getThing().postEvent(e.event, true);
|
||||
getThing().setThingOffline(ThingStatusDetail.FIRMWARE_UPDATING,
|
||||
"offline.status-error-fwupgrade");
|
||||
break;
|
||||
case SHELLY2_EVENT_OTAPROGRESS:
|
||||
logger.debug("{}: Firmware update in progress: {}", thingName, getString(e.msg));
|
||||
getThing().postEvent(e.event, false);
|
||||
break;
|
||||
case SHELLY2_EVENT_OTADONE:
|
||||
logger.debug("{}: Firmware update completed: {}", thingName, getString(e.msg));
|
||||
getThing().setThingOffline(ThingStatusDetail.CONFIGURATION_PENDING,
|
||||
"offline.status-error-restarted");
|
||||
getThing().requestUpdates(1, true); // refresh config
|
||||
break;
|
||||
case SHELLY2_EVENT_SLEEP:
|
||||
logger.debug("{}: Device went to sleep mode", thingName);
|
||||
break;
|
||||
case SHELLY2_EVENT_WIFICONNFAILED:
|
||||
logger.debug("{}: WiFi connect failed, check setup, reason {}", thingName,
|
||||
getInteger(e.reason));
|
||||
getThing().postEvent(e.event, false);
|
||||
break;
|
||||
case SHELLY2_EVENT_WIFIDISCONNECTED:
|
||||
logger.debug("{}: WiFi disconnected, reason {}", thingName, getInteger(e.reason));
|
||||
getThing().postEvent(e.event, false);
|
||||
break;
|
||||
default:
|
||||
logger.debug("{}: Event {} was not handled", thingName, e.event);
|
||||
}
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Unable to process event", thingName, e);
|
||||
incProtErrors();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
logger.debug("{}: Unexpected RPC message received: {}", thingName, message);
|
||||
incProtErrors();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int statusCode, String reason) {
|
||||
try {
|
||||
logger.debug("{}: WebSocket connection closed, status = {}/{}", thingName, statusCode, getString(reason));
|
||||
if (statusCode == StatusCode.ABNORMAL && !discovery && getProfile().alwaysOn) { // e.g. device rebooted
|
||||
thingOffline("WebSocket connection closed abnormal");
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Exception on onClose()", thingName, e);
|
||||
incProtErrors();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable cause) {
|
||||
logger.debug("{}: WebSocket error", thingName);
|
||||
if (thing != null && thing.getProfile().alwaysOn) {
|
||||
thingOffline("WebSocket error");
|
||||
}
|
||||
}
|
||||
|
||||
private void thingOffline(String reason) {
|
||||
if (thing != null) { // do not reinit of battery powered devices with sleep mode
|
||||
thing.setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "offline.status-error-unexpected-error",
|
||||
reason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellySettingsDevice getDeviceInfo() throws ShellyApiException {
|
||||
Shelly2DeviceSettings device = callApi("/shelly", Shelly2DeviceSettings.class);
|
||||
ShellySettingsDevice info = new ShellySettingsDevice();
|
||||
info.hostname = getString(device.id);
|
||||
info.fw = getString(device.firmware);
|
||||
info.type = getString(device.model);
|
||||
info.mac = getString(device.mac);
|
||||
info.auth = getBool(device.authEnable);
|
||||
info.gen = getInteger(device.gen);
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellySettingsStatus getStatus() throws ShellyApiException {
|
||||
ShellyDeviceProfile profile = getProfile();
|
||||
ShellySettingsStatus status = profile.status;
|
||||
Shelly2DeviceStatusResult ds = apiRequest(SHELLYRPC_METHOD_GETSTATUS, null, Shelly2DeviceStatusResult.class);
|
||||
status.time = ds.sys.time;
|
||||
status.uptime = ds.sys.uptime;
|
||||
status.cloud.connected = getBool(ds.cloud.connected);
|
||||
status.mqtt.connected = getBool(ds.mqtt.connected);
|
||||
status.wifiSta.ssid = getString(ds.wifi.ssid);
|
||||
status.wifiSta.enabled = !status.wifiSta.ssid.isEmpty();
|
||||
status.wifiSta.ip = getString(ds.wifi.staIP);
|
||||
status.wifiSta.rssi = getInteger(ds.wifi.rssi);
|
||||
status.fsFree = ds.sys.fsFree;
|
||||
status.fsSize = ds.sys.fsSize;
|
||||
status.discoverable = getBool(profile.settings.discoverable);
|
||||
|
||||
if (ds.sys.wakeupPeriod != null) {
|
||||
profile.settings.sleepMode.period = ds.sys.wakeupPeriod / 60;
|
||||
}
|
||||
|
||||
status.hasUpdate = status.update.hasUpdate = false;
|
||||
status.update.oldVersion = getProfile().fwVersion;
|
||||
if (ds.sys.availableUpdates != null) {
|
||||
status.update.hasUpdate = ds.sys.availableUpdates.stable != null;
|
||||
if (ds.sys.availableUpdates.stable != null) {
|
||||
status.update.newVersion = "v" + getString(ds.sys.availableUpdates.stable.version);
|
||||
}
|
||||
if (ds.sys.availableUpdates.beta != null) {
|
||||
status.update.betaVersion = "v" + getString(ds.sys.availableUpdates.beta.version);
|
||||
}
|
||||
}
|
||||
|
||||
if (ds.sys.wakeUpReason != null && ds.sys.wakeUpReason.boot != null) {
|
||||
List<Object> values = new ArrayList<>();
|
||||
String boot = getString(ds.sys.wakeUpReason.boot);
|
||||
String cause = getString(ds.sys.wakeUpReason.cause);
|
||||
|
||||
// Index 0 is aggregated status, 1 boot, 2 cause
|
||||
String reason = boot.equals(SHELLY2_WAKEUPO_BOOT_RESTART) ? ALARM_TYPE_RESTARTED : cause;
|
||||
values.add(reason);
|
||||
values.add(ds.sys.wakeUpReason.boot);
|
||||
values.add(ds.sys.wakeUpReason.cause);
|
||||
getThing().updateWakeupReason(values);
|
||||
}
|
||||
|
||||
fillDeviceStatus(status, ds, false);
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSleepTime(int value) throws ShellyApiException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyStatusRelay getRelayStatus(int relayIndex) throws ShellyApiException {
|
||||
if (getProfile().status.wifiSta.ssid == null) {
|
||||
// Update status when not yet initialized
|
||||
getStatus();
|
||||
}
|
||||
return relayStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRelayTurn(int id, String turnMode) throws ShellyApiException {
|
||||
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
|
||||
params.id = id;
|
||||
params.on = SHELLY_API_ON.equals(turnMode);
|
||||
apiRequest(SHELLYRPC_METHOD_SWITCH_SET, params, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyRollerStatus getRollerStatus(int rollerIndex) throws ShellyApiException {
|
||||
if (rollerIndex < rollerStatus.size()) {
|
||||
return rollerStatus.get(rollerIndex);
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid rollerIndex on getRollerStatus");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollerTurn(int relayIndex, String turnMode) throws ShellyApiException {
|
||||
String operation = "";
|
||||
switch (turnMode) {
|
||||
case SHELLY_ALWD_ROLLER_TURN_OPEN:
|
||||
operation = SHELLY2_COVER_CMD_OPEN;
|
||||
break;
|
||||
case SHELLY_ALWD_ROLLER_TURN_CLOSE:
|
||||
operation = SHELLY2_COVER_CMD_CLOSE;
|
||||
break;
|
||||
case SHELLY_ALWD_ROLLER_TURN_STOP:
|
||||
operation = SHELLY2_COVER_CMD_STOP;
|
||||
break;
|
||||
}
|
||||
|
||||
apiRequest(new Shelly2RpcRequest().withMethod("Cover." + operation).withId(relayIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollerPos(int relayIndex, int position) throws ShellyApiException {
|
||||
apiRequest(
|
||||
new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_COVER_SETPOS).withId(relayIndex).withPos(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
|
||||
return sensorData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAutoTimer(int index, String timerName, double value) throws ShellyApiException {
|
||||
Shelly2RpcRequest req = new Shelly2RpcRequest().withMethod(SHELLYRPC_METHOD_SWITCH_SETCONFIG).withId(index);
|
||||
|
||||
req.params.withConfig();
|
||||
req.params.config.name = "Switch" + index;
|
||||
if (timerName.equals(SHELLY_TIMER_AUTOON)) {
|
||||
req.params.config.autoOn = value > 0;
|
||||
req.params.config.autoOnDelay = value;
|
||||
} else {
|
||||
req.params.config.autoOff = value > 0;
|
||||
req.params.config.autoOffDelay = value;
|
||||
}
|
||||
apiRequest(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellySettingsLogin getLoginSettings() throws ShellyApiException {
|
||||
return new ShellySettingsLogin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException {
|
||||
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
|
||||
params.user = "admin";
|
||||
params.realm = config.serviceName;
|
||||
params.ha1 = sha256(params.user + ":" + params.realm + ":" + password);
|
||||
apiRequest(SHELLYRPC_METHOD_AUTHSET, params, String.class);
|
||||
|
||||
ShellySettingsLogin res = new ShellySettingsLogin();
|
||||
res.enabled = true;
|
||||
res.username = params.user;
|
||||
res.password = password;
|
||||
return new ShellySettingsLogin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setWiFiRangeExtender(boolean enable) throws ShellyApiException {
|
||||
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
|
||||
params.config.ap = new Shelly2DeviceConfigAp();
|
||||
params.config.ap.rangeExtender = new Shelly2DeviceConfigApRE();
|
||||
params.config.ap.rangeExtender.enable = enable;
|
||||
Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_WIFISETCONG, params, Shelly2WsConfigResult.class);
|
||||
return res.restartRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setEthernet(boolean enable) throws ShellyApiException {
|
||||
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
|
||||
params.config.enable = enable;
|
||||
Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_ETHSETCONG, params, Shelly2WsConfigResult.class);
|
||||
return res.restartRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBluetooth(boolean enable) throws ShellyApiException {
|
||||
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
|
||||
params.config.enable = enable;
|
||||
Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_BLESETCONG, params, Shelly2WsConfigResult.class);
|
||||
return res.restartRequired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deviceReboot() throws ShellyApiException {
|
||||
return apiRequest(SHELLYRPC_METHOD_REBOOT, null, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String factoryReset() throws ShellyApiException {
|
||||
return apiRequest(SHELLYRPC_METHOD_RESET, null, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException {
|
||||
Shelly2DeviceStatusSysAvlUpdate status = apiRequest(SHELLYRPC_METHOD_CHECKUPD, null,
|
||||
Shelly2DeviceStatusSysAvlUpdate.class);
|
||||
ShellyOtaCheckResult result = new ShellyOtaCheckResult();
|
||||
result.status = status.stable != null || status.beta != null ? "new" : "ok";
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellySettingsUpdate firmwareUpdate(String fwurl) throws ShellyApiException {
|
||||
ShellySettingsUpdate res = new ShellySettingsUpdate();
|
||||
boolean prod = fwurl.contains("update");
|
||||
boolean beta = fwurl.contains("beta");
|
||||
|
||||
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams();
|
||||
if (prod || beta) {
|
||||
params.stage = prod || beta ? "stable" : "beta";
|
||||
} else {
|
||||
params.url = fwurl;
|
||||
}
|
||||
apiRequest(SHELLYRPC_METHOD_UPDATE, params, String.class);
|
||||
res.status = "Update initiated";
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setCloud(boolean enable) throws ShellyApiException {
|
||||
Shelly2RpcRequestParams params = new Shelly2RpcRequestParams().withConfig();
|
||||
params.config.enable = enable;
|
||||
Shelly2WsConfigResult res = apiRequest(SHELLYRPC_METHOD_CLOUDSET, params, Shelly2WsConfigResult.class);
|
||||
return res.restartRequired ? "restart required" : "ok";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setDebug(boolean enabled) throws ShellyApiException {
|
||||
return "failed";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDebugLog(String id) throws ShellyApiException {
|
||||
return ""; // Gen2 uses WS to publish debug log
|
||||
}
|
||||
|
||||
/*
|
||||
* The following API calls are not yet relevant, because currently there a no Plus/Pro (Gen2) devices of those
|
||||
* categories (e.g. bulbs)
|
||||
*/
|
||||
@Override
|
||||
public void setLedStatus(String ledName, Boolean value) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyStatusLight getLightStatus() throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyShortLightStatus getLightStatus(int index) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLightParm(int lightIndex, String parm, String value) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLightParms(int lightIndex, Map<String, String> parameters) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellyShortLightStatus setLightTurn(int id, String turnMode) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBrightness(int id, int brightness, boolean autoOn) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLightMode(String mode) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValveMode(int valveId, boolean auto) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValvePosition(int valveId, double value) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValveTemperature(int valveId, int value) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValveProfile(int valveId, int value) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValveBoostTime(int valveId, int value) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startValveBoost(int valveId, int value) throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resetStaCache() throws ShellyApiException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActionURLs() throws ShellyApiException {
|
||||
// not relevant for Gen2
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException {
|
||||
// not relevant for Gen2
|
||||
return new ShellySettingsLogin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCoIoTDescription() {
|
||||
return ""; // not relevant to Gen2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException {
|
||||
throw new ShellyApiException("API call not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setWiFiRecovery(boolean enable) throws ShellyApiException {
|
||||
return "failed"; // not supported by Gen2
|
||||
}
|
||||
|
||||
@Override
|
||||
public String setApRoaming(boolean enable) throws ShellyApiException {
|
||||
return "false";// not supported by Gen2
|
||||
}
|
||||
|
||||
private void asyncApiRequest(String method) throws ShellyApiException {
|
||||
Shelly2RpcBaseMessage request = buildRequest(method, null);
|
||||
reconnect();
|
||||
rpcSocket.sendMessage(gson.toJson(request)); // submit, result wull be async
|
||||
}
|
||||
|
||||
public <T> T apiRequest(String method, @Nullable Object params, Class<T> classOfT) throws ShellyApiException {
|
||||
String json = "";
|
||||
Shelly2RpcBaseMessage req = buildRequest(method, params);
|
||||
try {
|
||||
reconnect(); // make sure WS is connected
|
||||
|
||||
if (authInfo.realm != null) {
|
||||
req.auth = buildAuthRequest(authInfo, config.userId, config.serviceName, config.password);
|
||||
}
|
||||
json = rpcPost(gson.toJson(req));
|
||||
} catch (ShellyApiException e) {
|
||||
ShellyApiResult res = e.getApiResult();
|
||||
String auth = getString(res.authResponse);
|
||||
if (res.isHttpAccessUnauthorized() && !auth.isEmpty()) {
|
||||
String[] options = auth.split(",");
|
||||
for (String o : options) {
|
||||
String key = substringBefore(o, "=").stripLeading().trim();
|
||||
String value = substringAfter(o, "=").replaceAll("\"", "").trim();
|
||||
switch (key) {
|
||||
case "Digest qop":
|
||||
break;
|
||||
case "realm":
|
||||
authInfo.realm = value;
|
||||
break;
|
||||
case "nonce":
|
||||
authInfo.nonce = Long.parseLong(value, 16);
|
||||
break;
|
||||
case "algorithm":
|
||||
authInfo.algorithm = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
authInfo.nc = 1;
|
||||
req.auth = buildAuthRequest(authInfo, config.userId, authInfo.realm, config.password);
|
||||
json = rpcPost(gson.toJson(req));
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
json = gson.toJson(gson.fromJson(json, Shelly2RpcBaseMessage.class).result);
|
||||
return fromJson(gson, json, classOfT);
|
||||
}
|
||||
|
||||
public <T> T apiRequest(Shelly2RpcRequest request, Class<T> classOfT) throws ShellyApiException {
|
||||
return apiRequest(request.method, request.params, classOfT);
|
||||
}
|
||||
|
||||
public String apiRequest(Shelly2RpcRequest request) throws ShellyApiException {
|
||||
return apiRequest(request.method, request.params, String.class);
|
||||
}
|
||||
|
||||
private String rpcPost(String postData) throws ShellyApiException {
|
||||
return httpPost("/rpc", postData);
|
||||
}
|
||||
|
||||
private void reconnect() throws ShellyApiException {
|
||||
if (!rpcSocket.isConnected()) {
|
||||
logger.debug("{}: Connect Rpc Socket (discovery = {})", thingName, discovery);
|
||||
rpcSocket.connect();
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
if (rpcSocket.isConnected()) {
|
||||
rpcSocket.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public Shelly2RpctInterface getRpcHandler() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
logger.debug("{}: Closing Rpc API (socket is {}, discovery={})", thingName,
|
||||
rpcSocket.isConnected() ? "connected" : "disconnected", discovery);
|
||||
disconnect();
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
private void incProtErrors() {
|
||||
if (thing != null) {
|
||||
thing.incProtErrors();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.shelly.internal.api2;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcBaseMessage;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* {@link Shelly1HttpApi} wraps the Shelly REST API and provides various low level function to access the device api
|
||||
* (not
|
||||
* cloud api).
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@WebSocket(maxIdleTime = Integer.MAX_VALUE)
|
||||
public class Shelly2RpcSocket {
|
||||
private final Logger logger = LoggerFactory.getLogger(Shelly2RpcSocket.class);
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
private String thingName = "";
|
||||
private String deviceIp = "";
|
||||
private boolean inbound = false;
|
||||
private CountDownLatch connectLatch = new CountDownLatch(1);
|
||||
|
||||
private @Nullable Session session;
|
||||
private @Nullable Shelly2RpctInterface websocketHandler;
|
||||
private WebSocketClient client = new WebSocketClient();
|
||||
private @Nullable ShellyThingTable thingTable;
|
||||
|
||||
public Shelly2RpcSocket() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular constructor for Thing and Discover handler
|
||||
*
|
||||
* @param thingName Thing/Service name
|
||||
* @param thingTable
|
||||
* @param deviceIp IP address for the device
|
||||
*/
|
||||
public Shelly2RpcSocket(String thingName, @Nullable ShellyThingTable thingTable, String deviceIp) {
|
||||
this.thingName = thingName;
|
||||
this.deviceIp = deviceIp;
|
||||
this.thingTable = thingTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor called from Servlet handler
|
||||
*
|
||||
* @param thingTable
|
||||
* @param inbound
|
||||
*/
|
||||
public Shelly2RpcSocket(ShellyThingTable thingTable, boolean inbound) {
|
||||
this.thingTable = thingTable;
|
||||
this.inbound = inbound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener for inbound messages implementing Shelly2RpctInterface
|
||||
*
|
||||
* @param interfacehandler
|
||||
*/
|
||||
public void addMessageHandler(Shelly2RpctInterface interfacehandler) {
|
||||
this.websocketHandler = interfacehandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect outbound Web Socket
|
||||
*
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public void connect() throws ShellyApiException {
|
||||
try {
|
||||
disconnect(); // for safety
|
||||
|
||||
URI uri = new URI("ws://" + deviceIp + "/rpc");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setHeader(HttpHeaders.HOST, deviceIp);
|
||||
request.setHeader("Origin", "http://" + deviceIp);
|
||||
request.setHeader("Pragma", "no-cache");
|
||||
request.setHeader("Cache-Control", "no-cache");
|
||||
|
||||
logger.debug("{}: Connect WebSocket, URI={}", thingName, uri);
|
||||
client = new WebSocketClient();
|
||||
connectLatch = new CountDownLatch(1);
|
||||
client.start();
|
||||
client.setConnectTimeout(5000);
|
||||
client.setStopTimeout(0);
|
||||
client.connect(this, uri, request);
|
||||
} catch (Exception e) {
|
||||
throw new ShellyApiException("Unable to initialize WebSocket", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Web Socket is connected, lookup thing and create connectLatch to synchronize first sendMessage()
|
||||
*
|
||||
* @param session Newly created WebSocket connection
|
||||
*/
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session session) {
|
||||
try {
|
||||
if (session.getRemoteAddress() == null) {
|
||||
logger.debug("{}: Invalid inbound WebSocket connect", thingName);
|
||||
session.close(StatusCode.ABNORMAL, "Invalid remote IP");
|
||||
return;
|
||||
}
|
||||
this.session = session;
|
||||
if (deviceIp.isEmpty()) {
|
||||
// This is the inbound event web socket
|
||||
deviceIp = session.getRemoteAddress().getAddress().getHostAddress();
|
||||
}
|
||||
if (websocketHandler == null) {
|
||||
if (thingTable != null) {
|
||||
ShellyThingInterface thing = thingTable.getThing(deviceIp);
|
||||
Shelly2ApiRpc api = (Shelly2ApiRpc) thing.getApi();
|
||||
websocketHandler = api.getRpcHandler();
|
||||
}
|
||||
}
|
||||
connectLatch.countDown();
|
||||
|
||||
logger.debug("{}: WebSocket connected {}<-{}, Idle Timeout={}", thingName, session.getLocalAddress(),
|
||||
session.getRemoteAddress(), session.getIdleTimeout());
|
||||
if (websocketHandler != null) {
|
||||
websocketHandler.onConnect(deviceIp, true);
|
||||
return;
|
||||
}
|
||||
} catch (IllegalArgumentException e) { // unknown thing
|
||||
// debug is below
|
||||
}
|
||||
|
||||
if (websocketHandler == null && thingTable != null) {
|
||||
logger.debug("Rpc: Unable to handle connection from {} (unknown/disabled thing), closing socket", deviceIp);
|
||||
session.close(StatusCode.SHUTDOWN, "Thing not active");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send request over WebSocket
|
||||
*
|
||||
* @param str API request message
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public void sendMessage(String str) throws ShellyApiException {
|
||||
if (session != null) {
|
||||
try {
|
||||
connectLatch.await();
|
||||
session.getRemote().sendString(str);
|
||||
return;
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new ShellyApiException("Error RpcSend failed", e);
|
||||
}
|
||||
}
|
||||
throw new ShellyApiException("Unable to send API request (No Rpc session)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Close WebSocket session
|
||||
*/
|
||||
public void disconnect() {
|
||||
try {
|
||||
if (session != null) {
|
||||
Session s = session;
|
||||
if (s.isOpen()) {
|
||||
logger.debug("{}: Disconnecting WebSocket ({} -> {})", thingName, s.getLocalAddress(),
|
||||
s.getRemoteAddress());
|
||||
s.disconnect();
|
||||
}
|
||||
s.close(StatusCode.NORMAL, "Socket closed");
|
||||
session = null;
|
||||
}
|
||||
if (client.isStarted()) {
|
||||
client.stop();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e.getCause() instanceof InterruptedException) {
|
||||
logger.debug("{}: Unable to close socket - interrupted", thingName); // e.g. device was rebooted
|
||||
} else {
|
||||
logger.debug("{}: Unable to close socket", thingName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inbound WebSocket message
|
||||
*
|
||||
* @param session WebSpcket session
|
||||
* @param receivedMessage Textial API message
|
||||
*/
|
||||
@OnWebSocketMessage
|
||||
public void onText(Session session, String receivedMessage) {
|
||||
try {
|
||||
Shelly2RpctInterface handler = websocketHandler;
|
||||
Shelly2RpcBaseMessage message = fromJson(gson, receivedMessage, Shelly2RpcBaseMessage.class);
|
||||
logger.trace("{}: Inbound Rpc message: {}", thingName, receivedMessage);
|
||||
if (handler != null) {
|
||||
if (thingName.isEmpty()) {
|
||||
thingName = getString(message.src);
|
||||
}
|
||||
if (message.method == null) {
|
||||
message.method = SHELLYRPC_METHOD_NOTIFYFULLSTATUS;
|
||||
}
|
||||
switch (getString(message.method)) {
|
||||
case SHELLYRPC_METHOD_NOTIFYSTATUS:
|
||||
case SHELLYRPC_METHOD_NOTIFYFULLSTATUS:
|
||||
Shelly2RpcNotifyStatus status = fromJson(gson, receivedMessage, Shelly2RpcNotifyStatus.class);
|
||||
if (status.params == null) {
|
||||
status.params = status.result;
|
||||
}
|
||||
handler.onNotifyStatus(status);
|
||||
return;
|
||||
case SHELLYRPC_METHOD_NOTIFYEVENT:
|
||||
handler.onNotifyEvent(fromJson(gson, receivedMessage, Shelly2RpcNotifyEvent.class));
|
||||
return;
|
||||
default:
|
||||
handler.onMessage(receivedMessage);
|
||||
}
|
||||
} else {
|
||||
logger.debug("{}: No Rpc listener registered for device {}, skip message: {}", thingName,
|
||||
getString(message.src), receivedMessage);
|
||||
}
|
||||
} catch (ShellyApiException | IllegalArgumentException | NullPointerException e) {
|
||||
logger.debug("{}: Unable to process Rpc message: {}", thingName, receivedMessage, e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return session != null && session.isOpen();
|
||||
}
|
||||
|
||||
public boolean isInbound() {
|
||||
return inbound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Web Socket closed, notify thing handler
|
||||
*
|
||||
* @param statusCode StatusCode
|
||||
* @param reason Textual reason
|
||||
*/
|
||||
@OnWebSocketClose
|
||||
public void onClose(int statusCode, String reason) {
|
||||
if (statusCode != StatusCode.NORMAL) {
|
||||
logger.trace("{}: Rpc connection closed: {} - {}", thingName, statusCode, getString(reason));
|
||||
}
|
||||
if (inbound) {
|
||||
// Ignore disconnect: Device establishes the socket, sends NotifyxFullStatus and disconnects
|
||||
return;
|
||||
}
|
||||
disconnect();
|
||||
if (websocketHandler != null) {
|
||||
websocketHandler.onClose(statusCode, reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket error handler
|
||||
*
|
||||
* @param cause WebSocket error/Exception
|
||||
*/
|
||||
@OnWebSocketError
|
||||
public void onError(Throwable cause) {
|
||||
if (inbound) {
|
||||
// Ignore disconnect: Device establishes the socket, sends NotifyxFullStatus and disconnects
|
||||
return;
|
||||
}
|
||||
if (websocketHandler != null) {
|
||||
websocketHandler.onError(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.shelly.internal.api2;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyEvent;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2RpcNotifyStatus;
|
||||
|
||||
/**
|
||||
* The {@link WebsocketInterface} is responsible for interfacing the Websocket.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Shelly2RpctInterface {
|
||||
|
||||
public void onConnect(String deviceIp, boolean connected);
|
||||
|
||||
public void onMessage(String decodedmessage);
|
||||
|
||||
public void onNotifyStatus(Shelly2RpcNotifyStatus message);
|
||||
|
||||
public void onNotifyEvent(Shelly2RpcNotifyEvent message);
|
||||
|
||||
public void onClose(int statusCode, String reason);
|
||||
|
||||
public void onError(Throwable cause);
|
||||
}
|
||||
@@ -27,9 +27,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiResult;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
@@ -139,15 +141,20 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
config.userId = bindingConfig.defaultUserId;
|
||||
config.password = bindingConfig.defaultPassword;
|
||||
|
||||
boolean gen2 = "2".equals(service.getPropertyString("gen"));
|
||||
try {
|
||||
Shelly1HttpApi api = new Shelly1HttpApi(name, config, httpClient);
|
||||
|
||||
ShellyApiInterface api = gen2 ? new Shelly2ApiRpc(name, config, httpClient)
|
||||
: new Shelly1HttpApi(name, config, httpClient);
|
||||
if (name.contains("plushat")) {
|
||||
int i = 1;
|
||||
}
|
||||
api.initialize();
|
||||
profile = api.getDeviceProfile(thingType);
|
||||
api.close();
|
||||
logger.debug("{}: Shelly settings : {}", name, profile.settingsJson);
|
||||
deviceName = profile.name;
|
||||
model = profile.deviceType;
|
||||
mode = profile.mode;
|
||||
|
||||
properties = ShellyBaseHandler.fillDeviceProperties(profile);
|
||||
logger.trace("{}: thingType={}, deviceType={}, mode={}, symbolic name={}", name, thingType,
|
||||
profile.deviceType, mode.isEmpty() ? "<standard>" : mode, deviceName);
|
||||
@@ -174,7 +181,7 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
addProperty(properties, PROPERTY_SERVICE_NAME, name);
|
||||
addProperty(properties, PROPERTY_DEV_NAME, deviceName);
|
||||
addProperty(properties, PROPERTY_DEV_TYPE, thingType);
|
||||
addProperty(properties, PROPERTY_DEV_GEN, "1");
|
||||
addProperty(properties, PROPERTY_DEV_GEN, gen2 ? "2" : "1");
|
||||
addProperty(properties, PROPERTY_DEV_MODE, mode);
|
||||
|
||||
logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString());
|
||||
|
||||
@@ -69,6 +69,8 @@ public class ShellyThingCreator {
|
||||
public static final String SHELLYDT_PLUS1PMUL = "SNSW-001P15UL";
|
||||
public static final String SHELLYDT_PLUS2PM_RELAY = "SNSW-002P16EU-relay";
|
||||
public static final String SHELLYDT_PLUS2PM_ROLLER = "SNSW-002P16EU-roller";
|
||||
public static final String SHELLYDT_PLUS2PM_RELAY_2 = "SNSW-102P16EU-relay";
|
||||
public static final String SHELLYDT_PLUS2PM_ROLLER_2 = "SNSW-102P16EU-roller";
|
||||
public static final String SHELLYDT_PLUSPLUGUS = "SNPL-00116US";
|
||||
public static final String SHELLYDT_PLUSI4 = "SNSN-0024X";
|
||||
public static final String SHELLYDT_PLUSI4DC = "SNSN-0D24X";
|
||||
@@ -77,17 +79,19 @@ public class ShellyThingCreator {
|
||||
// Shelly Pro Series
|
||||
public static final String SHELLYDT_PRO1 = "SPSW-001XE16EU";
|
||||
public static final String SHELLYDT_PRO1_2 = "SPSW-101XE16EU";
|
||||
public static final String SHELLYDT_PRO1_3 = "SPSW-201XE16EU";
|
||||
public static final String SHELLYDT_PRO1PM = "SPSW-001PE16EU";
|
||||
public static final String SHELLYDT_PRO1PM_ = "SPSW-201PE16EU";
|
||||
public static final String SHELLYDT_PRO1PM_2 = "SPSW-101PE16EU";
|
||||
public static final String SHELLYDT_PRO1PM_3 = "SPSW-201PE16EU";
|
||||
public static final String SHELLYDT_PRO2_RELAY = "SPSW-002XE16EU-relay";
|
||||
public static final String SHELLYDT_PRO2_ROLLER = "SPSW-002XE16EU-roller";
|
||||
public static final String SHELLYDT_PRO2_RELAY_2 = "SPSW-102XE16EU-relay";
|
||||
public static final String SHELLYDT_PRO2_ROLLER_2 = "SPSW-102XE16EU-roller";
|
||||
public static final String SHELLYDT_PRO2_RELAY_3 = "SPSW-202XE16EU-relay";
|
||||
public static final String SHELLYDT_PRO2PM_RELAY = "SPSW-002PE16EU-relay";
|
||||
public static final String SHELLYDT_PRO2PM_ROLLER = "SPSW-002PE16EU-roller";
|
||||
public static final String SHELLYDT_PRO2PM_RELAY_2 = "SPSW-002PE16EU-relay";
|
||||
public static final String SHELLYDT_PRO2PM_ROLLER_2 = "SPSW-002PE16EU-roller";
|
||||
public static final String SHELLYDT_PRO2PM_RELAY_2 = "SPSW-102PE16EU-relay";
|
||||
public static final String SHELLYDT_PRO2PM_ROLLER_2 = "SPSW-102PE16EU-roller";
|
||||
public static final String SHELLYDT_PRO2PM_RELAY_3 = "SPSW-202PE16EU-relay";
|
||||
public static final String SHELLYDT_PRO2PM_ROLLER_3 = "SPSW-202PE16EU-roller";
|
||||
public static final String SHELLYDT_PRO3 = "SPSW-003XE16EU";
|
||||
public static final String SHELLYDT_PRO4PM = "SPSW-004PE16EU";
|
||||
public static final String SHELLYDT_PRO4PM_2 = "SPSW-104PE16EU";
|
||||
@@ -100,7 +104,6 @@ public class ShellyThingCreator {
|
||||
public static final String THING_TYPE_SHELLY3EM_STR = "shellyem3"; // bad: misspelled product name, it's 3EM
|
||||
public static final String THING_TYPE_SHELLY2_PREFIX = "shellyswitch";
|
||||
public static final String THING_TYPE_SHELLY2_RELAY_STR = "shelly2-relay";
|
||||
public static final String THING_TYPE_SHELLY2_ROLLER_STR = "shelly2-roller";
|
||||
public static final String THING_TYPE_SHELLY25_PREFIX = "shellyswitch25";
|
||||
public static final String THING_TYPE_SHELLY25_RELAY_STR = "shelly25-relay";
|
||||
public static final String THING_TYPE_SHELLY25_ROLLER_STR = "shelly25-roller";
|
||||
@@ -147,7 +150,6 @@ public class ShellyThingCreator {
|
||||
public static final String THING_TYPE_SHELLYPRO1_STR = "shellypro1";
|
||||
public static final String THING_TYPE_SHELLYPRO1PM_STR = "shellypro1pm";
|
||||
public static final String THING_TYPE_SHELLYPRO2_RELAY_STR = "shellypro2-relay";
|
||||
public static final String THING_TYPE_SHELLYPRO2_ROLLER_STR = "shellypro2-roller";
|
||||
public static final String THING_TYPE_SHELLYPRO2PM_RELAY_STR = "shellypro2pm-relay";
|
||||
public static final String THING_TYPE_SHELLYPRO2PM_ROLLER_STR = "shellypro2pm-roller";
|
||||
public static final String THING_TYPE_SHELLYPRO3_STR = "shellypro3";
|
||||
@@ -164,8 +166,6 @@ public class ShellyThingCreator {
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY3EM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY3EM_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY2_RELAY = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLY2_RELAY_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY2_ROLLER = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLY2_ROLLER_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY25_RELAY = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLY25_RELAY_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY25_ROLLER = new ThingTypeUID(BINDING_ID,
|
||||
@@ -233,8 +233,6 @@ public class ShellyThingCreator {
|
||||
THING_TYPE_SHELLYPRO1PM_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPRO2_RELAY = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYPRO2_RELAY_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPRO2_ROLLER = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYPRO2_ROLLER_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPRO2PM_RELAY = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPRO2PM_ROLLER = new ThingTypeUID(BINDING_ID,
|
||||
@@ -277,6 +275,8 @@ public class ShellyThingCreator {
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUS1PMUL, THING_TYPE_SHELLYPLUS1PM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_RELAY, THING_TYPE_SHELLYPLUS2PM_RELAY_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_ROLLER, THING_TYPE_SHELLYPLUS2PM_ROLLER_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_RELAY_2, THING_TYPE_SHELLYPLUS2PM_RELAY_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUS2PM_ROLLER_2, THING_TYPE_SHELLYPLUS2PM_ROLLER_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUSPLUGUS, THING_TYPE_SHELLYPLUSPLUGUS_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUSI4DC, THING_TYPE_SHELLYPLUSI4DC_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PLUSI4, THING_TYPE_SHELLYPLUSI4_STR);
|
||||
@@ -285,16 +285,19 @@ public class ShellyThingCreator {
|
||||
// Pro Series
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1, THING_TYPE_SHELLYPRO1_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1_2, THING_TYPE_SHELLYPRO1_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1_3, THING_TYPE_SHELLYPRO1_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1PM, THING_TYPE_SHELLYPRO1PM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1PM_2, THING_TYPE_SHELLYPRO1PM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO1PM_3, THING_TYPE_SHELLYPRO1PM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_RELAY, THING_TYPE_SHELLYPRO2_RELAY_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_RELAY_2, THING_TYPE_SHELLYPRO2_RELAY_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_ROLLER, THING_TYPE_SHELLYPRO2_ROLLER_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_ROLLER_2, THING_TYPE_SHELLYPRO2_ROLLER_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2_RELAY_3, THING_TYPE_SHELLYPRO2_RELAY_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_RELAY, THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_RELAY_2, THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_RELAY_3, THING_TYPE_SHELLYPRO2PM_RELAY_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_ROLLER, THING_TYPE_SHELLYPRO2PM_ROLLER_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_ROLLER_2, THING_TYPE_SHELLYPRO2PM_ROLLER_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO2PM_ROLLER_3, THING_TYPE_SHELLYPRO2PM_ROLLER_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO3, THING_TYPE_SHELLYPRO3_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO4PM, THING_TYPE_SHELLYPRO4PM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_PRO4PM_2, THING_TYPE_SHELLYPRO4PM_STR);
|
||||
@@ -364,7 +367,7 @@ public class ShellyThingCreator {
|
||||
return mode.equals(SHELLY_MODE_RELAY) ? THING_TYPE_SHELLY25_RELAY_STR : THING_TYPE_SHELLY25_ROLLER_STR;
|
||||
}
|
||||
if (name.startsWith(THING_TYPE_SHELLY2_PREFIX)) { // Shelly v2
|
||||
return mode.equals(SHELLY_MODE_RELAY) ? THING_TYPE_SHELLY2_RELAY_STR : THING_TYPE_SHELLY2_ROLLER_STR;
|
||||
return THING_TYPE_SHELLY2_RELAY_STR;
|
||||
}
|
||||
if (name.startsWith(THING_TYPE_SHELLYPLUG_STR)) {
|
||||
// shellyplug-s needs to be mapped to shellyplugs to follow the schema
|
||||
|
||||
@@ -21,13 +21,9 @@ import static org.openhab.core.thing.Thing.*;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -39,19 +35,21 @@ import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiResult;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyFavPos;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyThermnostat;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1CoapHandler;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
||||
import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.discovery.ShellyThingCreator;
|
||||
import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
|
||||
import org.openhab.binding.shelly.internal.provider.ShellyStateDescriptionProvider;
|
||||
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyChannelCache;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyVersionDTO;
|
||||
@@ -66,7 +64,6 @@ import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
@@ -84,23 +81,11 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyBaseHandler extends BaseThingHandler
|
||||
implements ShellyDeviceListener, ShellyManagerInterface, ShellyThingInterface {
|
||||
private class OptionEntry {
|
||||
public ChannelTypeUID uid;
|
||||
public String key;
|
||||
public String value;
|
||||
|
||||
public OptionEntry(ChannelTypeUID uid, String key, String value) {
|
||||
this.uid = uid;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
public abstract class ShellyBaseHandler extends BaseThingHandler
|
||||
implements ShellyThingInterface, ShellyDeviceListener, ShellyManagerInterface {
|
||||
|
||||
protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class);
|
||||
protected final ShellyChannelDefinitions channelDefinitions;
|
||||
private final CopyOnWriteArrayList<OptionEntry> stateOptions = new CopyOnWriteArrayList<>();
|
||||
|
||||
public String thingName = "";
|
||||
public String thingType = "";
|
||||
@@ -111,28 +96,29 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
private ShellyBindingConfiguration bindingConfig;
|
||||
protected ShellyThingConfiguration config = new ShellyThingConfiguration();
|
||||
protected ShellyDeviceProfile profile = new ShellyDeviceProfile(); // init empty profile to avoid NPE
|
||||
protected ShellyDeviceStats stats = new ShellyDeviceStats();
|
||||
private final Shelly1CoapHandler coap;
|
||||
public boolean autoCoIoT = false;
|
||||
private ShellyDeviceStats stats = new ShellyDeviceStats();
|
||||
private @Nullable Shelly1CoapHandler coap;
|
||||
|
||||
private final ShellyTranslationProvider messages;
|
||||
protected boolean stopping = false;
|
||||
private final ShellyChannelCache cache;
|
||||
private final int cacheCount = UPDATE_SETTINGS_INTERVAL_SECONDS / UPDATE_STATUS_INTERVAL_SECONDS;
|
||||
|
||||
private final boolean gen2;
|
||||
protected boolean autoCoIoT = false;
|
||||
|
||||
// Thing status
|
||||
private boolean channelsCreated = false;
|
||||
private boolean stopping = false;
|
||||
private int vibrationFilter = 0;
|
||||
private String lastWakeupReason = "";
|
||||
|
||||
// Scheduler
|
||||
private long watchdog = now();
|
||||
|
||||
private @Nullable ScheduledFuture<?> statusJob;
|
||||
public int scheduledUpdates = 0;
|
||||
protected int scheduledUpdates = 0;
|
||||
private int skipCount = UPDATE_SKIP_COUNT;
|
||||
private int skipUpdate = 0;
|
||||
private boolean refreshSettings = false;
|
||||
|
||||
// delay before enabling channel
|
||||
private final int cacheCount = UPDATE_SETTINGS_INTERVAL_SECONDS / UPDATE_STATUS_INTERVAL_SECONDS;
|
||||
private final ShellyChannelCache cache;
|
||||
|
||||
private String lastWakeupReason = "";
|
||||
private int vibrationFilter = 0;
|
||||
private @Nullable ScheduledFuture<?> statusJob;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -155,26 +141,28 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
this.channelDefinitions = new ShellyChannelDefinitions(messages);
|
||||
this.bindingConfig = bindingConfig;
|
||||
this.config = getConfigAs(ShellyThingConfiguration.class);
|
||||
|
||||
this.httpClient = httpClient;
|
||||
this.api = new Shelly1HttpApi(thingName, config, httpClient);
|
||||
|
||||
coap = new Shelly1CoapHandler(this, coapServer);
|
||||
Map<String, String> properties = thing.getProperties();
|
||||
String gen = getString(properties.get(PROPERTY_DEV_GEN));
|
||||
String thingType = getThingType();
|
||||
if (gen.isEmpty() && thingType.startsWith("shellyplus") || thingType.startsWith("shellypro")) {
|
||||
gen = "2";
|
||||
}
|
||||
gen2 = "2".equals(gen);
|
||||
this.api = !gen2 ? new Shelly1HttpApi(thingName, this) : new Shelly2ApiRpc(thingName, thingTable, this);
|
||||
if (gen2) {
|
||||
config.eventsCoIoT = false;
|
||||
}
|
||||
if (config.eventsCoIoT) {
|
||||
this.coap = new Shelly1CoapHandler(this, coapServer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkRepresentation(String key) {
|
||||
return key.equalsIgnoreCase(getUID()) || key.equalsIgnoreCase(config.deviceIp)
|
||||
|| key.equalsIgnoreCase(config.serviceName) || key.equalsIgnoreCase(thing.getUID().getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Set.of(ShellyStateDescriptionProvider.class);
|
||||
}
|
||||
|
||||
public String getUID() {
|
||||
return getThing().getUID().getAsString();
|
||||
|| key.equalsIgnoreCase(config.serviceName) || key.equalsIgnoreCase(getThingName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,6 +187,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
start = initializeThing();
|
||||
} catch (ShellyApiException e) {
|
||||
ShellyApiResult res = e.getApiResult();
|
||||
if (profile.alwaysOn && e.isConnectionError()) {
|
||||
setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "offline.status-error-connect",
|
||||
e.toString());
|
||||
}
|
||||
if (isAuthorizationFailed(res)) {
|
||||
start = false;
|
||||
}
|
||||
@@ -255,22 +247,25 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
lastWakeupReason = "";
|
||||
cache.setThingName(thingName);
|
||||
cache.clear();
|
||||
resetStats();
|
||||
|
||||
logger.debug("{}: Start initializing thing {}, type {}, ip address {}, CoIoT: {}", thingName,
|
||||
getThing().getLabel(), thingType, config.deviceIp, config.eventsCoIoT);
|
||||
logger.debug("{}: Start initializing for thing {}, type {}, IP address {}, Gen2: {}, CoIoT: {}", thingName,
|
||||
getThing().getLabel(), thingType, config.deviceIp, gen2, config.eventsCoIoT);
|
||||
if (config.deviceIp.isEmpty()) {
|
||||
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "config-status.error.missing-device-ip");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup CoAP listener to we get the CoAP message, which triggers initialization even the thing could not be
|
||||
// fully initialized here. In this case the CoAP messages triggers auto-initialization (like the Action URL does
|
||||
// when enabled)
|
||||
if (config.eventsCoIoT && !profile.alwaysOn) {
|
||||
// Gen 1 only: Setup CoAP listener to we get the CoAP message, which triggers initialization even the thing
|
||||
// could not be fully initialized here. In this case the CoAP messages triggers auto-initialization (like the
|
||||
// Action URL does when enabled)
|
||||
if (coap != null && config.eventsCoIoT && !profile.alwaysOn) {
|
||||
coap.start(thingName, config);
|
||||
}
|
||||
|
||||
// Initialize API access, exceptions will be catched by initialize()
|
||||
api.initialize();
|
||||
profile.initFromThingType(thingType);
|
||||
ShellySettingsDevice devInfo = api.getDeviceInfo();
|
||||
if (getBool(devInfo.auth) && config.password.isEmpty()) {
|
||||
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials");
|
||||
@@ -281,8 +276,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
}
|
||||
|
||||
api.setConfig(thingName, config);
|
||||
api.initialize();
|
||||
ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType);
|
||||
tmpPrf.isGen2 = gen2;
|
||||
tmpPrf.auth = devInfo.auth; // missing in /settings
|
||||
|
||||
if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) {
|
||||
changeThingType(thingName, tmpPrf.mode);
|
||||
return false; // force re-initialization
|
||||
@@ -290,7 +287,8 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
// Validate device mode
|
||||
String reqMode = thingType.contains("-") ? substringAfter(thingType, "-") : "";
|
||||
if (!reqMode.isEmpty() && !tmpPrf.mode.equals(reqMode)) {
|
||||
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode");
|
||||
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode", tmpPrf.mode,
|
||||
reqMode);
|
||||
return false;
|
||||
}
|
||||
if (!getString(devInfo.coiot).isEmpty()) {
|
||||
@@ -311,82 +309,32 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
tmpPrf.updatePeriod = UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
|
||||
}
|
||||
|
||||
tmpPrf.auth = devInfo.auth; // missing in /settings
|
||||
tmpPrf.status = api.getStatus();
|
||||
tmpPrf.status = api.getStatus(); // update thing properties
|
||||
tmpPrf.updateFromStatus(tmpPrf.status);
|
||||
addStateOptions(tmpPrf);
|
||||
|
||||
if (tmpPrf.isTRV) {
|
||||
String[] profileNames = tmpPrf.getValveProfileList(0);
|
||||
String channelId = mkChannelId(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE);
|
||||
logger.debug("{}: Adding TRV profile names to channel description: {}", thingName, profileNames);
|
||||
clearStateOptions(channelId);
|
||||
addStateOption(channelId, "0", "DISABLED");
|
||||
for (int i = 0; i < profileNames.length; i++) {
|
||||
addStateOption(channelId, "" + (i + 1), profileNames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
showThingConfig(tmpPrf);
|
||||
// update thing properties
|
||||
updateProperties(tmpPrf, tmpPrf.status);
|
||||
checkVersion(tmpPrf, tmpPrf.status);
|
||||
|
||||
if (config.eventsCoIoT && (tmpPrf.settings.coiot != null) && (tmpPrf.settings.coiot.enabled != null)) {
|
||||
String devpeer = getString(tmpPrf.settings.coiot.peer);
|
||||
String ourpeer = config.localIp + ":" + Shelly1CoapJSonDTO.COIOT_PORT;
|
||||
if (!tmpPrf.settings.coiot.enabled || (profile.isMotion && devpeer.isEmpty())) {
|
||||
try {
|
||||
api.setCoIoTPeer(ourpeer);
|
||||
logger.info("{}: CoIoT peer updated to {}", thingName, ourpeer);
|
||||
} catch (ShellyApiException e) {
|
||||
logger.warn("{}: Unable to set CoIoT peer: {}", thingName, e.toString());
|
||||
}
|
||||
} else if (!devpeer.isEmpty() && !devpeer.equals(ourpeer)) {
|
||||
logger.warn("{}: CoIoT peer in device settings does not point this to this host", thingName);
|
||||
}
|
||||
}
|
||||
if (autoCoIoT) {
|
||||
logger.debug("{}: Auto-CoIoT is enabled, disabling action urls", thingName);
|
||||
config.eventsCoIoT = true;
|
||||
config.eventsSwitch = false;
|
||||
config.eventsButton = false;
|
||||
config.eventsPush = false;
|
||||
config.eventsRoller = false;
|
||||
config.eventsSensorReport = false;
|
||||
api.setConfig(thingName, config);
|
||||
startCoap(config, tmpPrf);
|
||||
if (!gen2) {
|
||||
api.setActionURLs(); // register event urls
|
||||
}
|
||||
|
||||
// All initialization done, so keep the profile and set Thing to ONLINE
|
||||
fillDeviceStatus(tmpPrf.status, false);
|
||||
postEvent(ALARM_TYPE_NONE, false);
|
||||
api.setActionURLs(); // register event urls
|
||||
if (config.eventsCoIoT) {
|
||||
logger.debug("{}: Starting CoIoT (autoCoIoT={}/{})", thingName, bindingConfig.autoCoIoT, autoCoIoT);
|
||||
coap.start(thingName, config);
|
||||
}
|
||||
|
||||
profile = tmpPrf;
|
||||
showThingConfig(profile);
|
||||
|
||||
logger.debug("{}: Thing successfully initialized.", thingName);
|
||||
profile = tmpPrf;
|
||||
|
||||
updateProperties(tmpPrf, tmpPrf.status);
|
||||
updateProperties(profile, profile.status);
|
||||
setThingOnline(); // if API call was successful the thing must be online
|
||||
return true; // success
|
||||
}
|
||||
|
||||
private void showThingConfig(ShellyDeviceProfile profile) {
|
||||
logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {}", thingName,
|
||||
profile.hostname, profile.deviceType, profile.hwRev, profile.hwBatchId, profile.fwVersion,
|
||||
profile.fwDate);
|
||||
logger.debug("{}: Shelly settings info for {}: {}", thingName, profile.hostname, profile.settingsJson);
|
||||
logger.debug("{}: Device "
|
||||
+ "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{})"
|
||||
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}"
|
||||
+ ",alwaysOn:{}, updatePeriod:{}sec", thingName, profile.hasRelays, profile.numRelays, profile.isRoller,
|
||||
profile.numRollers, profile.isDimmer, profile.numMeters, profile.isEMeter, profile.isSensor,
|
||||
profile.isDW, profile.hasBattery,
|
||||
profile.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "", profile.isSense,
|
||||
profile.isMotion, profile.isLight, profile.isBulb, profile.isDuo, profile.isRGBW2, profile.inColor,
|
||||
profile.alwaysOn, profile.updatePeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Channel Commands
|
||||
*/
|
||||
@@ -443,17 +391,31 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
break;
|
||||
case CHANNEL_CONTROL_PROFILE:
|
||||
logger.debug("{}: Select profile {}", thingName, command);
|
||||
String cmd = command.toString();
|
||||
int id = Integer.parseInt(cmd);
|
||||
int id = -1;
|
||||
if (command instanceof Number) {
|
||||
id = (int) getNumber(command);
|
||||
} else {
|
||||
String cmd = command.toString();
|
||||
if (isDigit(cmd.charAt(0))) {
|
||||
id = Integer.parseInt(cmd);
|
||||
} else if (profile.settings.thermostats != null) {
|
||||
ShellyThermnostat t = profile.settings.thermostats.get(0);
|
||||
for (int i = 0; i < t.profileNames.length; i++) {
|
||||
if (t.profileNames[i].equalsIgnoreCase(cmd)) {
|
||||
id = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (id < 0 || id > 5) {
|
||||
logger.warn("{}: Invalid profile Id {} requested", thingName, profile);
|
||||
} else {
|
||||
api.setValveProfile(0, id);
|
||||
break;
|
||||
}
|
||||
api.setValveProfile(0, id);
|
||||
break;
|
||||
case CHANNEL_CONTROL_MODE:
|
||||
logger.debug("{}: Set mode to {}", thingName, command);
|
||||
api.setValveMode(0, SHELLY_TRV_MODE_AUTO.equalsIgnoreCase(command.toString()));
|
||||
api.setValveMode(0, CHANNEL_CONTROL_MODE.equalsIgnoreCase(command.toString()));
|
||||
break;
|
||||
case CHANNEL_CONTROL_SETTEMP:
|
||||
logger.debug("{}: Set temperature to {}", thingName, command);
|
||||
@@ -519,7 +481,7 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
|
||||
if (vibrationFilter > 0) {
|
||||
vibrationFilter--;
|
||||
logger.debug("{}: Vibration events are absorbed for {} more seconds", thingName,
|
||||
logger.debug("{}: Vibration events are absorbed for {} more seconds", thingName,
|
||||
vibrationFilter * UPDATE_STATUS_INTERVAL_SECONDS);
|
||||
}
|
||||
|
||||
@@ -533,6 +495,9 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
}
|
||||
// Get profile, if refreshSettings == true reload settings from device
|
||||
ShellySettingsStatus status = api.getStatus();
|
||||
if (status.uptime != null && status.uptime == 0 && profile.alwaysOn) {
|
||||
status = api.getStatus();
|
||||
}
|
||||
boolean restarted = checkRestarted(status);
|
||||
profile = getProfile(refreshSettings || restarted);
|
||||
profile.status = status;
|
||||
@@ -563,7 +528,11 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
// sleep mode. Once the next update is successful the device goes back online
|
||||
String status = "";
|
||||
ShellyApiResult res = e.getApiResult();
|
||||
if (isWatchdogStarted()) {
|
||||
if (profile.alwaysOn && e.isConnectionError()) {
|
||||
status = "offline.status-error-connect";
|
||||
} else if (res.isHttpAccessUnauthorized()) {
|
||||
status = "offline.conf-error-access-denied";
|
||||
} else if (isWatchdogStarted()) {
|
||||
if (!isWatchdogExpired()) {
|
||||
logger.debug("{}: Ignore API Timeout, retry later", thingName);
|
||||
} else {
|
||||
@@ -571,8 +540,6 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
status = "offline.status-error-watchdog";
|
||||
}
|
||||
}
|
||||
} else if (res.isHttpAccessUnauthorized()) {
|
||||
status = "offline.conf-error-access-denied";
|
||||
} else if (e.isJSONException()) {
|
||||
status = "offline.status-error-unexpected-api-result";
|
||||
logger.debug("{}: Unable to parse API response: {}; json={}", thingName, res.getUrl(), res.response, e);
|
||||
@@ -603,31 +570,73 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
}
|
||||
}
|
||||
|
||||
private void showThingConfig(ShellyDeviceProfile profile) {
|
||||
logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {}", thingName,
|
||||
profile.hostname, profile.deviceType, profile.hwRev, profile.hwBatchId, profile.fwVersion,
|
||||
profile.fwDate);
|
||||
logger.debug("{}: Shelly settings info for {}: {}", thingName, profile.hostname, profile.settingsJson);
|
||||
logger.debug("{}: Device "
|
||||
+ "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{})"
|
||||
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}"
|
||||
+ ",alwaysOn:{}, updatePeriod:{}sec", thingName, profile.hasRelays, profile.numRelays, profile.isRoller,
|
||||
profile.numRollers, profile.isDimmer, profile.numMeters, profile.isEMeter, profile.isSensor,
|
||||
profile.isDW, profile.hasBattery,
|
||||
profile.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "", profile.isSense,
|
||||
profile.isMotion, profile.isLight, profile.isBulb, profile.isDuo, profile.isRGBW2, profile.inColor,
|
||||
profile.alwaysOn, profile.updatePeriod);
|
||||
}
|
||||
|
||||
private void addStateOptions(ShellyDeviceProfile prf) {
|
||||
if (prf.isTRV) {
|
||||
String[] profileNames = prf.getValveProfileList(0);
|
||||
String channelId = mkChannelId(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE);
|
||||
logger.debug("{}: Adding TRV profile names to channel description: {}", thingName, profileNames);
|
||||
channelDefinitions.clearStateOptions(channelId);
|
||||
int fid = 1;
|
||||
for (String name : profileNames) {
|
||||
channelDefinitions.addStateOption(channelId, "" + fid, fid + ": " + name);
|
||||
fid++;
|
||||
}
|
||||
}
|
||||
if (prf.isRoller && prf.settings.favorites != null) {
|
||||
String channelId = mkChannelId(CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_FAV);
|
||||
logger.debug("{}: Adding {} roler favorite(s) to channel description", thingName,
|
||||
prf.settings.favorites.size());
|
||||
channelDefinitions.clearStateOptions(channelId);
|
||||
int fid = 1;
|
||||
for (ShellyFavPos fav : prf.settings.favorites) {
|
||||
channelDefinitions.addStateOption(channelId, "" + fid, fid + ": " + fav.name);
|
||||
fid++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getThingType() {
|
||||
return thing.getThingTypeUID().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingStatus getThingStatus() {
|
||||
return getThing().getStatus();
|
||||
return thing.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingStatusDetail getThingStatusDetail() {
|
||||
return getThing().getStatusInfo().getStatusDetail();
|
||||
return thing.getStatusInfo().getStatusDetail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isThingOnline() {
|
||||
return getThing().getStatus() == ThingStatus.ONLINE;
|
||||
return getThingStatus() == ThingStatus.ONLINE;
|
||||
}
|
||||
|
||||
public boolean isThingOffline() {
|
||||
return getThing().getStatus() == ThingStatus.OFFLINE;
|
||||
return getThingStatus() == ThingStatus.OFFLINE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingOnline() {
|
||||
if (stopping) {
|
||||
logger.debug("{}: Thing should go ONLINE, but handler is shutting down, ignore!", thingName);
|
||||
return;
|
||||
}
|
||||
if (!isThingOnline()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
@@ -640,16 +649,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey) {
|
||||
String message = messages.get(messageKey);
|
||||
if (stopping) {
|
||||
logger.debug("{}: Thing should go OFFLINE with status {}, but handler is shutting down -> ignore",
|
||||
thingName, message);
|
||||
return;
|
||||
}
|
||||
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments) {
|
||||
if (!isThingOffline()) {
|
||||
updateStatus(ThingStatus.OFFLINE, detail, message);
|
||||
updateStatus(ThingStatus.OFFLINE, detail, messages.get(messageKey, arguments));
|
||||
api.close(); // Gen2: disconnect WS/close http sessions
|
||||
watchdog = 0;
|
||||
channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new
|
||||
}
|
||||
@@ -723,15 +726,22 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
if (status.uptime != null) {
|
||||
stats.lastUptime = getLong(status.uptime);
|
||||
}
|
||||
if (coap != null) {
|
||||
stats.coiotMessages = coap.getMessageCount();
|
||||
stats.coiotErrors = coap.getErrorCount();
|
||||
}
|
||||
|
||||
if (!alarm.isEmpty()) {
|
||||
postEvent(alarm, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incProtMessages() {
|
||||
stats.protocolMessages++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incProtErrors() {
|
||||
stats.protocolErrors++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device has restarted and needs a new Thing initialization
|
||||
*
|
||||
@@ -741,8 +751,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
private boolean checkRestarted(ShellySettingsStatus status) {
|
||||
if (profile.isInitialized() && profile.alwaysOn /* exclude battery powered devices */
|
||||
&& (status.uptime != null && status.uptime < stats.lastUptime
|
||||
|| !profile.status.update.oldVersion.isEmpty()
|
||||
&& !status.update.oldVersion.equals(profile.status.update.oldVersion))) {
|
||||
|| (!profile.status.update.oldVersion.isEmpty()
|
||||
&& !status.update.oldVersion.equals(profile.status.update.oldVersion)))) {
|
||||
logger.debug("{}: Device has been restarted, uptime={}/{}, firmware={}/{}", thingName, stats.lastUptime,
|
||||
getLong(status.uptime), profile.status.update.oldVersion, status.update.oldVersion);
|
||||
updateProperties(profile, status);
|
||||
return true;
|
||||
}
|
||||
@@ -785,6 +797,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUpdateScheduled() {
|
||||
return scheduledUpdates > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for device events
|
||||
*
|
||||
@@ -980,12 +996,13 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
if (version.checkBeta(getString(prf.fwVersion))) {
|
||||
logger.info("{}: {}", prf.hostname, messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate));
|
||||
} else {
|
||||
if ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWVERSION) < 0) && !profile.isMotion) {
|
||||
String minVersion = !gen2 ? SHELLY_API_MIN_FWVERSION : SHELLY2_API_MIN_FWVERSION;
|
||||
if (version.compare(prf.fwVersion, minVersion) < 0) {
|
||||
logger.warn("{}: {}", prf.hostname,
|
||||
messages.get("versioncheck.tooold", prf.fwVersion, prf.fwDate, SHELLY_API_MIN_FWVERSION));
|
||||
messages.get("versioncheck.tooold", prf.fwVersion, prf.fwDate, minVersion));
|
||||
}
|
||||
}
|
||||
if (bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0)
|
||||
if (!gen2 && bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0)
|
||||
|| (prf.fwVersion.equalsIgnoreCase("production_test"))) {
|
||||
if (!config.eventsCoIoT) {
|
||||
logger.info("{}: {}", thingName, messages.get("versioncheck.autocoiot"));
|
||||
@@ -1001,6 +1018,50 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
}
|
||||
}
|
||||
|
||||
public String checkForUpdate() {
|
||||
try {
|
||||
ShellyOtaCheckResult result = api.checkForUpdate();
|
||||
return result.status;
|
||||
} catch (ShellyApiException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public void startCoap(ShellyThingConfiguration config, ShellyDeviceProfile profile) throws ShellyApiException {
|
||||
if (coap == null || !config.eventsCoIoT) {
|
||||
return;
|
||||
}
|
||||
if (profile.settings.coiot != null && profile.settings.coiot.enabled != null) {
|
||||
String devpeer = getString(profile.settings.coiot.peer);
|
||||
String ourpeer = config.localIp + ":" + Shelly1CoapJSonDTO.COIOT_PORT;
|
||||
if (!profile.settings.coiot.enabled || (profile.isMotion && devpeer.isEmpty())) {
|
||||
try {
|
||||
api.setCoIoTPeer(ourpeer);
|
||||
logger.info("{}: CoIoT peer updated to {}", thingName, ourpeer);
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Unable to set CoIoT peer: {}", thingName, e.toString());
|
||||
}
|
||||
} else if (!devpeer.isEmpty() && !devpeer.equals(ourpeer)) {
|
||||
logger.warn("{}: CoIoT peer in device settings does not point this to this host", thingName);
|
||||
}
|
||||
}
|
||||
if (autoCoIoT) {
|
||||
logger.debug("{}: Auto-CoIoT is enabled, disabling action urls", thingName);
|
||||
config.eventsCoIoT = true;
|
||||
config.eventsSwitch = false;
|
||||
config.eventsButton = false;
|
||||
config.eventsPush = false;
|
||||
config.eventsRoller = false;
|
||||
config.eventsSensorReport = false;
|
||||
api.setConfig(thingName, config);
|
||||
}
|
||||
|
||||
logger.debug("{}: Starting CoIoT (autoCoIoT={}/{})", thingName, bindingConfig.autoCoIoT, autoCoIoT);
|
||||
if (coap != null) {
|
||||
coap.start(thingName, config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the http response for authorization error.
|
||||
* If the authorization failed the binding can't access the device settings and determine the thing type. In this
|
||||
@@ -1081,10 +1142,6 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isUpdateScheduled() {
|
||||
return scheduledUpdates > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map input states to channels
|
||||
*
|
||||
@@ -1098,17 +1155,15 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
boolean updated = false;
|
||||
|
||||
if (status.inputs != null) {
|
||||
if (!areChannelsCreated()) {
|
||||
updateChannelDefinitions(ShellyChannelDefinitions.createInputChannels(thing, profile, status));
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
boolean multiInput = status.inputs.size() >= 2; // device has multiple SW (inputs)
|
||||
boolean multiInput = !profile.isIX && status.inputs.size() >= 2; // device has multiple SW (inputs)
|
||||
for (ShellyInputState input : status.inputs) {
|
||||
String group = profile.getInputGroup(idx);
|
||||
String suffix = multiInput ? profile.getInputSuffix(idx) : "";
|
||||
|
||||
if (!areChannelsCreated()) {
|
||||
updateChannelDefinitions(
|
||||
ShellyChannelDefinitions.createInputChannels(thing, profile, status, group));
|
||||
}
|
||||
|
||||
updated |= updateChannel(group, CHANNEL_INPUT + suffix, getOnOff(input.input));
|
||||
if (input.event != null) {
|
||||
updated |= updateChannel(group, CHANNEL_STATUS_EVENTTYPE + suffix, getStringType(input.event));
|
||||
@@ -1248,12 +1303,14 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
*/
|
||||
public void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus status) {
|
||||
Map<String, Object> properties = fillDeviceProperties(profile);
|
||||
properties.put(PROPERTY_SERVICE_NAME, config.serviceName);
|
||||
String deviceName = getString(profile.settings.name);
|
||||
properties.put(PROPERTY_SERVICE_NAME, config.serviceName);
|
||||
properties.put(PROPERTY_DEV_GEN, "1");
|
||||
if (!deviceName.isEmpty()) {
|
||||
properties.put(PROPERTY_DEV_NAME, deviceName);
|
||||
}
|
||||
properties.put(PROPERTY_DEV_GEN, !profile.isGen2 ? "1" : "2");
|
||||
|
||||
// add status properties
|
||||
if (status.wifiSta != null) {
|
||||
@@ -1372,32 +1429,13 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StateOption> getStateOptions(ChannelTypeUID uid) {
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
for (OptionEntry oe : stateOptions) {
|
||||
if (oe.uid.equals(uid)) {
|
||||
options.add(new StateOption(oe.key, oe.value));
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable List<StateOption> getStateOptions(ChannelTypeUID uid) {
|
||||
List<StateOption> options = channelDefinitions.getStateOptions(uid);
|
||||
if (!options.isEmpty()) {
|
||||
logger.debug("{}: Return {} state options for channel uid {}", thingName, options.size(), uid.getId());
|
||||
return options;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private void addStateOption(String channelId, String key, String value) {
|
||||
ChannelTypeUID uid = channelDefinitions.getChannelTypeUID(channelId);
|
||||
stateOptions.addIfAbsent(new OptionEntry(uid, key, value));
|
||||
}
|
||||
|
||||
private void clearStateOptions(String channelId) {
|
||||
ChannelTypeUID uid = channelDefinitions.getChannelTypeUID(channelId);
|
||||
for (OptionEntry oe : stateOptions) {
|
||||
if (oe.uid.equals(uid)) {
|
||||
stateOptions.remove(oe);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected ShellyDeviceProfile getDeviceProfile() {
|
||||
@@ -1431,10 +1469,7 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
statusJob = null;
|
||||
logger.debug("{}: Shelly statusJob stopped", thingName);
|
||||
}
|
||||
|
||||
if (coap != null) {
|
||||
coap.stop();
|
||||
}
|
||||
api.close();
|
||||
profile.initialized = false;
|
||||
}
|
||||
|
||||
@@ -1443,6 +1478,7 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("{}: Stopping Thing", thingName);
|
||||
stopping = true;
|
||||
stop();
|
||||
super.dispose();
|
||||
@@ -1455,6 +1491,10 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getUID() {
|
||||
return getThing().getUID().getAsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Device specific handlers are overriding this method to do additional stuff
|
||||
*/
|
||||
@@ -1483,22 +1523,13 @@ public class ShellyBaseHandler extends BaseThingHandler
|
||||
return api;
|
||||
}
|
||||
|
||||
public Map<String, String> getStatsProp() {
|
||||
return stats.asProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getScheduledUpdates() {
|
||||
return scheduledUpdates;
|
||||
}
|
||||
|
||||
public String checkForUpdate() {
|
||||
try {
|
||||
ShellyOtaCheckResult result = api.checkForUpdate();
|
||||
return result.status;
|
||||
} catch (ShellyApiException e) {
|
||||
return "";
|
||||
}
|
||||
public Map<String, String> getStatsProp() {
|
||||
return stats.asProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,8 +33,8 @@ public class ShellyDeviceStats {
|
||||
public long alarms = 0;
|
||||
public String lastAlarm = "";
|
||||
public long lastAlarmTs = 0;
|
||||
public long coiotMessages = 0;
|
||||
public long coiotErrors = 0;
|
||||
public long protocolMessages = 0;
|
||||
public long protocolErrors = 0;
|
||||
public int wifiRssi = 0;
|
||||
public int maxInternalTemp = 0;
|
||||
|
||||
@@ -48,8 +48,8 @@ public class ShellyDeviceStats {
|
||||
prop.put("alarmCount", String.valueOf(alarms));
|
||||
prop.put("lastAlarm", lastAlarm);
|
||||
prop.put("lastAlarmTs", ShellyUtils.convertTimestamp(lastAlarmTs));
|
||||
prop.put("coiotMessages", String.valueOf(coiotMessages));
|
||||
prop.put("coiotErrors", String.valueOf(coiotErrors));
|
||||
prop.put("protocolMessages", String.valueOf(protocolMessages));
|
||||
prop.put("protocolErrors", String.valueOf(protocolErrors));
|
||||
prop.put("wifiRssi", String.valueOf(wifiRssi));
|
||||
return prop;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,11 @@ public interface ShellyManagerInterface {
|
||||
|
||||
public void setThingOnline();
|
||||
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey);
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
|
||||
|
||||
public boolean requestUpdates(int requestCount, boolean refreshSettings);
|
||||
|
||||
public void incProtMessages();
|
||||
|
||||
public void incProtErrors();
|
||||
}
|
||||
|
||||
@@ -111,7 +111,9 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
channelUID.getIdWithoutGroup().equals(CHANNEL_ROL_CONTROL_CONTROL));
|
||||
|
||||
// request updates the next 45sec to update roller position after it stopped
|
||||
requestUpdates(autoCoIoT ? 1 : 45 / UPDATE_STATUS_INTERVAL_SECONDS, false);
|
||||
if (!autoCoIoT && !profile.isGen2) {
|
||||
requestUpdates(45 / UPDATE_STATUS_INTERVAL_SECONDS, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case CHANNEL_ROL_CONTROL_FAV:
|
||||
@@ -129,11 +131,11 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
|
||||
case CHANNEL_TIMER_AUTOON:
|
||||
logger.debug("{}: Set Auto-ON timer to {}", thingName, command);
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command).intValue());
|
||||
api.setAutoTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command).doubleValue());
|
||||
break;
|
||||
case CHANNEL_TIMER_AUTOOFF:
|
||||
logger.debug("{}: Set Auto-OFF timer to {}", thingName, command);
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOOFF, getNumber(command).intValue());
|
||||
api.setAutoTimer(rIndex, SHELLY_TIMER_AUTOOFF, getNumber(command).doubleValue());
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
@@ -241,7 +243,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
position = shpos;
|
||||
} else {
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
|
||||
position = SHELLY_MIN_ROLLER_POS;
|
||||
}
|
||||
} else if (command == UpDownType.DOWN || command == OnOffType.OFF
|
||||
|| ((command instanceof DecimalType) && (((DecimalType) command).intValue() == 0))) {
|
||||
@@ -255,7 +256,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
position = shpos;
|
||||
} else {
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
|
||||
position = SHELLY_MAX_ROLLER_POS;
|
||||
}
|
||||
}
|
||||
} else if (command == StopMoveType.STOP) {
|
||||
@@ -283,18 +283,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
logger.debug("{}: Changing roller position to {}", thingName, position);
|
||||
api.setRollerPos(index, position);
|
||||
}
|
||||
|
||||
if (position != -1) {
|
||||
// make sure both are in sync
|
||||
if (isControl) {
|
||||
int pos = SHELLY_MAX_ROLLER_POS - Math.max(0, Math.min(position, SHELLY_MAX_ROLLER_POS));
|
||||
logger.debug("{}: Set roller position for control channel to {}", thingName, pos);
|
||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL, new PercentType(pos));
|
||||
} else {
|
||||
logger.debug("{}: Set roller position channel to {}", thingName, position);
|
||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_POS, new PercentType(position));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,7 +317,6 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
*/
|
||||
public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
|
||||
if (profile.hasRelays && !profile.isDimmer) {
|
||||
double voltage = -1;
|
||||
if (status.voltage == null && profile.settings.supplyVoltage != null) {
|
||||
@@ -343,25 +330,23 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_VOLTAGE,
|
||||
toQuantityType(voltage, DIGITS_VOLT, Units.VOLT));
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.hasRelays && !profile.isRoller) {
|
||||
logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays);
|
||||
for (int i = 0; i < status.relays.size(); i++) {
|
||||
createRelayChannels(status.relays.get(i), i);
|
||||
updated |= ShellyComponents.updateRelay(this, status, i);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
// Check for Relay in Roller Mode
|
||||
logger.trace("{}: Updating {} rollers", thingName, profile.numRollers);
|
||||
for (int i = 0; i < profile.numRollers; i++) {
|
||||
ShellyRollerStatus roller = status.rollers.get(i);
|
||||
createRollerChannels(roller);
|
||||
updated |= ShellyComponents.updateRoller(this, roller, i);
|
||||
if (!profile.isRoller) {
|
||||
logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays);
|
||||
for (int i = 0; i < status.relays.size(); i++) {
|
||||
createRelayChannels(status.relays.get(i), i);
|
||||
updated |= ShellyComponents.updateRelay(this, status, i);
|
||||
}
|
||||
} else {
|
||||
// Check for Relay in Roller Mode
|
||||
logger.trace("{}: Updating {} rollers", thingName, profile.numRollers);
|
||||
for (int i = 0; i < profile.numRollers; i++) {
|
||||
ShellyRollerStatus roller = status.rollers.get(i);
|
||||
createRollerChannels(roller);
|
||||
updated |= ShellyComponents.updateRoller(this, roller, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public interface ShellyThingInterface {
|
||||
|
||||
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
|
||||
|
||||
public List<StateOption> getStateOptions(ChannelTypeUID uid);
|
||||
public @Nullable List<StateOption> getStateOptions(ChannelTypeUID uid);
|
||||
|
||||
public double getChannelDouble(String group, String channel);
|
||||
|
||||
@@ -51,7 +51,9 @@ public interface ShellyThingInterface {
|
||||
|
||||
public void setThingOnline();
|
||||
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey);
|
||||
public void setThingOffline(ThingStatusDetail detail, String messageKey, Object... arguments);
|
||||
|
||||
public String getThingType();
|
||||
|
||||
public ThingStatus getThingStatus();
|
||||
|
||||
@@ -110,4 +112,8 @@ public interface ShellyThingInterface {
|
||||
public void fillDeviceStatus(ShellySettingsStatus status, boolean updated);
|
||||
|
||||
public boolean checkRepresentation(String key);
|
||||
|
||||
public void incProtMessages();
|
||||
|
||||
public void incProtErrors();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ public class ShellyThingTable {
|
||||
private Map<String, ShellyThingInterface> thingTable = new ConcurrentHashMap<>();
|
||||
|
||||
public void addThing(String key, ShellyThingInterface thing) {
|
||||
if (thingTable.containsKey(key)) {
|
||||
thingTable.remove(key);
|
||||
}
|
||||
thingTable.put(key, thing);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
|
||||
}
|
||||
|
||||
String peer = getString(profile.settings.coiot.peer);
|
||||
boolean mcast = peer.isEmpty() || SHELLY_COIOT_MCAST.equalsIgnoreCase(peer);
|
||||
boolean mcast = peer.isEmpty() || SHELLY_COIOT_MCAST.equalsIgnoreCase(peer) || profile.isMotion;
|
||||
String newPeer = mcast ? localIp + ":" + Shelly1CoapJSonDTO.COIOT_PORT : SHELLY_COIOT_MCAST;
|
||||
String displayPeer = mcast ? newPeer : "Multicast";
|
||||
|
||||
@@ -252,7 +252,6 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
|
||||
refreshTimer = 3;
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTION_ENAPROAMING:
|
||||
case ACTION_DISAPROAMING:
|
||||
enable = ACTION_ENAPROAMING.equalsIgnoreCase(action);
|
||||
@@ -269,6 +268,77 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
|
||||
refreshTimer = 3;
|
||||
}
|
||||
break;
|
||||
case ACTION_ENRANGEEXT:
|
||||
case ACTION_DISRANGEEXT:
|
||||
enable = ACTION_ENRANGEEXT.equalsIgnoreCase(action);
|
||||
if (!"yes".equalsIgnoreCase(update)) {
|
||||
message = getMessage(
|
||||
enable ? "action.setwifirangeext-enable" : "action.setwifirangeext-disable");
|
||||
actionUrl = buildActionUrl(uid, action);
|
||||
} else {
|
||||
try {
|
||||
boolean res = api.setWiFiRangeExtender(enable);
|
||||
if (res) {
|
||||
message = getMessageP("action.restart.info", MCINFO);
|
||||
actionButtonLabel = "Ok";
|
||||
actionUrl = buildActionUrl(uid, ACTION_RESTART);
|
||||
} else {
|
||||
message = getMessage("action.setwifirangeext-confirm", res ? "yes" : "no");
|
||||
refreshTimer = 5;
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
message = getMessage("action.setwifirangeext-failed", e.toString());
|
||||
refreshTimer = 10;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTION_ENETHERNET:
|
||||
case ACTION_DISETHERNET:
|
||||
enable = ACTION_ENETHERNET.equalsIgnoreCase(action);
|
||||
if (!"yes".equalsIgnoreCase(update)) {
|
||||
message = getMessage(enable ? "action.setethernet-enable" : "action.setethernet-disable");
|
||||
actionUrl = buildActionUrl(uid, action);
|
||||
} else {
|
||||
try {
|
||||
boolean res = api.setEthernet(enable);
|
||||
if (res) {
|
||||
message = getMessageP("action.restart.info", MCINFO);
|
||||
actionButtonLabel = "Ok";
|
||||
actionUrl = buildActionUrl(uid, ACTION_RESTART);
|
||||
} else {
|
||||
message = getMessage("action.setethernet-confirm", res ? "yes" : "no");
|
||||
refreshTimer = 5;
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
message = getMessage("action.setethernet-failed", e.toString());
|
||||
refreshTimer = 10;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ACTION_ENBLUETOOTH:
|
||||
case ACTION_DISBLUETOOTH:
|
||||
enable = ACTION_ENBLUETOOTH.equalsIgnoreCase(action);
|
||||
if (!"yes".equalsIgnoreCase(update)) {
|
||||
message = getMessage(enable ? "action.setbluetooth-enable" : "action.setbluetooth-disable");
|
||||
actionUrl = buildActionUrl(uid, action);
|
||||
} else {
|
||||
try {
|
||||
boolean res = api.setBluetooth(enable);
|
||||
if (res) {
|
||||
message = getMessageP("action.restart.info", MCINFO);
|
||||
actionButtonLabel = "Ok";
|
||||
actionUrl = buildActionUrl(uid, ACTION_RESTART);
|
||||
} else {
|
||||
message = getMessage("action.setbluetooth-confirm", res ? "yes" : "no");
|
||||
refreshTimer = 5;
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
message = getMessage("action.setbluetooth-failed", e.toString());
|
||||
refreshTimer = 10;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ACTION_GETDEB:
|
||||
case ACTION_GETDEB1:
|
||||
@@ -303,35 +373,51 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
|
||||
|
||||
public static Map<String, String> getActions(ShellyDeviceProfile profile) {
|
||||
Map<String, String> list = new LinkedHashMap<>();
|
||||
boolean gen2 = profile.isGen2;
|
||||
|
||||
list.put(ACTION_RES_STATS, "Reset Statistics");
|
||||
list.put(ACTION_RESTART, "Reboot Device");
|
||||
list.put(ACTION_PROTECT, "Protect Device");
|
||||
if (gen2) {
|
||||
list.put(ACTION_PROTECT, "Protect Device");
|
||||
}
|
||||
|
||||
if ((profile.settings.coiot != null) && (profile.settings.coiot.peer != null) && !profile.isMotion) {
|
||||
if ((profile.settings.coiot != null) && profile.settings.coiot.peer != null) {
|
||||
boolean mcast = profile.settings.coiot.peer.isEmpty()
|
||||
|| SHELLY_COIOT_MCAST.equalsIgnoreCase(profile.settings.coiot.peer);
|
||||
|| SHELLY_COIOT_MCAST.equalsIgnoreCase(profile.settings.coiot.peer) || profile.isMotion;
|
||||
list.put(mcast ? ACTION_SETCOIOT_PEER : ACTION_SETCOIOT_MCAST,
|
||||
mcast ? "Set CoIoT Peer Mode" : "Set CoIoT Multicast Mode");
|
||||
}
|
||||
if (profile.isSensor && !profile.isMotion && (profile.settings.wifiSta != null)
|
||||
if (profile.isSensor && !profile.isMotion && profile.settings.wifiSta != null
|
||||
&& profile.settings.wifiSta.enabled) {
|
||||
// FW 1.10+: Reset STA list, force WiFi rescan and connect to stringest AP
|
||||
list.put(ACTION_RESSTA, "Reconnect WiFi");
|
||||
}
|
||||
if (profile.settings.apRoaming != null) {
|
||||
if (!gen2 && profile.settings.apRoaming != null) {
|
||||
list.put(!profile.settings.apRoaming.enabled ? ACTION_ENAPROAMING : ACTION_DISAPROAMING,
|
||||
!profile.settings.apRoaming.enabled ? "Enable WiFi Roaming" : "Disable WiFi Roaming");
|
||||
}
|
||||
if (profile.settings.wifiRecoveryReboot != null) {
|
||||
if (!gen2 && profile.settings.wifiRecoveryReboot != null) {
|
||||
list.put(!profile.settings.wifiRecoveryReboot ? ACTION_ENWIFIREC : ACTION_DISWIFIREC,
|
||||
!profile.settings.wifiRecoveryReboot ? "Enable WiFi Recovery" : "Disable WiFi Recovery");
|
||||
}
|
||||
if (profile.settings.wifiAp != null && profile.settings.wifiAp.rangeExtender != null) {
|
||||
list.put(!profile.settings.wifiAp.rangeExtender ? ACTION_ENRANGEEXT : ACTION_DISRANGEEXT,
|
||||
!profile.settings.wifiAp.rangeExtender ? "Enable Range Extender" : "Disable Range Extender");
|
||||
}
|
||||
if (profile.settings.ethernet != null) {
|
||||
list.put(!profile.settings.ethernet ? ACTION_ENETHERNET : ACTION_DISETHERNET,
|
||||
!profile.settings.ethernet ? "Enable Ethernet" : "Disable Ethernet");
|
||||
}
|
||||
if (profile.settings.bluetooth != null) {
|
||||
list.put(!profile.settings.bluetooth ? ACTION_ENBLUETOOTH : ACTION_DISBLUETOOTH,
|
||||
!profile.settings.bluetooth ? "Enable Bluetooth" : "Disable Bluetooth");
|
||||
}
|
||||
|
||||
boolean set = profile.settings.cloud != null && profile.settings.cloud.enabled;
|
||||
list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud");
|
||||
|
||||
list.put(ACTION_RESET, "-Factory Reset");
|
||||
if (profile.extFeatures) {
|
||||
if (!gen2 && profile.extFeatures) {
|
||||
list.put(ACTION_OTACHECK, "Check for Update");
|
||||
boolean debug_enable = getBool(profile.settings.debugEnable);
|
||||
list.put(!debug_enable ? ACTION_ENDEBUG : ACTION_DISDEBUG,
|
||||
|
||||
@@ -39,7 +39,6 @@ public class ShellyManagerConstants {
|
||||
public static final String ACTION_SETCOIOT_PEER = "setcoiotpeer";
|
||||
public static final String ACTION_SETCOIOT_MCAST = "setcoiotmcast";
|
||||
public static final String ACTION_SETTZ = "settz";
|
||||
public static final String ACTION_SETNTP = "setntp";
|
||||
public static final String ACTION_ENCLOUD = "encloud";
|
||||
public static final String ACTION_DISCLOUD = "discloud";
|
||||
public static final String ACTION_RES_STATS = "reset_stat";
|
||||
@@ -49,6 +48,12 @@ public class ShellyManagerConstants {
|
||||
public static final String ACTION_DISWIFIREC = "diswifirec";
|
||||
public static final String ACTION_ENAPROAMING = "enaproaming";
|
||||
public static final String ACTION_DISAPROAMING = "disaproaming";
|
||||
public static final String ACTION_ENRANGEEXT = "enrangeext";
|
||||
public static final String ACTION_ENETHERNET = "enethernet";
|
||||
public static final String ACTION_DISETHERNET = "disethernet";
|
||||
public static final String ACTION_ENBLUETOOTH = "enbluetooth";
|
||||
public static final String ACTION_DISBLUETOOTH = "disbluetooth";
|
||||
public static final String ACTION_DISRANGEEXT = "disrangeext";
|
||||
public static final String ACTION_OTACHECK = "otacheck";
|
||||
public static final String ACTION_ENDEBUG = "endebug";
|
||||
public static final String ACTION_DISDEBUG = "disdebug";
|
||||
|
||||
@@ -33,9 +33,9 @@ import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsUpdate;
|
||||
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyManagerInterface;
|
||||
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
|
||||
@@ -122,7 +122,7 @@ public class ShellyManagerOtaPage extends ShellyManagerPage {
|
||||
|
||||
new Thread(() -> { // schedule asynchronous reboot
|
||||
try {
|
||||
Shelly1HttpApi api = new Shelly1HttpApi(uid, config, httpClient);
|
||||
ShellyApiInterface api = th.getApi();
|
||||
ShellySettingsUpdate result = api.firmwareUpdate(updateUrl);
|
||||
String status = getString(result.status);
|
||||
logger.info("{}: {}", th.getThingName(), getMessage("fwupdate.initiated", status));
|
||||
|
||||
@@ -111,7 +111,7 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
|
||||
properties.put(ATTRIBUTE_STATUS_ICON, ICON_ATTENTION);
|
||||
}
|
||||
if (!"unknown".equalsIgnoreCase(deviceType) && (status == ThingStatus.ONLINE)) {
|
||||
properties.put(ATTRIBUTE_FIRMWARE_SEL, fillFirmwareHtml(uid, deviceType, profile.mode));
|
||||
properties.put(ATTRIBUTE_FIRMWARE_SEL, fillFirmwareHtml(profile, uid, deviceType));
|
||||
properties.put(ATTRIBUTE_ACTION_LIST, fillActionHtml(th, uid));
|
||||
} else {
|
||||
properties.put(ATTRIBUTE_FIRMWARE_SEL, "");
|
||||
@@ -132,7 +132,8 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
|
||||
return new ShellyMgrResponse(fillAttributes(html, properties), HttpStatus.OK_200);
|
||||
}
|
||||
|
||||
private String fillFirmwareHtml(String uid, String deviceType, String mode) throws ShellyApiException {
|
||||
private String fillFirmwareHtml(ShellyDeviceProfile profile, String uid, String deviceType)
|
||||
throws ShellyApiException {
|
||||
String html = "\n\t\t\t\t<select name=\"fwList\" id=\"fwList\" onchange=\"location = this.options[this.selectedIndex].value;\">\n";
|
||||
html += "\t\t\t\t\t<option value=\"\" selected disabled hidden>update to</option>\n";
|
||||
|
||||
@@ -140,45 +141,55 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
|
||||
String bVersion = "";
|
||||
String updateUrl = SHELLY_MGR_FWUPDATE_URI + "?" + URLPARM_UID + "=" + urlEncode(uid);
|
||||
try {
|
||||
// Get current prod + beta version from original firmware repo
|
||||
logger.debug("{}: Load firmware version list for device type {}", LOG_PREFIX, deviceType);
|
||||
FwRepoEntry fw = getFirmwareRepoEntry(deviceType, mode);
|
||||
pVersion = extractFwVersion(fw.version);
|
||||
if (!profile.isGen2) { // currently there is no public firmware repo for Gen2
|
||||
logger.debug("{}: Load firmware version list for device type {}", LOG_PREFIX, deviceType);
|
||||
FwRepoEntry fw = getFirmwareRepoEntry(deviceType, profile.mode);
|
||||
|
||||
pVersion = extractFwVersion(fw.version);
|
||||
bVersion = extractFwVersion(fw.betaVer);
|
||||
} else {
|
||||
pVersion = extractFwVersion(getString(profile.status.update.newVersion));
|
||||
bVersion = extractFwVersion(getString(profile.status.update.betaVersion));
|
||||
}
|
||||
if (!pVersion.isEmpty()) {
|
||||
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + FWPROD + "\">Release "
|
||||
+ pVersion + "</option>\n";
|
||||
}
|
||||
bVersion = extractFwVersion(fw.betaVer);
|
||||
if (!bVersion.isEmpty()) {
|
||||
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + FWBETA + "\">Beta "
|
||||
+ bVersion + "</option>\n";
|
||||
}
|
||||
|
||||
// Add those from Shelly Firmware Archive
|
||||
String json = httpGet(FWREPO_ARCH_URL + "?" + URLPARM_TYPE + "=" + deviceType);
|
||||
if (json.startsWith("[]")) {
|
||||
// no files available for this device type
|
||||
logger.debug("{}: No firmware files found for device type {}", LOG_PREFIX, deviceType);
|
||||
} else {
|
||||
// Create selection list
|
||||
json = "{" + json.replace("[{", "\"versions\":[{") + "}"; // make it an named array
|
||||
FwArchList list = getFirmwareArchiveList(deviceType);
|
||||
ArrayList<FwArchEntry> versions = list.versions;
|
||||
if (versions != null) {
|
||||
html += "\t\t\t\t\t<option value=\"\" disabled>-- Archive:</option>\n";
|
||||
for (int i = versions.size() - 1; i >= 0; i--) {
|
||||
FwArchEntry e = versions.get(i);
|
||||
String version = getString(e.version);
|
||||
ShellyVersionDTO v = new ShellyVersionDTO();
|
||||
if (!version.equalsIgnoreCase(pVersion) && !version.equalsIgnoreCase(bVersion)
|
||||
&& (v.compare(version, SHELLY_API_MIN_FWCOIOT) >= 0) || version.contains("master")) {
|
||||
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + version
|
||||
+ "\">" + version + "</option>\n";
|
||||
if (!profile.isGen2) { // currently no online repo for Gen2
|
||||
// Add those from Shelly Firmware Archive
|
||||
String json = httpGet(FWREPO_ARCH_URL + "?" + URLPARM_TYPE + "=" + deviceType);
|
||||
if (json.startsWith("[]")) {
|
||||
// no files available for this device type
|
||||
logger.debug("{}: No firmware files found for device type {}", LOG_PREFIX, deviceType);
|
||||
} else {
|
||||
// Create selection list
|
||||
json = "{" + json.replace("[{", "\"versions\":[{") + "}"; // make it an named array
|
||||
FwArchList list = getFirmwareArchiveList(deviceType);
|
||||
ArrayList<FwArchEntry> versions = list.versions;
|
||||
if (versions != null) {
|
||||
html += "\t\t\t\t\t<option value=\"\" disabled>-- Archive:</option>\n";
|
||||
for (int i = versions.size() - 1; i >= 0; i--) {
|
||||
FwArchEntry e = versions.get(i);
|
||||
String version = getString(e.version);
|
||||
ShellyVersionDTO v = new ShellyVersionDTO();
|
||||
if (!version.equalsIgnoreCase(pVersion) && !version.equalsIgnoreCase(bVersion)
|
||||
&& (v.compare(version, SHELLY_API_MIN_FWCOIOT) >= 0)
|
||||
|| version.contains("master")) {
|
||||
html += "\t\t\t\t\t<option value=\"" + updateUrl + "&" + URLPARM_VERSION + "=" + version
|
||||
+ "\">" + version + "</option>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ShellyApiException e) {
|
||||
} catch (
|
||||
|
||||
ShellyApiException e) {
|
||||
logger.debug("{}: Unable to retrieve firmware list: {}", LOG_PREFIX, e.toString());
|
||||
}
|
||||
|
||||
@@ -282,9 +293,9 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage {
|
||||
if ((config.eventsCoIoT) && (profile.settings.coiot != null)) {
|
||||
if ((profile.settings.coiot.enabled != null) && !profile.settings.coiot.enabled) {
|
||||
result.put("CoIoT Status", "COIOT_DISABLED");
|
||||
} else if (stats.coiotMessages == 0) {
|
||||
} else if (stats.protocolMessages == 0) {
|
||||
result.put("CoIoT Discovery", "NO_COIOT_DISCOVERY");
|
||||
} else if (stats.coiotMessages < 2) {
|
||||
} else if (stats.protocolMessages < 2) {
|
||||
result.put("CoIoT Multicast", "NO_COIOT_MULTICAST");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +261,9 @@ public class ShellyManagerPage {
|
||||
properties.put(ATTRIBUTE_ACTIONS_SKIPPED,
|
||||
profile.status.astats != null ? String.valueOf(profile.status.astats.skipped) : "n/a");
|
||||
properties.put(ATTRIBUTE_MAX_ITEMP, stats.maxInternalTemp > 0 ? stats.maxInternalTemp + " °C" : "n/a");
|
||||
if (stats.maxInternalTemp == 0) {
|
||||
properties.replace(CHANNEL_DEVST_ITEMP, "n/a");
|
||||
}
|
||||
|
||||
// Shelly H&T: When external power is connected the battery level is not valid
|
||||
if (!profile.isHT || (getInteger(profile.settings.externalPower) == 0)) {
|
||||
|
||||
@@ -16,12 +16,15 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.SHELLY_API_INVTEMP;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
@@ -47,6 +50,7 @@ import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelKind;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
@@ -97,6 +101,20 @@ public class ShellyChannelDefinitions {
|
||||
public static final String PREFIX_GROUP = "group-type." + BINDING_ID + ".";
|
||||
public static final String PREFIX_CHANNEL = "channel-type." + BINDING_ID + ".";
|
||||
|
||||
public class OptionEntry {
|
||||
public ChannelTypeUID uid;
|
||||
public String key;
|
||||
public String value;
|
||||
|
||||
public OptionEntry(ChannelTypeUID uid, String key, String value) {
|
||||
this.uid = uid;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private final CopyOnWriteArrayList<OptionEntry> stateOptions = new CopyOnWriteArrayList<>();
|
||||
|
||||
private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
|
||||
|
||||
@Activate
|
||||
@@ -169,8 +187,6 @@ public class ShellyChannelDefinitions {
|
||||
.add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_TIMER_AUTOOFF, "timerAutoOff", ITEMT_TIME))
|
||||
.add(new ShellyChannel(m, CHGR_LIGHTCH, CHANNEL_TIMER_ACTIVE, "timerActive", ITEMT_SWITCH))
|
||||
|
||||
// RGBW2-color
|
||||
.add(new ShellyChannel(m, CHGR_LIGHT, CHANNEL_LIGHT_POWER, "system:power", ITEMT_SWITCH))
|
||||
// Power Meter
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_CURRENTWATTS, "meterWatts", ITEMT_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_TOTALKWH, "meterTotal", ITEMT_ENERGY))
|
||||
@@ -182,7 +198,7 @@ public class ShellyChannelDefinitions {
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_REACTWATTS, "meterReactive", ITEMT_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_VOLTAGE, "meterVoltage", ITEMT_VOLT))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEMT_AMP))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEMT_PERCENT))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEMT_NUMBER))
|
||||
|
||||
// Sensors
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEMT_TEMP))
|
||||
@@ -227,11 +243,11 @@ public class ShellyChannelDefinitions {
|
||||
// TRV
|
||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_POSITION, "sensorPosition", ITEMT_DIMMER))
|
||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_MODE, "controlMode", ITEMT_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_SCHEDULE, "controlSchedule", ITEMT_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_PROFILE, "controlProfile", ITEMT_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_SETTEMP, "targetTemp", ITEMT_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_BCONTROL, "boostControl", ITEMT_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_BTIMER, "boostTimer", ITEMT_TIME));
|
||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_BTIMER, "boostTimer", ITEMT_TIME))
|
||||
.add(new ShellyChannel(m, CHGR_CONTROL, CHANNEL_CONTROL_SCHEDULE, "controlSchedule", ITEMT_SWITCH));
|
||||
}
|
||||
|
||||
public static @Nullable ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
|
||||
@@ -273,7 +289,8 @@ public class ShellyChannelDefinitions {
|
||||
|
||||
addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
|
||||
|
||||
if (!profile.isSensor && !profile.isIX && getDouble(status.temperature) != SHELLY_API_INVTEMP) {
|
||||
if (!profile.isSensor && !profile.isIX && status.temperature != null
|
||||
&& status.temperature != SHELLY_API_INVTEMP) {
|
||||
// Only some devices report the internal device temp
|
||||
addChannel(thing, add, status.tmp != null || status.temperature != null, CHGR_DEVST, CHANNEL_DEVST_ITEMP);
|
||||
}
|
||||
@@ -311,19 +328,20 @@ public class ShellyChannelDefinitions {
|
||||
|
||||
if (profile.settings.relays != null) {
|
||||
ShellySettingsRelay rs = profile.settings.relays.get(idx);
|
||||
boolean timer = rs.hasTimer != null || rstatus.hasTimer != null; // Dimmer 1/2 have
|
||||
addChannel(thing, add, rs.ison != null, group, CHANNEL_OUTPUT);
|
||||
addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
|
||||
|
||||
boolean timer = rs.hasTimer != null || rstatus.hasTimer != null; // Dimmer 1/2 have
|
||||
addChannel(thing, add, timer, group, CHANNEL_TIMER_ACTIVE);
|
||||
addChannel(thing, add, rs.autoOn != null, group, CHANNEL_TIMER_AUTOON);
|
||||
addChannel(thing, add, rs.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
|
||||
addChannel(thing, add, timer, group, CHANNEL_TIMER_ACTIVE);
|
||||
}
|
||||
|
||||
// Shelly 1/1PM Addon
|
||||
if (profile.settings.extTemperature != null) {
|
||||
addChannel(thing, add, profile.settings.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
|
||||
addChannel(thing, add, profile.settings.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
|
||||
addChannel(thing, add, profile.settings.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
|
||||
if (profile.status.extTemperature != null) {
|
||||
addChannel(thing, add, profile.status.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
|
||||
addChannel(thing, add, profile.status.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
|
||||
addChannel(thing, add, profile.status.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
|
||||
}
|
||||
if (profile.settings.extHumidity != null) {
|
||||
addChannel(thing, add, profile.settings.extHumidity.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY);
|
||||
@@ -344,7 +362,6 @@ public class ShellyChannelDefinitions {
|
||||
ShellySettingsDimmer ds = profile.settings.dimmers.get(idx);
|
||||
addChannel(thing, add, ds.autoOn != null, group, CHANNEL_TIMER_AUTOON);
|
||||
addChannel(thing, add, ds.autoOff != null, group, CHANNEL_TIMER_AUTOOFF);
|
||||
|
||||
ShellyShortLightStatus dss = dstatus.dimmers.get(idx);
|
||||
addChannel(thing, add, dss != null && dss.hasTimer != null, group, CHANNEL_TIMER_ACTIVE);
|
||||
}
|
||||
@@ -359,7 +376,6 @@ public class ShellyChannelDefinitions {
|
||||
if (profile.settings.lights != null) {
|
||||
ShellySettingsRgbwLight light = profile.settings.lights.get(idx);
|
||||
String whiteGroup = profile.isRGBW2 ? group : CHANNEL_GROUP_WHITE_CONTROL;
|
||||
|
||||
// Create power channel in color mode and brightness channel in white mode
|
||||
addChannel(thing, add, profile.inColor, group, CHANNEL_LIGHT_POWER);
|
||||
addChannel(thing, add, light.autoOn != null, group, CHANNEL_TIMER_AUTOON);
|
||||
@@ -368,29 +384,31 @@ public class ShellyChannelDefinitions {
|
||||
addChannel(thing, add, light.brightness != null, whiteGroup, CHANNEL_BRIGHTNESS);
|
||||
addChannel(thing, add, light.temp != null, whiteGroup, CHANNEL_COLOR_TEMP);
|
||||
}
|
||||
|
||||
return add;
|
||||
}
|
||||
|
||||
public static Map<String, Channel> createInputChannels(final Thing thing, final ShellyDeviceProfile profile,
|
||||
final ShellySettingsStatus status, String group) {
|
||||
final ShellySettingsStatus status) {
|
||||
Map<String, Channel> add = new LinkedHashMap<>();
|
||||
if (status.inputs != null) {
|
||||
// Create channels per input. For devices with more than 1 input (Dimmer, 1L) multiple channel sets are
|
||||
// created by adding the index to the channel name
|
||||
boolean multi = (profile.numRelays == 1 || profile.isDimmer || profile.isRoller) && profile.numInputs >= 2;
|
||||
for (int i = 0; i < profile.numInputs; i++) {
|
||||
String suffix = multi ? String.valueOf(i + 1) : "";
|
||||
ShellyInputState input = status.inputs.get(i);
|
||||
String group = profile.getInputGroup(i);
|
||||
String suffix = profile.getInputSuffix(i); // multi ? String.valueOf(i + 1) : "";
|
||||
addChannel(thing, add, true, group, CHANNEL_INPUT + suffix);
|
||||
addChannel(thing, add, true, group,
|
||||
(!profile.isRoller ? CHANNEL_BUTTON_TRIGGER + suffix : CHANNEL_EVENT_TRIGGER));
|
||||
if (profile.inButtonMode(i)) {
|
||||
ShellyInputState input = status.inputs.get(i);
|
||||
addChannel(thing, add, input.event != null, group, CHANNEL_STATUS_EVENTTYPE + suffix);
|
||||
addChannel(thing, add, input.eventCount != null, group, CHANNEL_STATUS_EVENTCOUNT + suffix);
|
||||
}
|
||||
addChannel(thing, add, true, group,
|
||||
(!profile.isRoller ? CHANNEL_BUTTON_TRIGGER + suffix : CHANNEL_EVENT_TRIGGER));
|
||||
}
|
||||
} else if (status.input != null) {
|
||||
// old RGBW2 firmware
|
||||
String group = profile.getInputGroup(0);
|
||||
addChannel(thing, add, true, group, CHANNEL_INPUT);
|
||||
addChannel(thing, add, true, group, CHANNEL_BUTTON_TRIGGER);
|
||||
}
|
||||
@@ -409,7 +427,7 @@ public class ShellyChannelDefinitions {
|
||||
ShellyThingInterface handler = (ShellyThingInterface) thing.getHandler();
|
||||
if (handler != null) {
|
||||
ShellySettingsGlobal settings = handler.getProfile().settings;
|
||||
if (getBool(settings.favoritesEnabled) && (settings.favorites != null)) {
|
||||
if (getBool(settings.favoritesEnabled) && settings.favorites != null) {
|
||||
addChannel(thing, add, roller.currentPos != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_FAV);
|
||||
}
|
||||
}
|
||||
@@ -435,7 +453,7 @@ public class ShellyChannelDefinitions {
|
||||
addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
|
||||
addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
|
||||
addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
|
||||
addChannel(thing, newChannels, emeter.power != null, group, CHANNEL_EMETER_PFACTOR); // EM has no PF. but power
|
||||
addChannel(thing, newChannels, emeter.pf != null, group, CHANNEL_EMETER_PFACTOR); // EM has no PF. but power
|
||||
|
||||
addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
|
||||
return newChannels;
|
||||
@@ -498,7 +516,7 @@ public class ShellyChannelDefinitions {
|
||||
addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_MODE);
|
||||
addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE);
|
||||
addChannel(thing, newChannels, true, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SCHEDULE);
|
||||
addChannel(thing, newChannels, true, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE); // TRV
|
||||
addChannel(thing, newChannels, true, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE);
|
||||
}
|
||||
|
||||
// Battery
|
||||
@@ -550,6 +568,30 @@ public class ShellyChannelDefinitions {
|
||||
}
|
||||
}
|
||||
|
||||
public List<StateOption> getStateOptions(ChannelTypeUID uid) {
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
for (OptionEntry oe : stateOptions) {
|
||||
if (oe.uid.equals(uid)) {
|
||||
options.add(new StateOption(oe.key, oe.value));
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
public void addStateOption(String channelId, String key, String value) {
|
||||
ChannelTypeUID uid = getChannelTypeUID(channelId);
|
||||
stateOptions.addIfAbsent(new OptionEntry(uid, key, value));
|
||||
}
|
||||
|
||||
public void clearStateOptions(String channelId) {
|
||||
ChannelTypeUID uid = getChannelTypeUID(channelId);
|
||||
for (OptionEntry oe : stateOptions) {
|
||||
if (oe.uid.equals(uid)) {
|
||||
stateOptions.remove(oe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ShellyChannel {
|
||||
private final ShellyTranslationProvider messages;
|
||||
public String group = "";
|
||||
|
||||
@@ -13,17 +13,29 @@
|
||||
|
||||
package org.openhab.binding.shelly.internal.provider;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.BINDING_ID;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* This class provides the list of valid inputs for the input channel of a source.
|
||||
@@ -32,27 +44,39 @@ import org.openhab.core.types.StateDescription;
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyStateDescriptionProvider extends BaseDynamicStateDescriptionProvider implements ThingHandlerService {
|
||||
private @Nullable ShellyThingInterface handler;
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, ShellyStateDescriptionProvider.class })
|
||||
public class ShellyStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
private final ThingRegistry thingRegistry;
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
this.handler = (ShellyThingInterface) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return (ThingHandler) handler;
|
||||
@Activate
|
||||
public ShellyStateDescriptionProvider(final @Reference EventPublisher eventPublisher, //
|
||||
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
|
||||
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService,
|
||||
@Reference ThingRegistry thingRegistry) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
this.thingRegistry = thingRegistry;
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Override
|
||||
public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
|
||||
@Nullable Locale locale) {
|
||||
public @Nullable StateDescription getStateDescription(Channel channel,
|
||||
@Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
|
||||
ChannelTypeUID uid = channel.getChannelTypeUID();
|
||||
if (uid != null && handler != null) {
|
||||
setStateOptions(channel.getUID(), handler.getStateOptions(uid));
|
||||
if (uid == null || !BINDING_ID.equals(uid.getBindingId()) || originalStateDescription == null) {
|
||||
return null;
|
||||
}
|
||||
return super.getStateDescription(channel, original, locale);
|
||||
|
||||
Thing thing = thingRegistry.get(channel.getUID().getThingUID());
|
||||
ShellyThingInterface handler = (ShellyThingInterface) thing.getHandler();
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StateOption> stateOptions = handler.getStateOptions(uid);
|
||||
return stateOptions == null ? null
|
||||
: StateDescriptionFragmentBuilder.create(originalStateDescription).withOptions(stateOptions).build()
|
||||
.toStateDescription();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:shelly:relay-gen2">
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>@text/thing-type.config.shelly.deviceIp.label</label>
|
||||
<description>@text/thing-type.config.shelly.deviceIp.description</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>@text/thing-type.config.shelly.password.label</label>
|
||||
<description>@text/thing-type.config.shelly.password.description</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
|
||||
<label>@text/thing-type.config.shelly.updateInterval.label</label>
|
||||
<description>@text/thing-type.config.shelly.updateInterval.description</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:roller-gen2">
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>@text/thing-type.config.shelly.deviceIp.label</label>
|
||||
<description>@text/thing-type.config.shelly.deviceIp.description</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>@text/thing-type.config.shelly.password.label</label>
|
||||
<description>@text/thing-type.config.shelly.password.description</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="favoriteUP" type="integer" min="0" max="4" required="false">
|
||||
<label>@text/thing-type.config.shelly.roller.favoriteUP.label</label>
|
||||
<description>@text/thing-type.config.shelly.roller.favoriteUP.description</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="favoriteDOWN" type="integer" min="0" max="4" required="false">
|
||||
<label>@text/thing-type.config.shelly.roller.favoriteDOWN.label</label>
|
||||
<description>@text/thing-type.config.shelly.roller.favoriteDOWN.description</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" min="10" required="true" unit="s">
|
||||
<label>@text/thing-type.config.shelly.updateInterval.label</label>
|
||||
<description>@text/thing-type.config.shelly.updateInterval.description</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:battery-gen2">
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>@text/thing-type.config.shelly.deviceIp.label</label>
|
||||
<description>@text/thing-type.config.shelly.deviceIp.description</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>@text/thing-type.config.shelly.password.label</label>
|
||||
<description>@text/thing-type.config.shelly.password.description</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="lowBattery" type="integer" required="false">
|
||||
<label>@text/thing-type.config.shelly.battery.lowBattery.label</label>
|
||||
<description>@text/thing-type.config.shelly.battery.lowBattery.description</description>
|
||||
<default>20</default>
|
||||
<unitLabel>%</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" min="60" required="true" unit="s">
|
||||
<label>@text/thing-type.config.shelly.updateInterval.label</label>
|
||||
<description>@text/thing-type.config.shelly.updateInterval.description</description>
|
||||
<default>900</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -17,9 +17,10 @@ message.config-status.error.missing-userid = No user ID in the Thing configurati
|
||||
# Thing status descriptions
|
||||
message.offline.conf-error-no-credentials = Device is password protected, but no credentials have been configured.
|
||||
message.offline.conf-error-access-denied = Access denied, check user id and password.
|
||||
message.offline.conf-error-wrong-mode = Device is no longer in the configured device mode {0}, required {1}. Delete the thing and re-discover the device.
|
||||
message.offline.conf-error-wrong-mode = Device is in device mode {0}, thing requires {1}. Delete the thing and re-discover the device.
|
||||
message.offline.status-error-connect = Unable to connect to device - {0}
|
||||
message.offline.status-error-timeout = Device is not reachable (API timeout)
|
||||
message.offline.status-error-unexpected-error = Unexpected error
|
||||
message.offline.status-error-unexpected-error = Unexpected error: {0}
|
||||
message.offline.status-error-unexpected-api-result = An unexpected API response. Please verify the logfile to get more detailed information.
|
||||
message.offline.status-error-watchdog = Device is not responding, seems to be unavailable.
|
||||
message.offline.status-error-restarted = The device has restarted and will be re-initialized.
|
||||
@@ -27,8 +28,8 @@ message.offline.status-error-fwupgrade = Firmware upgrade in progress
|
||||
|
||||
# General messages
|
||||
message.versioncheck.failed = Unable to check firmware version: {0}
|
||||
message.versioncheck.beta = Device is running a Beta version: {0}/{1}.
|
||||
message.versioncheck.tooold = WARNING: Firmware might be too old, installed: {0}/{1}, required minimal {3}.
|
||||
message.versioncheck.beta = Device is running a Beta version: {0}/{1}
|
||||
message.versioncheck.tooold = WARNING: Firmware might be too old, installed: {0}/{1}, minimal required {2}
|
||||
message.versioncheck.update = INFO: New firmware available: current version: {0}, new version: {1}
|
||||
message.versioncheck.autocoiot = INFO: Firmware is full-filling the minimum version to auto-enable CoIoT
|
||||
message.init.noipaddress = Unable to detect local IP address. Please make sure that IPv4 is enabled for this interface and check openHAB Network Configuration.
|
||||
@@ -76,7 +77,7 @@ thing-type.shelly.shellysmoke.description = Shelly Smoke Sensor (battery powered
|
||||
thing-type.shelly.shellyflood.description = Shelly Flood Sensor (battery powered)
|
||||
thing-type.shelly.shellysense.description = Shelly Sense (Motion Sensor and Remote IR Controller)
|
||||
thing-type.shelly.shellybulb.description = Shelly Bulb (Dimmable RGB+W Bulb)
|
||||
thing-type.shelly.shellybulbduo.description = Shelly Duo (Dimmable WW/CW Bulb)
|
||||
thing-type.shelly.shellybulbduo.description = Shelly Duo (Dimmable RGB+W/CW Bulb)
|
||||
thing-type.shelly.shellycolorbulb.description = Shelly Color Bulb (Dimmable RGB+W Bulb)
|
||||
thing-type.shelly.shellyvintage.description = Shelly Vintage (Dimmable Vintage Bulb)
|
||||
thing-type.shelly.shellyrgbw2-color.description = Shelly RGBW2 (LED Controller in Color Mode)
|
||||
@@ -85,6 +86,24 @@ thing-type.shelly.shellymotion.description = Shelly Motion (Motion Detection (ba
|
||||
thing-type.shelly.shellygas.description = Shelly Gas (Gas Sensor - Gas Leak Detection)
|
||||
thing-type.shelly.shellytrv.description = Shelly TRV (Radiator value, battery powered)
|
||||
thing-type.shelly.shellyix3.description = Shelly ix3 (Activation Device with 3 inputs)
|
||||
thing-type.shelly.shellypludht.description = Shelly Plus HT - Temperature and Humidity Sensor
|
||||
|
||||
# Plus/Pro devices
|
||||
thing-type.shelly.shellyplus1.description = Shelly Plus 1 (Single Relay Switch)
|
||||
thing-type.shelly.shellyplus1pm.description = Shelly Plus 1PM - Single Relay Switch with Power Meter
|
||||
thing-type.shelly.shellyplus2-relay.description = Shelly Plus 2PM - Dual Relay Switch with Power Meter
|
||||
thing-type.shelly.shellyplus2pm-roller.description = Shelly Plus 2PM - Roller Control with Power Meter
|
||||
thing-type.shelly.shellyplusht.description = Shelly Plus HT - Humidity and Temperature sensor with display
|
||||
thing-type.shelly.shellyplusi4.description = Shelly Plus i4 - 4xInput Device
|
||||
thing-type.shelly.shellyplusi4dc.description = Shelly Plus i4DC - 4xDC Input Device
|
||||
thing-type.shelly.shellypro1.description = Shelly Pro 1 - Single Relay Switch
|
||||
thing-type.shelly.shellypro1pm.description = Shelly Pro 1PM - Single Relay Switch with Power Meter
|
||||
thing-type.shelly.shellypro2-relay.description = Shelly Pro 2 - Dual Relay Switch
|
||||
thing-type.shelly.shellypro2pm-relay.description= Shelly Pro 2PM - Dual Relay Switch with Power Meter
|
||||
thing-type.shelly.shellypro2pm-roller.description = Shelly Pro 2PM - Roller Control with Power Meter
|
||||
thing-type.shelly.shellypro3.description = Shelly Pro 3 - 3xRelay Switch
|
||||
thing-type.shelly.shellypro4pm.description = Shelly Pro 4PM - 4xRelay Switch with Power Meter
|
||||
|
||||
|
||||
# Plus/Pro devices
|
||||
thing-type.shelly.shellyplus1.description = Shelly Plus 1 (Single Relay Switch)
|
||||
@@ -180,11 +199,11 @@ channel-group-type.shelly.relayChannel3.label = Relay 3
|
||||
channel-group-type.shelly.relayChannel4.label = Relay 4
|
||||
channel-group-type.shelly.dimmerChannel.label = Dimmer
|
||||
channel-group-type.shelly.dimmerChannel.description = A Shelly Dimmer channel
|
||||
channel-group-type.shelly.ix3Channel1.label = Input 1
|
||||
channel-group-type.shelly.ix3Channel2.label = Input 2
|
||||
channel-group-type.shelly.ix3Channel3.label = Input 3
|
||||
channel-group-type.shelly.ix3Channel4.label = Input 4
|
||||
channel-group-type.shelly.ix3Channel.description = Input Status
|
||||
channel-group-type.shelly.iXChannel1.label = Input 1
|
||||
channel-group-type.shelly.iXChannel2.label = Input 2
|
||||
channel-group-type.shelly.iXChannel3.label = Input 3
|
||||
channel-group-type.shelly.iXChannel4.label = Input 3
|
||||
channel-group-type.shelly.iXChannel.description = Input Status
|
||||
channel-group-type.shelly.rollerControl.label = Roller Control
|
||||
channel-group-type.shelly.rollerControl.description = Controlling the roller mode
|
||||
channel-group-type.shelly.meter.label = Power Meter
|
||||
@@ -200,9 +219,9 @@ channel-group-type.shelly.externalSensors.description = Temperatures from extern
|
||||
channel-type.shelly.outputName.label = Output Name
|
||||
channel-type.shelly.outputName.description = Output/Channel Name as configured in the Shelly App
|
||||
channel-type.shelly.timerAutoOn.label = Auto-ON Timer
|
||||
channel-type.shelly.timerAutoOn.description = When the output of the relay is switched off, it will be switched off automatically after n seconds
|
||||
channel-type.shelly.timerAutoOn.description = When the output of the relay is switched off, it will be switched back on automatically after n seconds
|
||||
channel-type.shelly.timerAutoOff.label = Auto-OFF Timer
|
||||
channel-type.shelly.timerAutoOff.description = When the output of the relay is switched on, it will be switched on automatically after n seconds
|
||||
channel-type.shelly.timerAutoOff.description = When the output of the relay is switched on, it will be switched back off automatically after n seconds
|
||||
channel-type.shelly.timerActive.label = Auto ON/OFF timer active
|
||||
channel-type.shelly.timerActive.description = ON: A timer is active, OFF: no timer active
|
||||
channel-type.shelly.temperature.label = Temperature
|
||||
@@ -398,7 +417,7 @@ channel-type.shelly.boostTimer.label = Boost Timer
|
||||
channel-type.shelly.boostTimer.description = Number of minutes to boost maximum power
|
||||
channel-type.shelly.batVoltage.label = Battery Voltage
|
||||
channel-type.shelly.batVoltage.description = Battery voltage (V)
|
||||
channel-type.shelly.charger.label = Charger Connected
|
||||
channel-type.shelly.charger.label = External Power
|
||||
channel-type.shelly.charger.description = ON: External power is connected
|
||||
channel-type.shelly.calibrated.label = Calibrated
|
||||
channel-type.shelly.calibrated.description = ON: The device/sensor is calibrated
|
||||
@@ -429,7 +448,7 @@ channel-type.shelly.lastEvent.state.option.L = Long push
|
||||
channel-type.shelly.lastEvent.state.option.SL = Short-Long push
|
||||
channel-type.shelly.lastEvent.state.option.LS = Long-Short push
|
||||
channel-type.shelly.eventCount.label = Event Count
|
||||
channel-type.shelly.eventCount.description = Event Count
|
||||
channel-type.shelly.eventCount.description = Number of input events received from device
|
||||
channel-type.shelly.eventTrigger.label = Event
|
||||
channel-type.shelly.eventTrigger.description = Event Trigger (ROLLER_OPEN/ROLLER_CLOSE/ROLLER_STOP)
|
||||
channel-type.shelly.eventTrigger.option.ROLLER_OPEN = Roller is open
|
||||
@@ -506,6 +525,18 @@ message.manager.action.setwifirec-enable = The device performs an auto-restart i
|
||||
message.manager.action.setwifirec-disable = WiFi Recovery Mode will be disabled.
|
||||
message.manager.action.setwifirec-confirm = WiFi Recovery Mode has been {0}.
|
||||
message.manager.action.setwifirec-failed = Unable to update setting for WiFi Recovery Mode: {0}
|
||||
message.manager.action.setwifirangeext-enable = WiFi Range Extender will be enabled.
|
||||
message.manager.action.setwifirangeext-disable = WiFi Range Extender will be disabled.
|
||||
message.manager.action.setwifirangeext-confirm = WiFiRange Extender has been set, restart required: {0}
|
||||
message.manager.action.setwifirangeext-failed = Unable to update setting for WiFi Range Extender: {0}
|
||||
message.manager.action.setethernet-enable = Ethernet interface will be enabled, this requires a device reboot.
|
||||
message.manager.action.setethernet-disable = Ethernet interface will be disabled, this may require a device reboot.
|
||||
message.manager.action.setethernet-confirm = Ethernet interface has been {0}.
|
||||
message.manager.action.setethernet-failed = Unable to update setting for Ethernet interface: {0}
|
||||
message.manager.action.setbluetooth-enable = Bluetooth interface will be enabled, this requires a device reboot.
|
||||
message.manager.action.setbluetooth-disable = Bluetooth interface will be disabled, this may require a device reboot.
|
||||
message.manager.action.setbluetooth-confirm = Bluetooth interface has been {0}.
|
||||
message.manager.action.setbluetooth-failed = Unable to update setting for Bluetooth interface: {0}
|
||||
message.manager.action.aproaming-enable = WiFi Access Point Roaming will be enabled. Check product documentation for details.
|
||||
message.manager.action.aproaming-disable = WiFi Access Point Roaming will be disabled.
|
||||
message.manager.action.aproaming-confirm = Unable to update setting WiFi Access Point Roaming: {0}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="shellybulb">
|
||||
<label>Shelly Bulb (SHBLB-1)</label>
|
||||
<label>Shelly Bulb</label>
|
||||
<description>@text/thing-type.shelly.shellybulb.description</description>
|
||||
<category>Lightbulb</category>
|
||||
<channel-groups>
|
||||
@@ -20,7 +20,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellybulbduo">
|
||||
<label>Shelly Duo (SHBDUO-1)</label>
|
||||
<label>Shelly Duo</label>
|
||||
<description>@text/thing-type.shelly.shellybulbduo.description</description>
|
||||
<category>Lightbulb</category>
|
||||
<channel-groups>
|
||||
@@ -35,7 +35,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellycolorbulb">
|
||||
<label>Shelly Color Bulb (SHCB-1)</label>
|
||||
<label>Shelly Color Bulb</label>
|
||||
<description>@text/thing-type.shelly.shellycolorbulb.description</description>
|
||||
<category>ColorLight</category>
|
||||
<channel-groups>
|
||||
@@ -51,7 +51,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyvintage">
|
||||
<label>Shelly Vintage (SHVIN-1)</label>
|
||||
<label>Shelly Vintage</label>
|
||||
<description>@text/thing-type.shelly.shellyvintage.description</description>
|
||||
<category>Lightbulb</category>
|
||||
<channel-groups>
|
||||
@@ -66,7 +66,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyrgbw2-color">
|
||||
<label>Shelly RGBW2 Color Mode (SHRGBW2)</label>
|
||||
<label>Shelly RGBW2 Color Mode</label>
|
||||
<description>@text/thing-type.shelly.shellyrgbw2-color.description</description>
|
||||
<category>ColorLight</category>
|
||||
<channel-groups>
|
||||
@@ -81,7 +81,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyrgbw2-white">
|
||||
<label>Shelly RGBW2 White Mode (SHRGBW2)</label>
|
||||
<label>Shelly RGBW2 White Mode</label>
|
||||
<description>@text/thing-type.shelly.shellyrgbw2-white.description</description>
|
||||
<category>Lightbulb</category>
|
||||
<channel-groups>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="shelly1">
|
||||
<label>Shelly 1 (SHSW-1)</label>
|
||||
<label>Shelly 1</label>
|
||||
<description>@text/thing-type.shelly.shelly1.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
@@ -18,7 +18,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly1l">
|
||||
<label>Shelly 1L (SHSW-L)</label>
|
||||
<label>Shelly 1L</label>
|
||||
<description>@text/thing-type.shelly.shelly1l.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
@@ -32,7 +32,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly1pm">
|
||||
<label>Shelly 1PM (SHSW-PM)</label>
|
||||
<label>Shelly 1PM</label>
|
||||
<description>@text/thing-type.shelly.shelly1pm.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
@@ -46,7 +46,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyem">
|
||||
<label>Shelly EM (SHEM)</label>
|
||||
<label>Shelly EM</label>
|
||||
<description>@text/thing-type.shelly.shellyem.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
<!-- Shelly 3EM - device reports wrong service name: shellyem3, see README -->
|
||||
<thing-type id="shellyem3">
|
||||
<label>Shelly 3EM (SHEM-3)</label>
|
||||
<label>Shelly EM3</label>
|
||||
<description>@text/thing-type.shelly.shellyem3.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
@@ -86,7 +86,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly2-relay">
|
||||
<label>Shelly 2 Relay (SHSW-21)</label>
|
||||
<label>Shelly 2 Relay</label>
|
||||
<description>@text/thing-type.shelly.shelly2-relay.description</description>
|
||||
|
||||
<channel-groups>
|
||||
@@ -104,22 +104,8 @@
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly2-roller">
|
||||
<label>Shelly 2 Roller (SHSW-21)</label>
|
||||
<description>@text/thing-type.shelly.shelly2-roller.description</description>
|
||||
<category>Rollershutter</category>
|
||||
<channel-groups>
|
||||
<channel-group id="roller" typeId="rollerControl"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:roller"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly25-relay">
|
||||
<label>Shelly 2.5 Relay (SHSW-25)</label>
|
||||
<label>Shelly 2.5 Relay</label>
|
||||
<description>@text/thing-type.shelly.shelly25-relay.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
@@ -142,7 +128,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly25-roller">
|
||||
<label>Shelly 2.5 Roller (SHSW-25)</label>
|
||||
<label>Shelly 2.5 Roller</label>
|
||||
<description>@text/thing-type.shelly.shelly25-roller.description</description>
|
||||
<category>Rollershutter</category>
|
||||
<channel-groups>
|
||||
@@ -156,7 +142,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly4pro">
|
||||
<label>Shelly 4 Pro Relay (SHSW-44)</label>
|
||||
<label>Shelly 4 Pro Relay</label>
|
||||
<description>@text/thing-type.shelly.shelly4pro.description</description>
|
||||
|
||||
<channel-groups>
|
||||
@@ -192,7 +178,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplug">
|
||||
<label>Shelly Plug (SHPLG-1)</label>
|
||||
<label>Shelly Plug</label>
|
||||
<description>@text/thing-type.shelly.shellyplug.description</description>
|
||||
<category>PowerOutlet</category>
|
||||
<channel-groups>
|
||||
@@ -206,7 +192,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplugs">
|
||||
<label>Shelly Plug-S (SHPLG-S)</label>
|
||||
<label>Shelly Plug-S</label>
|
||||
<description>@text/thing-type.shelly.shellyplugs.description</description>
|
||||
<category>PowerOutlet</category>
|
||||
<channel-groups>
|
||||
@@ -220,7 +206,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplugu1">
|
||||
<label>Shelly Plug US (SHPLG-U1)</label>
|
||||
<label>Shelly Plug US</label>
|
||||
<description>@text/thing-type.shelly.shellyplugu1.description</description>
|
||||
<category>PowerOutlet</category>
|
||||
<channel-groups>
|
||||
@@ -234,7 +220,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyuni">
|
||||
<label>Shelly UNI (SHUNI-1)</label>
|
||||
<label>Shelly UNI</label>
|
||||
<description>@text/thing-type.shelly.shellyuni.description</description>
|
||||
|
||||
<channel-groups>
|
||||
@@ -253,7 +239,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydimmer">
|
||||
<label>Shelly Dimmer (SHDM-1)</label>
|
||||
<label>Shelly Dimmer</label>
|
||||
<description>@text/thing-type.shelly.shellydimmer.description</description>
|
||||
<category>DimmableLight</category>
|
||||
<channel-groups>
|
||||
@@ -267,7 +253,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydimmer2">
|
||||
<label>Shelly Dimmer 2 (SHDM-2)</label>
|
||||
<label>Shelly Dimmer 2</label>
|
||||
<description>@text/thing-type.shelly.shellydimmer2.description</description>
|
||||
<category>DimmableLight</category>
|
||||
<channel-groups>
|
||||
@@ -281,17 +267,17 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyix3">
|
||||
<label>Shelly ix3 (SHIX3-1)</label>
|
||||
<label>Shelly ix3</label>
|
||||
<description>@text/thing-type.shelly.shellyix3.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="status1" typeId="ix3Channel">
|
||||
<label>@text/channel-group-type.shelly.ix3Channel1.label</label>
|
||||
<channel-group id="status1" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="status2" typeId="ix3Channel">
|
||||
<label>@text/channel-group-type.shelly.ix3Channel2.label</label>
|
||||
<channel-group id="status2" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="status3" typeId="ix3Channel">
|
||||
<label>@text/channel-group-type.shelly.ix3Channel3.label</label>
|
||||
<channel-group id="status3" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel3.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
@@ -315,9 +301,9 @@
|
||||
<description>@text/channel-group-type.shelly.dimmerChannel.description</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="ix3Channel">
|
||||
<label>@text/channel-group-type.shelly.ix3Channel.label</label>
|
||||
<description>@text/channel-group-type.shelly.ix3Channel.description</description>
|
||||
<channel-group-type id="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel.label</label>
|
||||
<description>@text/channel-group-type.shelly.ixChannel.description</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="rollerControl">
|
||||
@@ -576,7 +562,7 @@
|
||||
<item-type>Number</item-type>
|
||||
<label>@text/channel-type.shelly.meterPowerFactor.label</label>
|
||||
<description>@text/channel-type.shelly.meterPowerFactor.description</description>
|
||||
<state readOnly="true" pattern="%.3f %unit%">
|
||||
<state readOnly="true" pattern="%.3f">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="shellyht">
|
||||
<label>Shelly H&T (SHHT-1)</label>
|
||||
<label>Shelly H&T</label>
|
||||
<description>@text/thing-type.shelly.shellyht.description</description>
|
||||
<category>Sensor</category>
|
||||
<channel-groups>
|
||||
@@ -33,7 +33,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellygas">
|
||||
<label>Shelly GAS (SHGS-1)</label>
|
||||
<label>Shelly Gas</label>
|
||||
<description>@text/thing-type.shelly.shellygas.description</description>
|
||||
<category>Sensor</category>
|
||||
<channel-groups>
|
||||
@@ -46,7 +46,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyflood">
|
||||
<label>Shelly Flood (SHWT-1)</label>
|
||||
<label>Shelly Flood</label>
|
||||
<description>@text/thing-type.shelly.shellyflood.description</description>
|
||||
<category>Sensor</category>
|
||||
<channel-groups>
|
||||
@@ -60,7 +60,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydw">
|
||||
<label>Shelly Door/Window (SHDW-1)</label>
|
||||
<label>Shelly Door/Window</label>
|
||||
<description>@text/thing-type.shelly.shellydw.description</description>
|
||||
<category>Sensor</category>
|
||||
<channel-groups>
|
||||
@@ -74,7 +74,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydw2">
|
||||
<label>Shelly Door/Window (SHDW-2)</label>
|
||||
<label>Shelly Door/Window</label>
|
||||
<description>@text/thing-type.shelly.shellydw2.description</description>
|
||||
<category>Sensor</category>
|
||||
<channel-groups>
|
||||
@@ -88,7 +88,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellysense">
|
||||
<label>Shelly Sense (SHSEN-1)</label>
|
||||
<label>Shelly Sense</label>
|
||||
<description>@text/thing-type.shelly.shellysense.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="control"/>
|
||||
@@ -102,7 +102,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellybutton1">
|
||||
<label>Shelly Button 1 (SHBTN-1)</label>
|
||||
<label>Shelly Button 1</label>
|
||||
<description>@text/thing-type.shelly.shellybutton1.description</description>
|
||||
<category>WallSwitch</category>
|
||||
<channel-groups>
|
||||
@@ -116,7 +116,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellybutton2">
|
||||
<label>Shelly Button 2 (SHBTN-2)</label>
|
||||
<label>Shelly Button 2</label>
|
||||
<description>@text/thing-type.shelly.shellybutton2.description</description>
|
||||
<category>WallSwitch</category>
|
||||
<channel-groups>
|
||||
@@ -130,7 +130,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellymotion">
|
||||
<label>Shelly Motion (SHMOS-01/SHMOS-02)</label>
|
||||
<label>Shelly Motion</label>
|
||||
<description>@text/thing-type.shelly.shellymotion.description</description>
|
||||
<category>MotionDetector</category>
|
||||
<channel-groups>
|
||||
@@ -144,7 +144,7 @@
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellytrv">
|
||||
<label>Shelly TRV (SHTRV-01)</label>
|
||||
<label>Shelly TRV</label>
|
||||
<description>@text/thing-type.shelly.shellytrv.description</description>
|
||||
<category>RadiatorControl</category>
|
||||
<channel-groups>
|
||||
@@ -210,7 +210,6 @@
|
||||
<state readOnly="false"></state>
|
||||
</channel-type>
|
||||
|
||||
|
||||
<channel-group-type id="sensorData">
|
||||
<label>@text/channel-group-type.shelly.sensorData.label</label>
|
||||
<description>@text/channel-group-type.shelly.sensorData.description</description>
|
||||
@@ -457,7 +456,7 @@
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>@text/channel-type.shelly.sensorADC.label</label>
|
||||
<description>@text/channel-type.shelly.sensorADC.description</description>
|
||||
<state readOnly="true" pattern="%.0f %unit%">
|
||||
<state readOnly="true" pattern="%.3f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="shelly"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="shellyplus1">
|
||||
<label>ShellyPlus 1</label>
|
||||
<description>@text/thing-type.shelly.shellyplus1.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="sensors" typeId="externalSensors"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplus1pm">
|
||||
<label>ShellyPlus 1PM</label>
|
||||
<description>@text/thing-type.shelly.shellyplus1pm.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="sensors" typeId="externalSensors"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplus2pm-relay">
|
||||
<label>ShellyPlus 2 Relay</label>
|
||||
<description>@text/thing-type.shelly.shellyplus2-relay.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel"/>
|
||||
<channel-group id="meter1" typeId="meter"/>
|
||||
<channel-group id="relay2" typeId="relayChannel"/>
|
||||
<channel-group id="meter2" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplus2pm-roller">
|
||||
<label>ShellyPlus 2PM Roller</label>
|
||||
<description>@text/thing-type.shelly.shellyplus2pm-roller.description</description>
|
||||
<category>Rollershutter</category>
|
||||
<channel-groups>
|
||||
<channel-group id="roller" typeId="rollerControl"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:roller-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
|
||||
<thing-type id="shellyplusi4">
|
||||
<label>ShellyPlus i4</label>
|
||||
<description>@text/thing-type.shelly.shellyplusi4.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="status1" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="status2" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="status3" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel3.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="status4" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel4.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplusi4dc">
|
||||
<label>ShellyPlus i4DC</label>
|
||||
<description>@text/thing-type.shelly.shellyplusi4dc.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="status1" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="status2" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="status3" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel3.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="status4" typeId="ixChannel">
|
||||
<label>@text/channel-group-type.shelly.ixChannel4.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
|
||||
<thing-type id="shellypro1">
|
||||
<label>ShellyPro 1</label>
|
||||
<description>@text/thing-type.shelly.shellypro1.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="sensors" typeId="externalSensors"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellypro1pm">
|
||||
<label>ShellyPro 1PM</label>
|
||||
<description>@text/thing-type.shelly.shellypro1pm.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="sensors" typeId="externalSensors"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellypro2-relay">
|
||||
<label>ShellyPro 2 Relay</label>
|
||||
<description>@text/thing-type.shelly.shellypro2-relay.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellypro2pm-relay">
|
||||
<label>ShellyPro 2PM Relay</label>
|
||||
<description>@text/thing-type.shelly.shellypro2pm-relay.description</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellypro2pm-roller">
|
||||
<label>ShellyPro 2PM Roller</label>
|
||||
<description>@text/thing-type.shelly.shellypro2pm-roller.description</description>
|
||||
<category>Rollershutter</category>
|
||||
<channel-groups>
|
||||
<channel-group id="roller" typeId="rollerControl"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:roller-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellypro3">
|
||||
<label>ShellyPro 3</label>
|
||||
<description>@text/thing-type.shelly.shellypro3.description</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay3" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel3.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay4" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel4.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
|
||||
<thing-type id="shellypro4pm">
|
||||
<label>ShellyPro 4PM</label>
|
||||
<description>@text/thing-type.shelly.shellypro4pm.description</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter1.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter2.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay3" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel3.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter3" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter3.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay4" typeId="relayChannel">
|
||||
<label>@text/channel-group-type.shelly.relayChannel4.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter4" typeId="meter">
|
||||
<label>@text/channel-group-type.shelly.meter4.label</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:relay-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="shelly"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="shellyplusht">
|
||||
<label>ShellyPlus H&T</label>
|
||||
<description>@text/thing-type.shelly.shellypludht.description</description>
|
||||
<category>Sensor</category>
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="sensorData"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serviceName</representation-property>
|
||||
<config-description-ref uri="thing-type:shelly:battery-gen2"/>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -56,9 +56,9 @@
|
||||
</td>
|
||||
<td>
|
||||
<div title="Refresh Device Status">
|
||||
<a href="${uri}/overview?action=refresh&uid=${uid}">
|
||||
<img src="${uri}/images/refresh.png" class="icon"/>
|
||||
</a>
|
||||
<a href="${uri}/overview?action=refresh&uid=${uid}">
|
||||
<img src="${uri}/images/refresh.png" class="icon"/>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@@ -101,6 +101,6 @@
|
||||
<td align="right">${deviceRestarts}</td>
|
||||
<td align="right">${timeoutErrors}</td>
|
||||
<td align="right">${timeoutsRecovered}</td>
|
||||
<td align="right" title="CoIoT Status: ${coiotStatus}">${coiotMessages}</td>
|
||||
<td align="right">${coiotErrors}</td>
|
||||
<td align="right" title="CoIOT Status: ${coiotStatus}">${protocolMessages}</td>
|
||||
<td align="right">${protocolErrors}</td>
|
||||
</tr>
|
||||
|
||||
@@ -55,6 +55,6 @@
|
||||
<th>Device Restarts</th>
|
||||
<th>Timeout Errors</th>
|
||||
<th>Timeouts Recovered</th>
|
||||
<th>CoIOT Messages</th>
|
||||
<th>CoIOT Errors</th>
|
||||
<th>Protocol Messages</th>
|
||||
<th>Protocol Errors</th>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user