From 45786fa12c101bff247b89840bfebb8cc4bdf135 Mon Sep 17 00:00:00 2001 From: Markus Michels Date: Sat, 18 Nov 2023 16:28:44 +0100 Subject: [PATCH] =?UTF-8?q?[shelly]=C2=A0Fix=20Gen2=20auth,=20improved=20s?= =?UTF-8?q?ecurity=20for=20Gen1=20auth,=20improved=20discovery=20(#15898)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Markus Michels --- .../internal/api/ShellyApiException.java | 4 + .../internal/api/ShellyApiInterface.java | 4 +- .../internal/api/ShellyDeviceProfile.java | 48 ++++++------ .../shelly/internal/api/ShellyHttpClient.java | 6 +- .../internal/api1/Shelly1ApiJsonDTO.java | 5 +- .../internal/api1/Shelly1CoapHandler.java | 4 +- .../shelly/internal/api1/Shelly1HttpApi.java | 36 +++++++-- .../internal/api2/Shelly2ApiClient.java | 5 ++ .../internal/api2/Shelly2ApiJsonDTO.java | 7 +- .../shelly/internal/api2/Shelly2ApiRpc.java | 54 ++++++-------- .../shelly/internal/api2/ShellyBluApi.java | 14 ++-- .../discovery/ShellyDiscoveryParticipant.java | 15 +++- .../internal/handler/ShellyBaseHandler.java | 73 ++++++++----------- .../internal/handler/ShellyLightHandler.java | 4 +- .../manager/ShellyManagerActionPage.java | 2 +- .../manager/ShellyManagerOtaPage.java | 5 +- .../manager/ShellyManagerOverviewPage.java | 6 +- .../internal/manager/ShellyManagerPage.java | 3 +- 18 files changed, 164 insertions(+), 131 deletions(-) diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java index 845219bf92c..6c6dc2a9588 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiException.java @@ -118,6 +118,10 @@ public class ShellyApiException extends Exception { || exType == NoRouteToHostException.class; } + public boolean isNoRouteToHost() { + return getCauseClass() == NoRouteToHostException.class; + } + public boolean isUnknownHost() { return getCauseClass() == UnknownHostException.class; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java index 8b7716007d8..1f9443824e4 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java @@ -15,6 +15,7 @@ package org.openhab.binding.shelly.internal.api; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; 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.ShellySettingsDevice; @@ -42,7 +43,8 @@ public interface ShellyApiInterface { ShellySettingsDevice getDeviceInfo() throws ShellyApiException; - ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException; + ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice device) + throws ShellyApiException; ShellySettingsStatus getStatus() throws ShellyApiException; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java index 814917e26ba..6f96910b560 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java @@ -24,6 +24,7 @@ import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDimmer; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsGlobal; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput; @@ -47,24 +48,21 @@ import com.google.gson.Gson; @NonNullByDefault public class ShellyDeviceProfile { private final Logger logger = LoggerFactory.getLogger(ShellyDeviceProfile.class); - private static final Pattern VERSION_PATTERN = Pattern.compile("v\\d+\\.\\d+\\.\\d+(-[a-z0-9]*)?"); + private static final Pattern GEN1_VERSION_PATTERN = Pattern.compile("v\\d+\\.\\d+\\.\\d+(-[a-z0-9]*)?"); + private static final Pattern GEN2_VERSION_PATTERN = Pattern.compile("\\d+\\.\\d+\\.\\d+(-[a-fh-z0-9]*)?"); public boolean initialized = false; // true when initialized public String thingName = ""; - public String deviceType = ""; public boolean extFeatures = false; public String settingsJson = ""; + public ShellySettingsDevice device = new ShellySettingsDevice(); public ShellySettingsGlobal settings = new ShellySettingsGlobal(); public ShellySettingsStatus status = new ShellySettingsStatus(); - public String hostname = ""; public String name = ""; - public String model = ""; - public String mode = ""; public boolean discoverable = true; - public boolean auth = false; public boolean alwaysOn = true; public boolean isGen2 = false; public boolean isBlu = false; @@ -72,7 +70,6 @@ public class ShellyDeviceProfile { public String hwRev = ""; public String hwBatchId = ""; - public String mac = ""; public String fwVersion = ""; public String fwDate = ""; @@ -118,10 +115,13 @@ public class ShellyDeviceProfile { public ShellyDeviceProfile() { } - public ShellyDeviceProfile initialize(String thingType, String jsonIn) throws ShellyApiException { + public ShellyDeviceProfile initialize(String thingType, String jsonIn, @Nullable ShellySettingsDevice device) + throws ShellyApiException { Gson gson = new Gson(); - initialized = false; + if (device != null) { + this.device = device; + } initFromThingType(thingType); @@ -141,36 +141,36 @@ public class ShellyDeviceProfile { settings = fromJson(gson, json, ShellySettingsGlobal.class); // General settings + if (getString(device.hostname).isEmpty() && !getString(device.mac).isEmpty()) { + device.hostname = device.mac.length() >= 12 ? "shelly-" + device.mac.toUpperCase().substring(6, 11) + : "unknown"; + } name = getString(settings.name); - deviceType = getString(settings.device.type); - mac = getString(settings.device.mac); - hostname = !getString(settings.device.hostname).isEmpty() ? settings.device.hostname.toLowerCase() - : mac.length() >= 12 ? "shelly-" + mac.toUpperCase().substring(6, 11) : "unknown"; - mode = getString(settings.mode).toLowerCase(); hwRev = settings.hwinfo != null ? getString(settings.hwinfo.hwRevision) : ""; hwBatchId = settings.hwinfo != null ? getString(settings.hwinfo.batchId.toString()) : ""; - fwDate = substringBefore(settings.fw, "/"); - fwVersion = extractFwVersion(settings.fw); + fwDate = substringBefore(device.fw, "-"); + fwVersion = extractFwVersion(device.fw); ShellyVersionDTO version = new ShellyVersionDTO(); extFeatures = version.compare(fwVersion, SHELLY_API_FW_110) >= 0; discoverable = (settings.discoverable == null) || settings.discoverable; + String mode = getString(device.mode); isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER); inColor = isLight && mode.equalsIgnoreCase(SHELLY_MODE_COLOR); - numRelays = !isLight ? getInteger(settings.device.numOutputs) : 0; + numRelays = !isLight ? getInteger(device.numOutputs) : 0; if ((numRelays > 0) && (settings.relays == null)) { numRelays = 0; } hasRelays = (numRelays > 0) || isDimmer; - numRollers = getInteger(settings.device.numRollers); + numRollers = getInteger(device.numRollers); numInputs = settings.inputs != null ? settings.inputs.size() : hasRelays ? isRoller ? 2 : 1 : 0; isEMeter = settings.emeters != null; - numMeters = !isEMeter ? getInteger(settings.device.numMeters) : getInteger(settings.device.numEMeters); + numMeters = !isEMeter ? getInteger(device.numMeters) : getInteger(device.numEMeters); if ((numMeters == 0) && isLight) { // RGBW2 doesn't report, but has one - numMeters = inColor ? 1 : getInteger(settings.device.numOutputs); + numMeters = inColor ? 1 : getInteger(device.numOutputs); } initialized = true; @@ -199,8 +199,9 @@ public class ShellyDeviceProfile { isGen2 = isGeneration2(thingType); isBlu = isBluSeries(thingType); // e.g. SBBT for BLU Button - isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2) - || deviceType.equalsIgnoreCase(SHELLYDT_PLUSDIMMERUS) + String type = getString(device.type); + isDimmer = type.equalsIgnoreCase(SHELLYDT_DIMMER) || type.equalsIgnoreCase(SHELLYDT_DIMMER2) + || type.equalsIgnoreCase(SHELLYDT_PLUSDIMMERUS) || thingType.equalsIgnoreCase(THING_TYPE_SHELLYPLUSDIMMERUS_STR); isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR); isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR) @@ -390,7 +391,8 @@ public class ShellyDeviceProfile { .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); + Matcher matcher = version.startsWith("v") ? GEN1_VERSION_PATTERN.matcher(vers) + : GEN2_VERSION_PATTERN.matcher(vers); if (matcher.find()) { return matcher.group(0); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java index 5b04bc58a60..7ed4363450b 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java @@ -69,6 +69,7 @@ public class ShellyHttpClient { protected int timeoutErrors = 0; protected int timeoutsRecovered = 0; private ShellyDeviceProfile profile; + protected boolean basicAuth = false; public ShellyHttpClient(String thingName, ShellyThingInterface thing) { this(thingName, thing.getThingConfig(), thing.getHttpClient()); @@ -83,9 +84,6 @@ public class ShellyHttpClient { this.httpClient.setConnectTimeout(SHELLY_API_TIMEOUT_MS); } - public void initialize() throws ShellyApiException { - } - public void setConfig(String thingName, ShellyThingConfiguration config) { this.thingName = thingName; this.config = config; @@ -167,7 +165,7 @@ public class ShellyHttpClient { authHeader = formatAuthResponse(uri, buildAuthResponse(uri, auth, SHELLY2_AUTHDEF_USER, config.password)); } else { - if (!uri.equals(SHELLYRPC_ENDPOINT)) { + if (basicAuth) { String bearer = config.userId + ":" + config.password; authHeader = HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(bearer.getBytes()); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java index c5304fe4e1c..d836e728f2c 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java @@ -261,6 +261,10 @@ public class Shelly1ApiJsonDTO { public static class ShellySettingsDevice { public String type; + public String mode; // Gen 1 + public String id; // Gen2: service name + public String name; // Gen2: configured device name + public String profile; // Gen 2 public String mac; public String hostname; public String fw; @@ -563,7 +567,6 @@ public class Shelly1ApiJsonDTO { public static class ShellySettingsGlobal { // https://shelly-api-docs.shelly.cloud/#shelly1pm-settings - public ShellySettingsDevice device = new ShellySettingsDevice(); @SerializedName("wifi_ap") public ShellySettingsWiFiAp wifiAp = new ShellySettingsWiFiAp(); @SerializedName("wifi_sta") diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java index 23c9cdede01..3de692e2240 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java @@ -182,10 +182,10 @@ public class Shelly1CoapHandler implements Shelly1CoapListener { for (Option opt : options) { if (opt.getNumber() == COIOT_OPTION_GLOBAL_DEVID) { String devid = opt.getStringValue(); - if (devid.contains("#") && profile.mac != null) { + if (devid.contains("#") && profile.device.mac != null) { // Format: ## String macid = substringBetween(devid, "#", "#"); - if (profile.mac.toUpperCase().contains(macid.toUpperCase())) { + if (getString(profile.device.mac).toUpperCase().contains(macid.toUpperCase())) { match = true; break; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java index 69a4d7bd501..330b2be3606 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1HttpApi.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.Map; 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; @@ -79,10 +80,26 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa this.profile = new ShellyDeviceProfile(); } + @Override + public void initialize() throws ShellyApiException { + profile.device = getDeviceInfo(); + } + @Override public ShellySettingsDevice getDeviceInfo() throws ShellyApiException { ShellySettingsDevice info = callApi(SHELLY_URL_DEVINFO, ShellySettingsDevice.class); info.gen = 1; + basicAuth = getBool(info.auth); + + if (getString(info.mode).isEmpty()) { // older Gen1 Firmware + if (getInteger(info.numRollers) > 0) { + info.mode = SHELLY_CLASS_ROLLER; + } else if (getInteger(info.numOutputs) > 0) { + info.mode = SHELLY_CLASS_RELAY; + } else { + info.mode = ""; + } + } return info; } @@ -104,7 +121,14 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa * @throws ShellyApiException */ @Override - public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException { + public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice device) + throws ShellyApiException { + if (device != null) { + profile.device = device; + } + if (profile.device.type == null) { + profile.device = getDeviceInfo(); + } String json = httpRequest(SHELLY_URL_SETTINGS); if (json.contains("\"type\":\"SHDM-")) { logger.trace("{}: Detected a Shelly Dimmer: fix Json (replace lights[] tag with dimmers[]", thingName); @@ -112,10 +136,10 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa } // Map settings to device profile for Light and Sense - profile.initialize(thingType, json); + profile.initialize(thingType, json, profile.device); // 2nd level initialization - profile.thingName = profile.hostname; + profile.thingName = profile.device.hostname; if (profile.isLight && (profile.numMeters == 0)) { logger.debug("{}: Get number of meters from light status", thingName); ShellyStatusLight status = getLightStatus(); @@ -396,10 +420,10 @@ public class Shelly1HttpApi extends ShellyHttpClient implements ShellyApiInterfa */ @Override public void setLightMode(String mode) throws ShellyApiException { - if (!mode.isEmpty() && !profile.mode.equals(mode)) { + if (!mode.isEmpty() && !profile.device.mode.equals(mode)) { setLightSetting(SHELLY_API_MODE, mode); - profile.mode = mode; - profile.inColor = profile.isLight && profile.mode.equalsIgnoreCase(SHELLY_MODE_COLOR); + profile.device.mode = mode; + profile.inColor = profile.isLight && mode.equalsIgnoreCase(SHELLY_MODE_COLOR); } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java index a8c771fe67f..2805d131010 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiClient.java @@ -148,6 +148,11 @@ public class Shelly2ApiClient extends ShellyHttpClient { MAP_ROLLER_STATE.put(SHELLY2_RSTATE_STOPPED, SHELLY_RSTATE_STOP); MAP_ROLLER_STATE.put(SHELLY2_RSTATE_CALIB, SHELLY2_RSTATE_CALIB); // Gen2-only } + protected static final Map MAP_PROFILE = new HashMap<>(); + static { + MAP_PROFILE.put(SHELLY_CLASS_RELAY, SHELLY2_PROFILE_RELAY); + MAP_PROFILE.put(SHELLY_CLASS_ROLLER, SHELLY2_PROFILE_COVER); + } protected @Nullable ArrayList<@Nullable ShellySettingsRelay> fillRelaySettings(ShellyDeviceProfile profile, Shelly2GetConfigResult dc) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java index 672f9e3b8cc..ea309929f51 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiJsonDTO.java @@ -82,7 +82,7 @@ public class Shelly2ApiJsonDTO { // Component types public static final String SHELLY2_PROFILE_RELAY = "switch"; - public static final String SHELLY2_PROFILE_ROLLER = "cover"; + public static final String SHELLY2_PROFILE_COVER = "cover"; // Button types/modes public static final String SHELLY2_BTNT_MOMENTARY = "momentary"; @@ -183,13 +183,14 @@ public class Shelly2ApiJsonDTO { public String id; public String mac; public String model; + public String profile; public Integer gen; @SerializedName("fw_id") - public String firmware; + public String fw; public String ver; public String app; @SerializedName("auth_en") - public Boolean authEnable; + public Boolean auth; @SerializedName("auth_domain") public String authDomain; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java index d35273bcccc..17efc1d84eb 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/Shelly2ApiRpc.java @@ -113,11 +113,6 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac 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); - } } /** @@ -161,9 +156,17 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac @SuppressWarnings("null") @Override - public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException { + public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice devInfo) + throws ShellyApiException { ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile(); + if (devInfo != null) { + profile.device = devInfo; + } + if (profile.device.type == null) { + profile.device = getDeviceInfo(); + } + Shelly2GetConfigResult dc = apiRequest(SHELLYRPC_METHOD_GETCONFIG, null, Shelly2GetConfigResult.class); profile.isGen2 = true; profile.settingsJson = gson.toJson(dc); @@ -195,29 +198,18 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac 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; + if (getString(profile.device.mode).isEmpty() && profile.hasRelays) { + profile.device.mode = profile.isRoller ? SHELLY_CLASS_ROLLER : SHELLY_CLASS_RELAY; } - ShellySettingsDevice device = getDeviceInfo(); - profile.settings.device = device; - if (!getString(device.fw).isEmpty()) { - profile.fwDate = substringBefore(device.fw, "/"); - profile.fwVersion = profile.status.update.oldVersion = "v" + substringAfter(device.fw, "/"); - } - - profile.hostname = device.hostname; - profile.deviceType = device.type; - profile.mac = device.mac; - profile.auth = device.auth; + ShellySettingsDevice device = profile.device; profile.isGen2 = device.gen == 2; if (config.serviceName.isEmpty()) { - config.serviceName = getString(profile.hostname); + config.serviceName = getString(profile.device.hostname); } - profile.fwDate = substringBefore(device.fw, "/"); - profile.fwVersion = substringBefore(ShellyDeviceProfile.extractFwVersion(device.fw.replace("/", "/v")), "-"); - profile.status.update.oldVersion = profile.fwVersion; + profile.settings.fw = device.fw; + profile.fwDate = substringBefore(substringBefore(device.fw, "/"), "-"); + profile.fwVersion = profile.status.update.oldVersion = ShellyDeviceProfile.extractFwVersion(device.fw); profile.status.hasUpdate = profile.status.update.hasUpdate = false; if (dc.eth != null) { @@ -741,11 +733,13 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac Shelly2DeviceSettings device = callApi("/shelly", Shelly2DeviceSettings.class); ShellySettingsDevice info = new ShellySettingsDevice(); info.hostname = getString(device.id); - info.fw = getString(device.firmware); + info.name = getString(device.name); + info.fw = getString(device.fw); info.type = getString(device.model); info.mac = getString(device.mac); - info.auth = getBool(device.authEnable); + info.auth = getBool(device.auth); info.gen = getInteger(device.gen); + info.mode = mapValue(MAP_PROFILE, device.profile); return info; } @@ -770,16 +764,16 @@ public class Shelly2ApiRpc extends Shelly2ApiClient implements ShellyApiInterfac 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); + status.update.newVersion = ShellyDeviceProfile + .extractFwVersion(getString(ds.sys.availableUpdates.stable.version)); status.hasUpdate = new ShellyVersionDTO().compare(profile.fwVersion, status.update.newVersion) < 0; } if (ds.sys.availableUpdates.beta != null) { - status.update.betaVersion = "v" + getString(ds.sys.availableUpdates.beta.version); + status.update.betaVersion = ShellyDeviceProfile + .extractFwVersion(getString(ds.sys.availableUpdates.beta.version)); status.hasUpdate = new ShellyVersionDTO().compare(profile.fwVersion, status.update.betaVersion) < 0; } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java index 533d4448e89..5eabce2a73d 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api2/ShellyBluApi.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.shelly.internal.api.ShellyApiException; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputState; @@ -119,9 +120,13 @@ public class ShellyBluApi extends Shelly2ApiRpc { } @Override - public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException { + public ShellyDeviceProfile getDeviceProfile(String thingType, @Nullable ShellySettingsDevice devInfo) + throws ShellyApiException { ShellyDeviceProfile profile = thing != null ? getProfile() : new ShellyDeviceProfile(); + if (devInfo != null) { + profile.device = devInfo; + } profile.isBlu = true; profile.settingsJson = "{}"; profile.thingName = thingName; @@ -131,13 +136,8 @@ public class ShellyBluApi extends Shelly2ApiRpc { } 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); + config.serviceName = getString(profile.device.hostname); } profile.fwDate = substringBefore(device.fw, "/"); profile.fwVersion = substringBefore(ShellyDeviceProfile.extractFwVersion(device.fw.replace("/", "/v")), "-"); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java index 967a6586419..793b710f7db 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java @@ -31,6 +31,7 @@ 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.ShellySettingsDevice; import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi; import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc; import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration; @@ -144,17 +145,23 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant { boolean gen2 = "2".equals(service.getPropertyString("gen")); ShellyApiInterface api = null; + ShellySettingsDevice devInfo; try { api = gen2 ? new Shelly2ApiRpc(name, config, httpClient) : new Shelly1HttpApi(name, config, httpClient); api.initialize(); - profile = api.getDeviceProfile(thingType); + devInfo = api.getDeviceInfo(); + model = devInfo.type; + if (devInfo.name != null) { + deviceName = devInfo.name; + } + profile = api.getDeviceProfile(thingType, devInfo); + api.close(); logger.debug("{}: Shelly settings : {}", name, profile.settingsJson); deviceName = profile.name; - model = profile.deviceType; - mode = profile.mode; + mode = devInfo.mode; properties = ShellyBaseHandler.fillDeviceProperties(profile); logger.trace("{}: thingType={}, deviceType={}, mode={}, symbolic name={}", name, thingType, - profile.deviceType, mode.isEmpty() ? "" : mode, deviceName); + devInfo.type, mode.isEmpty() ? "" : mode, deviceName); // get thing type from device name thingUID = ShellyThingCreator.getThingUID(name, model, mode, false); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java index c76a181e458..1a35a432b1a 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java @@ -273,45 +273,41 @@ public abstract class ShellyBaseHandler extends BaseThingHandler updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING, messages.get("status.unknown.initializing")); - profile.initFromThingType(thingType); // do some basic initialization - // 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) + profile.initFromThingType(thingType); if (coap != null && config.eventsCoIoT && !profile.alwaysOn) { coap.start(thingName, config); } // Initialize API access, exceptions will be catched by initialize() api.initialize(); - ShellySettingsDevice devInfo = api.getDeviceInfo(); - if (getBool(devInfo.auth) && config.password.isEmpty()) { + ShellySettingsDevice device = profile.device = api.getDeviceInfo(); + if (getBool(device.auth) && config.password.isEmpty()) { setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials"); return false; } if (config.serviceName.isEmpty()) { - config.serviceName = getString(profile.hostname).toLowerCase(); + config.serviceName = getString(device.hostname).toLowerCase(); } api.setConfig(thingName, config); - ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType); - tmpPrf.isGen2 = gen2; - tmpPrf.auth = devInfo.auth; // missing in /settings - + ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType, profile.device); + String mode = getString(tmpPrf.device.mode); if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) { - changeThingType(thingName, tmpPrf.mode); + changeThingType(thingName, mode); return false; // force re-initialization } // 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", tmpPrf.mode, - reqMode); + if (!reqMode.isEmpty() && !mode.equals(reqMode)) { + setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode", mode, reqMode); return false; } - if (!getString(devInfo.coiot).isEmpty()) { + if (!getString(tmpPrf.device.coiot).isEmpty()) { // New Shelly devices might use a different endpoint for the CoAP listener - tmpPrf.coiotEndpoint = devInfo.coiot; + tmpPrf.coiotEndpoint = tmpPrf.device.coiot; } if (tmpPrf.settings.sleepMode != null && !tmpPrf.isTRV) { // Sensor, usually 12h, H&T in USB mode 10min @@ -566,13 +562,10 @@ public abstract class ShellyBaseHandler extends BaseThingHandler status = "offline.conf-error-access-denied"; } else if (isWatchdogStarted()) { if (!isWatchdogExpired()) { + logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); if (profile.alwaysOn) { // suppress for battery powered sensors logger.debug("{}: Ignore API Timeout on {} {}, retry later", thingName, res.method, res.url); } - } else { - if (isThingOnline()) { - status = "offline.status-error-watchdog"; - } } } else if (e.isJSONException()) { status = "offline.status-error-unexpected-api-result"; @@ -606,22 +599,18 @@ public abstract 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.device.hostname, profile.device.type, 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:{}), ext. Switch Add-On: {}\ - ,isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}, BLU Gateway support: {}\ - ,alwaysOn:{}, updatePeriod:{}sec\ - """, - thingName, profile.hasRelays, profile.numRelays, profile.isRoller, profile.numRollers, profile.isDimmer, - profile.numMeters, profile.isEMeter, profile.settings.extSwitch != null ? "installed" : "n/a", - 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, config.enableBluGateway); + logger.debug("{}: Shelly settings info for {}: {}", thingName, profile.device.hostname, profile.settingsJson); + logger.debug("{}: Device " + + "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{}), ext. Switch Add-On: {}" + + ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}, BLU Gateway support: {}" + + ",alwaysOn:{}, updatePeriod:{}sec", thingName, profile.hasRelays, profile.numRelays, profile.isRoller, + profile.numRollers, profile.isDimmer, profile.numMeters, profile.isEMeter, + profile.settings.extSwitch != null ? "installed" : "n/a", 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, config.enableBluGateway); if (profile.status.extTemperature != null || profile.status.extHumidity != null || profile.status.extVoltage != null || profile.status.extAnalogInput != null) { logger.debug("{}: Shelly Add-On detected with at least 1 external sensor", thingName); @@ -1059,15 +1048,15 @@ public abstract class ShellyBaseHandler extends BaseThingHandler // no fw version available (e.g. BLU device) return; } - ShellyVersionDTO version = new ShellyVersionDTO(); if (version.checkBeta(getString(prf.fwVersion))) { - logger.info("{}: {}", prf.hostname, messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate)); + logger.info("{}: {}", prf.device.hostname, + messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate)); } else { 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, minVersion)); + logger.warn("{}: {}", prf.device.hostname, + messages.get("versioncheck.beta", prf.fwVersion, prf.fwDate)); } } if (!gen2 && bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0) @@ -1450,10 +1439,10 @@ public abstract class ShellyBaseHandler extends BaseThingHandler Map properties = new TreeMap<>(); properties.put(PROPERTY_VENDOR, VENDOR); if (profile.isInitialized()) { - properties.put(PROPERTY_MODEL_ID, getString(profile.settings.device.type)); - properties.put(PROPERTY_MAC_ADDRESS, profile.mac); + properties.put(PROPERTY_MODEL_ID, getString(profile.device.type)); + properties.put(PROPERTY_MAC_ADDRESS, profile.device.mac); properties.put(PROPERTY_FIRMWARE_VERSION, profile.fwVersion + "/" + profile.fwDate); - properties.put(PROPERTY_DEV_MODE, profile.mode); + properties.put(PROPERTY_DEV_MODE, profile.device.mode); if (profile.hasRelays) { properties.put(PROPERTY_NUM_RELAYS, String.valueOf(profile.numRelays)); properties.put(PROPERTY_NUM_ROLLERS, String.valueOf(profile.numRollers)); @@ -1481,7 +1470,7 @@ public abstract class ShellyBaseHandler extends BaseThingHandler try { refreshSettings |= forceRefresh; if (refreshSettings) { - profile = api.getDeviceProfile(thingType); + profile = api.getDeviceProfile(thingType, null); if (!isThingOnline()) { logger.debug("{}: Device profile re-initialized (thingType={})", thingName, thingType); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java index 8fc49526f68..b9a901612bb 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java @@ -82,7 +82,7 @@ public class ShellyLightHandler extends ShellyBaseHandler { try { ShellyColorUtils oldCol = getCurrentColors(lightId); - oldCol.mode = profile.mode; + oldCol.mode = profile.device.mode; ShellyColorUtils col = new ShellyColorUtils(oldCol); boolean update = true; @@ -317,7 +317,7 @@ public class ShellyLightHandler extends ShellyBaseHandler { } ShellyStatusLight status = api.getLightStatus(); - logger.trace("{}: Updating light status in {} mode, {} channel(s)", thingName, profile.mode, + logger.trace("{}: Updating light status in {} mode, {} channel(s)", thingName, profile.device.mode, status.lights.size()); // In white mode we have multiple channels diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java index ef47191441e..6f6abdbd0fa 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java @@ -377,7 +377,7 @@ public class ShellyManagerActionPage extends ShellyManagerPage { list.put(ACTION_RES_STATS, "Reset Statistics"); list.put(ACTION_RESTART, "Reboot Device"); - if (gen2) { + if (!gen2 || !profile.isBlu) { list.put(ACTION_PROTECT, "Protect Device"); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOtaPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOtaPage.java index ed7efa891cb..d4ce439f1dc 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOtaPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOtaPage.java @@ -87,7 +87,7 @@ public class ShellyManagerOtaPage extends ShellyManagerPage { String deviceType = getDeviceType(properties); String uri = !url.isEmpty() && connection.equals(CONNECTION_TYPE_CUSTOM) ? url - : getFirmwareUrl(config.deviceIp, deviceType, profile.mode, version, + : getFirmwareUrl(config.deviceIp, deviceType, profile.device.mode, version, connection.equals(CONNECTION_TYPE_LOCAL)); if (connection.equalsIgnoreCase(CONNECTION_TYPE_INTERNET)) { // If target @@ -100,7 +100,8 @@ public class ShellyManagerOtaPage extends ShellyManagerPage { } } else if (connection.equalsIgnoreCase(CONNECTION_TYPE_LOCAL)) { // redirect to local server -> http://:/shelly/manager/ota?deviceType=xxx&version=xxx - String modeParm = !profile.mode.isEmpty() ? "&" + URLPARM_DEVMODE + "=" + profile.mode : ""; + String modeParm = !profile.device.mode.isEmpty() ? "&" + URLPARM_DEVMODE + "=" + profile.device.mode + : ""; url = URLPARM_URL + "=http://" + localIp + ":" + localPort + SHELLY_MGR_OTA_URI + urlEncode( "?" + URLPARM_DEVTYPE + "=" + deviceType + modeParm + "&" + URLPARM_VERSION + "=" + version); } else if (connection.equalsIgnoreCase(CONNECTION_TYPE_CUSTOM)) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java index ddc696e8054..7c0bdd0218d 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerOverviewPage.java @@ -143,7 +143,7 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage { try { 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); + FwRepoEntry fw = getFirmwareRepoEntry(deviceType, profile.device.mode); pVersion = extractFwVersion(fw.version); bVersion = extractFwVersion(fw.betaVer); @@ -238,7 +238,9 @@ public class ShellyManagerOverviewPage extends ShellyManagerPage { // return handler.getChannelValue(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE) == OnOffType.ON; return getBool(profile.status.hasUpdate); case FILTER_UNPROTECTED: - return !profile.auth; + if (profile.device.auth != null) { + return !profile.device.auth; + } case "*": default: return true; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java index a70b4472d9f..5d7e46bfa1d 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerPage.java @@ -254,7 +254,8 @@ public class ShellyManagerPage { properties.put(ATTRIBUTE_APR_TRESHOLD, profile.settings.apRoaming != null ? getOption(profile.settings.apRoaming.threshold) : "n/a"); properties.put(ATTRIBUTE_PWD_PROTECT, - profile.auth ? "enabled, user=" + getString(profile.settings.login.username) : "disabled"); + getBool(profile.device.auth) ? "enabled, user=" + getString(profile.settings.login.username) + : "disabled"); String tz = getString(profile.settings.timezone); properties.put(ATTRIBUTE_TIMEZONE, (tz.isEmpty() ? "n/a" : tz) + ", auto-detect: " + getBool(profile.settings.tzautodetect));