diff --git a/bundles/org.openhab.binding.shelly/README.md b/bundles/org.openhab.binding.shelly/README.md index 6f6ae77442a..1f686d63a32 100644 --- a/bundles/org.openhab.binding.shelly/README.md +++ b/bundles/org.openhab.binding.shelly/README.md @@ -45,8 +45,10 @@ Also check out the [Shelly Manager](doc/ShellyManager.md), which | shellyplugs | Shelly Plug-S | SHPLG-S | | shellyem | Shelly EM with integrated Power Meters | SHEM | | shellyem3 | Shelly 3EM with 3 integrated Power Meter | SHEM-3 | -| shellyrgbw2 | Shelly RGB Controller | SHRGBW2 | -| shellybulb | Shelly Bulb in Color or White Mode | SHBLB-1 | +| shellyrgbw2-color | Shelly RGBW2 Controller in Color Mode | SHRGBW2 | +| shellyrgbw2-white | Shelly RGBW2 Controller in White Mode | SHRGBW2 | +| shellybulb-color | Shelly Bulb in Color Mode | SHBLB-1 | +| shellybulb-white | Shelly Bulb in White Mode | SHBLB-1 | | shellybulbduo | Shelly Duo White | SHBDUO-1 | | shellybulbduo | Shelly Duo White G10 | SHBDUO-1 | | shellycolorbulb | Shelly Duo Color G10 | SHCB-1 | @@ -55,6 +57,7 @@ Also check out the [Shelly Manager](doc/ShellyManager.md), which | shellyflood | Shelly Flood Sensor | SHWT-1 | | shellysmoke | Shelly Smoke Sensor | SHSM-1 | | shellymotion | Shelly Motion Sensor | SHMOS-01 | +| shellymotion2 | Shelly Motion Sensor 2 | SHMOS-02 | | shellygas | Shelly Gas Sensor | SHGS-1 | | shellydw | Shelly Door/Window | SHDW-1 | | shellydw2 | Shelly Door/Window 2 | SHDW-2 | @@ -742,6 +745,23 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the `true`: Brightness will be set and device output is powered = light turns on with the new brightness `false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off. +### Shelly RGBW2 in White Mode (thing-type: shellyrgbw2-color) + +|Group |Channel |Type |read-only|Description | +|----------|-------------|---------|---------|-----------------------------------------------------------------------| +|control |power |Switch |r/w |Switch light ON/OFF | +| |input |Switch |yes |State of Input | +| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec | +| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec | +| |timerActive |Switch |yes |ON: An auto-on/off timer is active | +|color | | | |Color settings: only valid in COLOR mode | +| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, but not white | +|meter |currentWatts |Number |yes |Current power consumption in Watts (all channels) | + +Please note that the settings of channel group color are only valid in color mode and vice versa for white mode. +The current firmware doesn't support the timestamp report for the meters. +The binding emulates this by using the system time on every update. + ### Shelly RGBW2 in White Mode (thing-type: shellyrgbw2-white) |Group |Channel |Type |read-only|Description | @@ -845,6 +865,23 @@ You have a Motion controlling your light. You switch off the light and want to leave the room, but the motion sensor immediately switches light back on. Using 'sensorSleepTime' you could suppress motion events while leaving the room, e.g. for 5sec and the light doesn's switch on. +### Shelly Motion 2 (thing-type: shellymotion2) + +|Group |Channel |Type |read-only|Description | +|----------|---------------|---------|---------|---------------------------------------------------------------------| +|sensors |motion |Switch |yes |ON: Motion was detected | +| |motionTimestamp|DateTime |yes |Time when motion started/was detected | +| |lux |Number |yes |Brightness in Lux | +| |illumination |String |yes |Current illumination: dark/twilight/bright | +| |temperature |Number |yes |Temperature measured by the sensor | +| |vibration |Switch |yes |ON: Vibration detected | +| |charger |Switch |yes |ON: USB charging cable is connected external power supply activated. | +| |motionActive |Switch |yes |ON: Motion detection is currently active | +| |sensorSleepTime|Number |no |Specifies the number of sec the sensor should not report events ] +| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) | +|battery |batteryLevel |Number |yes |Battery Level in % | +| |lowBattery |Switch |yes |Low battery alert (< 20%) | + ### Shelly TRV (thing-type: shellytrv) Note: You might need to reboot the device to enable the discovery mode for 3 minutes(use the Web UI). diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java index 3b57275db70..e9efbaffeb9 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java @@ -69,9 +69,29 @@ public class ShellyBindingConstants { public static final String THING_TYPE_SHELLYSENSE_STR = "shellysense"; public static final String THING_TYPE_SHELLYTRV_STR = "shellytrv"; public static final String THING_TYPE_SHELLYMOTION_STR = "shellymotion"; + public static final String THING_TYPE_SHELLYMOTION2_STR = "shellymotion2"; public static final String THING_TYPE_SHELLYBUTTON1_STR = "shellybutton1"; public static final String THING_TYPE_SHELLYBUTTON2_STR = "shellybutton2"; public static final String THING_TYPE_SHELLYUNI_STR = "shellyuni"; + + // Shelly Plus Seriens + public static final String THING_TYPE_SHELLYPLUS1_STR = "shellyplus1"; + public static final String THING_TYPE_SHELLYPLUS1PM_STR = "shellyplus1pm"; + public static final String THING_TYPE_SHELLYPLUS2PM_RELAY_STR = "shellyplus2pm-relay"; + public static final String THING_TYPE_SHELLYPLUS2PM_ROLLER_STR = "shellyplus2pm-roller"; + public static final String THING_TYPE_SHELLYPLUSI4_STR = "shellyplusi4"; + public static final String THING_TYPE_SHELLYPLUSHT_STR = "shellyplusht"; + public static final String THING_TYPE_SHELLYPLUSPLUGUS_STR = "shellyplusplugus"; + + // Shelly Pro Series + 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_SHELLYPRO4PM_STR = "shellypro4pm"; + public static final String THING_TYPE_SHELLYPROTECTED_STR = "shellydevice"; public static final String THING_TYPE_SHELLYUNKNOWN_STR = "shellyunknown"; @@ -107,6 +127,24 @@ public class ShellyBindingConstants { public static final String SHELLYDT_UNI = "SHUNI-1"; public static final String SHELLYDT_TRV = "SHTRV-01"; + // Shelly Plus Series + public static final String SHELLYDT_PLUS1 = "SNSW-001X16EU"; + public static final String SHELLYDT_PLUS1PM = "SNSW-001P16EU"; + 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_PLUSPLUGUS = "SNPL-00116US"; + public static final String SHELLYDT_PLUSI4 = "SNSN-0024X"; + public static final String SHELLYDT_PLUSHT = "SNSN-0013A"; + + // Shelly Pro Series + public static final String SHELLYDT_PRO1 = "SPSW-001XE16EU"; + public static final String SHELLYDT_PRO1PM = "SPSW-001PE16EU"; + 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_PRO2PM_RELAY = "SPSW-002PE16EU-relay"; + public static final String SHELLYDT_PRO2PM_ROLLER = "SPSW-002PE16EU-roller"; + public static final String SHELLYDT_PRO4PM = "SPSW-004PE16EU"; + // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_SHELLY1 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1_STR); public static final ThingTypeUID THING_TYPE_SHELLY1L = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1L_STR); @@ -186,6 +224,7 @@ public class ShellyBindingConstants { public static final String PROPERTY_DEV_NAME = "deviceName"; public static final String PROPERTY_DEV_TYPE = "deviceType"; public static final String PROPERTY_DEV_MODE = "deviceMode"; + public static final String PROPERTY_DEV_GEN = "deviceGeneration"; public static final String PROPERTY_HWREV = "deviceHwRev"; public static final String PROPERTY_HWBATCH = "deviceHwBatch"; public static final String PROPERTY_UPDATE_PERIOD = "devUpdatePeriod"; @@ -340,7 +379,7 @@ public class ShellyBindingConstants { public static final String CHANNEL_BUTTON_TRIGGER2 = CHANNEL_BUTTON_TRIGGER + "2"; public static final String SERVICE_TYPE = "_http._tcp.local."; - public static final String SHELLY_API_MIN_FWVERSION = "v1.5.7";// v1.5.7+ + public static final String SHELLY_API_MIN_FWVERSION = "v1.8.2"; 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 diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java index 444c53beff0..4f5bb53a78e 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java @@ -17,7 +17,6 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -29,6 +28,8 @@ import org.openhab.binding.shelly.internal.handler.ShellyLightHandler; import org.openhab.binding.shelly.internal.handler.ShellyManagerInterface; import org.openhab.binding.shelly.internal.handler.ShellyProtectedHandler; import org.openhab.binding.shelly.internal.handler.ShellyRelayHandler; +import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; +import org.openhab.binding.shelly.internal.handler.ShellyThingTable; import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider; import org.openhab.binding.shelly.internal.util.ShellyUtils; import org.openhab.core.io.net.http.HttpClientFactory; @@ -63,7 +64,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { private final ShellyTranslationProvider messages; private final ShellyCoapServer coapServer; - private final Map deviceListeners = new ConcurrentHashMap<>(); + private final ShellyThingTable thingTable; private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration(); private String localIP = ""; private int httpPort = -1; @@ -77,8 +78,9 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { */ @Activate public ShellyHandlerFactory(@Reference NetworkAddressService networkAddressService, - @Reference ShellyTranslationProvider translationProvider, @Reference HttpClientFactory httpClientFactory, - ComponentContext componentContext, Map configProperties) { + @Reference ShellyTranslationProvider translationProvider, @Reference ShellyThingTable thingTable, + @Reference HttpClientFactory httpClientFactory, ComponentContext componentContext, + Map configProperties) { logger.debug("Activate Shelly HandlerFactory"); super.activate(componentContext); messages = translationProvider; @@ -100,6 +102,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { } logger.debug("Using OH HTTP port {}", httpPort); + this.thingTable = thingTable; this.coapServer = new ShellyCoapServer(); // Promote Shelly Manager usage @@ -137,8 +140,8 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { if (handler != null) { String uid = thing.getUID().getAsString(); - deviceListeners.put(uid, handler); - logger.debug("Thing handler for uid {} added, total things = {}", uid, deviceListeners.size()); + thingTable.addThing(uid, handler); + logger.debug("Thing handler for uid {} added, total things = {}", uid, thingTable.size()); return handler; } @@ -147,7 +150,11 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { } public Map getThingHandlers() { - return new HashMap<>(deviceListeners); + Map table = new HashMap<>(); + for (Map.Entry entry : thingTable.getTable().entrySet()) { + table.put(entry.getKey(), (ShellyManagerInterface) entry.getValue()); + } + return table; } /** @@ -157,7 +164,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { protected synchronized void removeHandler(@NonNull ThingHandler thingHandler) { if (thingHandler instanceof ShellyBaseHandler) { String uid = thingHandler.getThing().getUID().getAsString(); - deviceListeners.remove(uid); + thingTable.removeThing(uid); } } @@ -172,8 +179,8 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { public void onEvent(String ipAddress, String deviceName, String componentIndex, String eventType, Map parameters) { logger.trace("{}: Dispatch event to thing handler", deviceName); - for (Map.Entry listener : deviceListeners.entrySet()) { - ShellyBaseHandler thingHandler = listener.getValue(); + for (Map.Entry listener : thingTable.getTable().entrySet()) { + ShellyBaseHandler thingHandler = (ShellyBaseHandler) listener.getValue(); if (thingHandler.onEvent(ipAddress, deviceName, componentIndex, eventType, parameters)) { // event processed return; 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 new file mode 100644 index 00000000000..a7238a8b732 --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java @@ -0,0 +1,124 @@ +/** + * 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.api; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyOtaCheckResult; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDevice; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsLogin; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLight; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor; +import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; + +/** + * The {@link ShellyApiInterface} Defines device API + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +public interface ShellyApiInterface { + public boolean isInitialized(); + + public void setConfig(String thingName, ShellyThingConfiguration config); + + public ShellySettingsDevice getDeviceInfo() throws ShellyApiException; + + public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException; + + public ShellySettingsStatus getStatus() throws ShellyApiException; + + public void setLedStatus(String ledName, Boolean value) throws ShellyApiException; + + public void setSleepTime(int value) throws ShellyApiException; + + public ShellyStatusRelay getRelayStatus(Integer relayIndex) throws ShellyApiException; + + public void setRelayTurn(int id, String turnMode) throws ShellyApiException; + + public ShellyControlRoller getRollerStatus(int rollerIndex) throws ShellyApiException; + + public void setRollerTurn(int relayIndex, String turnMode) throws ShellyApiException; + + public void setRollerPos(int relayIndex, int position) throws ShellyApiException; + + public void setTimer(int index, String timerName, int value) throws ShellyApiException; + + public ShellyStatusSensor getSensorStatus() throws ShellyApiException; + + public ShellyStatusLight getLightStatus() throws ShellyApiException; + + public ShellyShortLightStatus getLightStatus(int index) throws ShellyApiException; + + public void setLightMode(String mode) throws ShellyApiException; + + public void setLightParm(int lightIndex, String parm, String value) throws ShellyApiException; + + public void setLightParms(int lightIndex, Map parameters) throws ShellyApiException; + + public ShellyShortLightStatus setLightTurn(int id, String turnMode) throws ShellyApiException; + + public void setBrightness(int id, int brightness, boolean autoOn) throws ShellyApiException; + + // Valve + public void setValveMode(int id, boolean auto) throws ShellyApiException; + + public void setValveTemperature(int valveId, int value) throws ShellyApiException; + + public void setValveProfile(int valveId, int value) throws ShellyApiException; + + public void setValvePosition(int valveId, double value) throws ShellyApiException; + + public void setValveBoostTime(int valveId, int value) throws ShellyApiException; + + public void startValveBoost(int valveId, int value) throws ShellyApiException; + + public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException; + + public ShellySettingsLogin getLoginSettings() throws ShellyApiException; + + public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException; + + public String setWiFiRecovery(boolean enable) throws ShellyApiException; + + public String deviceReboot() throws ShellyApiException; + + public String setDebug(boolean enabled) throws ShellyApiException; + + public String getDebugLog(String id) throws ShellyApiException; + + public String setCloud(boolean enabled) throws ShellyApiException; + + public String setApRoaming(boolean enable) throws ShellyApiException; + + public String factoryReset() throws ShellyApiException; + + public String resetStaCache() throws ShellyApiException; + + public int getTimeoutsRecovered(); + + public int getTimeoutErrors(); + + public String getCoIoTDescription() throws ShellyApiException; + + public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException; + + public void setActionURLs() throws ShellyApiException; + + public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException; +} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java index 83ea6a763c5..39158923a03 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java @@ -60,6 +60,7 @@ public class ShellyApiJsonDTO { public static final String SHELLY_WAKEUPT_PERIODIC = "PERIODIC"; // periodic wakeup public static final String SHELLY_WAKEUPT_BUTTON = "BUTTON"; // button pressed public static final String SHELLY_WAKEUPT_POWERON = "POWERON"; // device powered up + public static final String SHELLY_WAKEUPT_EXT_POWER = "EXT_POWER"; // charger connected public static final String SHELLY_WAKEUPT_UNKNOWN = "UNKNOWN"; // other event // @@ -94,6 +95,12 @@ public class ShellyApiJsonDTO { public static final String SHELLY_EVENT_ROLLER_OPEN = "roller_open"; public static final String SHELLY_EVENT_ROLLER_CLOSE = "roller_close"; public static final String SHELLY_EVENT_ROLLER_STOP = "roller_stop"; + public static final String SHELLY_EVENT_ROLLER_CALIB = "roller_calibrating"; + + // Roller states + public static final String SHELLY_RSTATE_OPEN = "open"; + public static final String SHELLY_RSTATE_STOP = "stop"; + public static final String SHELLY_RSTATE_CLOSE = "close"; // Sensors public static final String SHELLY_EVENT_SENSORREPORT = "report"; @@ -116,6 +123,8 @@ public class ShellyApiJsonDTO { // // API values // + public static final double SHELLY_API_INVTEMP = -999.0; + public static final String SHELLY_BTNT_MOMENTARY = "momentary"; public static final String SHELLY_BTNT_MOM_ON_RELEASE = "momentary_on_release"; public static final String SHELLY_BTNT_ONE_BUTTON = "one_button"; @@ -128,6 +137,8 @@ public class ShellyApiJsonDTO { public static final String SHELLY_STATE_STOP = "stop"; public static final String SHELLY_INP_MODE_OPENCLOSE = "openclose"; + public static final String SHELLY_INP_MODE_ONEBUTTON = "onebutton"; + public static final String SHELLY_OBSTMODE_DISABLED = "disabled"; public static final String SHELLY_SAFETYM_WHILEOPENING = "while_opening"; @@ -359,8 +370,6 @@ public class ShellyApiJsonDTO { public static class ShellySettingsRelay { public String name; - public Boolean ison; - public Boolean overpower; @SerializedName("default_state") public String defaultState; // Accepted values: off, on, last, switch @SerializedName("btn_type") @@ -393,6 +402,16 @@ public class ShellyApiJsonDTO { public String pushLongUrl; // to access when roller stopped @SerializedName("shortpush_url") public String pushShortUrl; // to access when roller stopped + + // Status information + public Boolean ison; + public Boolean overpower; + @SerializedName("is_valid") + public Boolean isValid; + @SerializedName("ext_temperature") + public ShellyStatusSensor.ShellyExtTemperature extTemperature; // Shelly 1/1PM: sensor values + @SerializedName("ext_humidity") + public ShellyStatusSensor.ShellyExtHumidity extHumidity; // Shelly 1/1PM: sensor values } public static class ShellySettingsDimmer { @@ -591,18 +610,19 @@ public class ShellyApiJsonDTO { public Boolean calibrated; public ArrayList relays; - public Double voltage; // AC voltage for Shelly 2.5 - @SerializedName("supply_voltage") - public Long supplyVoltage; // Shelly 1PM/1L: 0=110V, 1=220V + public ArrayList rollers; public ArrayList dimmers; public ArrayList lights; public ArrayList emeters; public ArrayList inputs; // ix3 - public ArrayList thermostats; // TRV + public Double voltage; // AC voltage for Shelly 2.5 + @SerializedName("supply_voltage") + public Long supplyVoltage; // Shelly 1PM/1L: 0=110V, 1=220V + @SerializedName("temperature_units") - public String temperatureUnits; // Either'C'or'F' + public String temperatureUnits = "C"; // Either'C'or'F' @SerializedName("led_status_disable") public Boolean ledStatusDisable; // PlugS only Disable LED indication for network @@ -641,7 +661,7 @@ public class ShellyApiJsonDTO { // Roller with FW 1.9.2+ @SerializedName("favorites_enabled") - public Boolean favoritesEnabled; + public Boolean favoritesEnabled = false; public ArrayList favorites; // Motion @@ -686,7 +706,7 @@ public class ShellyApiJsonDTO { public ShellyStatusMqtt mqtt; public String time; - public Integer serial; + public Integer serial = -1; @SerializedName("has_update") public Boolean hasUpdate; public String mac; @@ -709,7 +729,7 @@ public class ShellyApiJsonDTO { // Internal device temp public ShellyStatusSensor.ShellySensorTmp tmp; // Shelly 1PM - public Double temperature; // Shelly 2.5 + public Double temperature = SHELLY_API_INVTEMP; // Shelly 2.5 public Boolean overtemperature; // Shelly Dimmer only @@ -838,16 +858,16 @@ public class ShellyApiJsonDTO { public Integer currentPos; // current position 0..100, 100=open } - public class ShellyOtaCheckResult { + public static class ShellyOtaCheckResult { public String status; } - public class ShellyApRoaming { + public static class ShellyApRoaming { public Boolean enabled; public Integer threshold; } - public class ShellySensorSleepMode { + public static class ShellySensorSleepMode { public Integer period; public String unit; } 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 ad3b34ae5c6..774fc88ed9a 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 @@ -58,6 +58,7 @@ public class ShellyDeviceProfile { public ShellySettingsStatus status = new ShellySettingsStatus(); public String hostname = ""; + public String name = ""; public String mode = ""; public boolean discoverable = true; public boolean auth = false; @@ -118,6 +119,7 @@ public class ShellyDeviceProfile { settings = gs; // only update when no exception // General settings + name = getString(settings.name); deviceType = getString(settings.device.type); mac = getString(settings.device.mac); hostname = settings.device.hostname != null && !settings.device.hostname.isEmpty() @@ -251,6 +253,10 @@ public class ShellyDeviceProfile { return numRelays == 1 ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_STATUS + idx; } + public String getMeterGroup(int idx) { + return numMeters > 1 ? CHANNEL_GROUP_METER + (idx + 1) : CHANNEL_GROUP_METER; + } + public String getInputGroup(int i) { int idx = i + 1; // group names are 1-based if (isRGBW2) { @@ -275,7 +281,7 @@ public class ShellyDeviceProfile { // Roller has 2 relays, but it will be mapped to 1 roller with 2 inputs return String.valueOf(idx); } else if (hasRelays) { - return (numRelays) == 1 && (numInputs >= 2) ? String.valueOf(idx) : ""; + return numRelays == 1 && numInputs >= 2 ? String.valueOf(idx) : ""; } return ""; } @@ -288,7 +294,7 @@ public class ShellyDeviceProfile { String btnType = ""; if (isButton) { return true; - } else if (isIX3 && (settings.inputs != null) && (idx < settings.inputs.size())) { + } else if (isIX3 && settings.inputs != null && idx < settings.inputs.size()) { ShellySettingsInput input = settings.inputs.get(idx); btnType = getString(input.btnType); } else if (isDimmer) { @@ -315,7 +321,6 @@ public class ShellyDeviceProfile { btnType = light.btnType; } - logger.trace("{}: Checking for trigger, button-type[{}] is {}", thingName, idx, btnType); return btnType.equalsIgnoreCase(SHELLY_BTNT_MOMENTARY) || btnType.equalsIgnoreCase(SHELLY_BTNT_MOM_ON_RELEASE) || btnType.equalsIgnoreCase(SHELLY_BTNT_ONE_BUTTON) || btnType.equalsIgnoreCase(SHELLY_BTNT_TWO_BUTTON) || btnType.equalsIgnoreCase(SHELLY_BTNT_DETACHED); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java index a8906c84924..38cb916d066 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java @@ -60,7 +60,7 @@ import com.google.gson.JsonSyntaxException; * @author Markus Michels - Initial contribution */ @NonNullByDefault -public class ShellyHttpApi { +public class ShellyHttpApi implements ShellyApiInterface { public static final String HTTP_HEADER_AUTH = "Authorization"; public static final String HTTP_AUTH_TYPE_BASIC = "Basic"; public static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8"; @@ -82,19 +82,23 @@ public class ShellyHttpApi { profile.initFromThingType(thingName); } + @Override public void setConfig(String thingName, ShellyThingConfiguration config) { this.thingName = thingName; this.config = config; } - public ShellySettingsDevice getDevInfo() throws ShellyApiException { + @Override + public ShellySettingsDevice getDeviceInfo() throws ShellyApiException { return callApi(SHELLY_URL_DEVINFO, ShellySettingsDevice.class); } + @Override public String setDebug(boolean enabled) throws ShellyApiException { return callApi(SHELLY_URL_SETTINGS + "?debug_enable=" + Boolean.valueOf(enabled), String.class); } + @Override public String getDebugLog(String id) throws ShellyApiException { return callApi("/debug/" + id, String.class); } @@ -106,6 +110,7 @@ public class ShellyHttpApi { * @return Initialized ShellyDeviceProfile * @throws ShellyApiException */ + @Override public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException { String json = request(SHELLY_URL_SETTINGS); if (json.contains("\"type\":\"SHDM-")) { @@ -131,6 +136,7 @@ public class ShellyHttpApi { return profile; } + @Override public boolean isInitialized() { return profile.initialized; } @@ -141,6 +147,7 @@ public class ShellyHttpApi { * @return Device settings/status as ShellySettingsStatus object * @throws ShellyApiException */ + @Override public ShellySettingsStatus getStatus() throws ShellyApiException { String json = ""; try { @@ -156,42 +163,55 @@ public class ShellyHttpApi { } } + @Override public ShellyStatusRelay getRelayStatus(Integer relayIndex) throws ShellyApiException { return callApi(SHELLY_URL_STATUS_RELEAY + "/" + relayIndex.toString(), ShellyStatusRelay.class); } - public ShellyShortLightStatus setRelayTurn(Integer id, String turnMode) throws ShellyApiException { + @Override + public void setRelayTurn(int id, String turnMode) throws ShellyApiException { + callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(), + ShellyShortLightStatus.class); + } + + @Override + public ShellyShortLightStatus setLightTurn(int id, String turnMode) throws ShellyApiException { return callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(), ShellyShortLightStatus.class); } - public void setBrightness(Integer id, Integer brightness, boolean autoOn) throws ShellyApiException { + @Override + public void setBrightness(int id, int brightness, boolean autoOn) throws ShellyApiException { String turn = autoOn ? SHELLY_LIGHT_TURN + "=" + SHELLY_API_ON + "&" : ""; - request(getControlUriPrefix(id) + "?" + turn + "brightness=" + brightness.toString()); + request(getControlUriPrefix(id) + "?" + turn + "brightness=" + brightness); } - public ShellyControlRoller getRollerStatus(Integer rollerIndex) throws ShellyApiException { - String uri = SHELLY_URL_CONTROL_ROLLER + "/" + rollerIndex.toString() + "/pos"; + @Override + public ShellyControlRoller getRollerStatus(int idx) throws ShellyApiException { + String uri = SHELLY_URL_CONTROL_ROLLER + "/" + idx + "/pos"; return callApi(uri, ShellyControlRoller.class); } - public void setRollerTurn(Integer relayIndex, String turnMode) throws ShellyApiException { - request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?go=" + turnMode); + @Override + public void setRollerTurn(int idx, String turnMode) throws ShellyApiException { + request(SHELLY_URL_CONTROL_ROLLER + "/" + idx + "?go=" + turnMode); } - public void setRollerPos(Integer relayIndex, Integer position) throws ShellyApiException { - request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?go=to_pos&roller_pos=" - + position.toString()); + @Override + public void setRollerPos(int id, int position) throws ShellyApiException { + request(SHELLY_URL_CONTROL_ROLLER + "/" + id + "?go=to_pos&roller_pos=" + position); } - public void setRollerTimer(Integer relayIndex, Integer timer) throws ShellyApiException { - request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?timer=" + timer.toString()); + public void setRollerTimer(int idx, int timer) throws ShellyApiException { + request(SHELLY_URL_CONTROL_ROLLER + "/" + idx + "?timer=" + timer); } - public ShellyShortLightStatus getLightStatus(Integer index) throws ShellyApiException { + @Override + public ShellyShortLightStatus getLightStatus(int index) throws ShellyApiException { return callApi(getControlUriPrefix(index), ShellyShortLightStatus.class); } + @Override public ShellyStatusSensor getSensorStatus() throws ShellyApiException { ShellyStatusSensor status = callApi(SHELLY_URL_STATUS, ShellyStatusSensor.class); if (profile.isSense) { @@ -210,6 +230,7 @@ public class ShellyHttpApi { return status; } + @Override public void setTimer(int index, String timerName, int value) throws ShellyApiException { String type = SHELLY_CLASS_RELAY; if (profile.isRoller) { @@ -221,14 +242,17 @@ public class ShellyHttpApi { request(uri); } + @Override public void setSleepTime(int value) throws ShellyApiException { request(SHELLY_URL_SETTINGS + "?sleep_time=" + value); } - public void setTemperature(int valveId, int value) throws ShellyApiException { + @Override + public void setValveTemperature(int valveId, int value) throws ShellyApiException { request("/thermostat/" + valveId + "?target_t_enabled=1&target_t=" + value); } + @Override public void setValveMode(int valveId, boolean auto) throws ShellyApiException { String uri = "/settings/thermostat/" + valveId + "?target_t_enabled=" + (auto ? "1" : "0"); if (auto) { @@ -237,24 +261,29 @@ public class ShellyHttpApi { request(uri); // percentage to open the valve } - public void setProfile(int valveId, int value) throws ShellyApiException { + @Override + public void setValveProfile(int valveId, int value) throws ShellyApiException { String uri = "/settings/thermostat/" + valveId + "?"; request(uri + (value == 0 ? "schedule=0" : "schedule=1&schedule_profile=" + value)); } + @Override public void setValvePosition(int valveId, double value) throws ShellyApiException { request("/thermostat/" + valveId + "?pos=" + value); // percentage to open the valve } - public void setBoostTime(int valveId, int value) throws ShellyApiException { + @Override + public void setValveBoostTime(int valveId, int value) throws ShellyApiException { request("/settings/thermostat/" + valveId + "?boost_minutes=" + value); } - public void startBoost(int valveId, int value) throws ShellyApiException { + @Override + public void startValveBoost(int valveId, int value) throws ShellyApiException { int minutes = value != -1 ? value : getInteger(profile.settings.thermostats.get(0).boostMinutes); request("/thermostat/" + valveId + "?boost_minutes=" + minutes); } + @Override public void setLedStatus(String ledName, Boolean value) throws ShellyApiException { request(SHELLY_URL_SETTINGS + "?" + ledName + "=" + (value ? SHELLY_API_TRUE : SHELLY_API_FALSE)); } @@ -263,6 +292,7 @@ public class ShellyHttpApi { return callApi(SHELLY_URL_SETTINGS_LIGHT, ShellySettingsLight.class); } + @Override public ShellyStatusLight getLightStatus() throws ShellyApiException { return callApi(SHELLY_URL_STATUS, ShellyStatusLight.class); } @@ -271,15 +301,18 @@ public class ShellyHttpApi { request(SHELLY_URL_SETTINGS + "?" + parm + "=" + value); } + @Override public ShellySettingsLogin getLoginSettings() throws ShellyApiException { return callApi(SHELLY_URL_SETTINGS + "/login", ShellySettingsLogin.class); } + @Override public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException { return callApi(SHELLY_URL_SETTINGS + "/login?enabled=yes&username=" + urlEncode(user) + "&password=" + urlEncode(password), ShellySettingsLogin.class); } + @Override public String getCoIoTDescription() throws ShellyApiException { try { return callApi("/cit/d", String.class); @@ -291,31 +324,38 @@ public class ShellyHttpApi { } } + @Override public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException { return callApi(SHELLY_URL_SETTINGS + "?coiot_enable=true&coiot_peer=" + peer, ShellySettingsLogin.class); } + @Override public String deviceReboot() throws ShellyApiException { return callApi(SHELLY_URL_RESTART, String.class); } + @Override public String factoryReset() throws ShellyApiException { return callApi(SHELLY_URL_SETTINGS + "?reset=true", String.class); } + @Override public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException { return callApi("/ota/check", ShellyOtaCheckResult.class); // nw FW 1.10+: trigger update check } + @Override public String setWiFiRecovery(boolean enable) throws ShellyApiException { return callApi(SHELLY_URL_SETTINGS + "?wifirecovery_reboot_enabled=" + (enable ? "true" : "false"), String.class); // FW 1.10+: Enable auto-restart on WiFi problems } + @Override public String setApRoaming(boolean enable) throws ShellyApiException { // FW 1.10+: Enable AP Roadming return callApi(SHELLY_URL_SETTINGS + "?ap_roaming_enabled=" + (enable ? "true" : "false"), String.class); } + @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); } @@ -324,6 +364,7 @@ public class ShellyHttpApi { return callApi("/ota?" + uri, ShellySettingsUpdate.class); } + @Override public String setCloud(boolean enabled) throws ShellyApiException { return callApi("/settings/cloud/?enabled=" + (enabled ? "1" : "0"), String.class); } @@ -334,6 +375,7 @@ public class ShellyHttpApi { * @param mode * @throws ShellyApiException */ + @Override public void setLightMode(String mode) throws ShellyApiException { if (!mode.isEmpty() && !profile.mode.equals(mode)) { setLightSetting(SHELLY_API_MODE, mode); @@ -350,13 +392,15 @@ public class ShellyHttpApi { * @param value The value * @throws ShellyApiException */ - public void setLightParm(Integer lightIndex, String parm, String value) throws ShellyApiException { + @Override + public void setLightParm(int lightIndex, String parm, String value) throws ShellyApiException { // Bulb, RGW2: //?parm?value // Dimmer: /light/?parm=value request(getControlUriPrefix(lightIndex) + "?" + parm + "=" + value); } - public void setLightParms(Integer lightIndex, Map parameters) throws ShellyApiException { + @Override + public void setLightParms(int lightIndex, Map parameters) throws ShellyApiException { String url = getControlUriPrefix(lightIndex) + "?"; int i = 0; for (String key : parameters.keySet()) { @@ -405,6 +449,7 @@ public class ShellyHttpApi { * @throws ShellyApiException * @throws IllegalArgumentException */ + @Override public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException { String type = ""; if (profile.irCodes.containsKey(keyCode)) { @@ -437,6 +482,7 @@ public class ShellyHttpApi { * @param ShellyApiException * @throws ShellyApiException */ + @Override public void setActionURLs() throws ShellyApiException { setRelayEvents(); setDimmerEvents(); @@ -659,10 +705,12 @@ public class ShellyHttpApi { return uri; } + @Override public int getTimeoutErrors() { return timeoutErrors; } + @Override public int getTimeoutsRecovered() { return timeoutsRecovered; } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java index 0ffe7faed82..ce51241893d 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java @@ -21,13 +21,13 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.shelly.internal.api.ShellyApiInterface; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; -import org.openhab.binding.shelly.internal.api.ShellyHttpApi; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor; -import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler; import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; +import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.StringType; @@ -49,9 +49,9 @@ import com.google.gson.JsonSyntaxException; public class ShellyCoIoTProtocol { private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTProtocol.class); protected final String thingName; - protected final ShellyBaseHandler thingHandler; + protected final ShellyThingInterface thingHandler; protected final ShellyDeviceProfile profile; - protected final ShellyHttpApi api; + protected final ShellyApiInterface api; protected final Map blkMap; protected final Map sensorMap; private final Gson gson = new GsonBuilder().create(); @@ -63,7 +63,7 @@ public class ShellyCoIoTProtocol { protected String[] inputEvent = { "", "", "", "", "", "", "", "" }; protected String lastWakeup = ""; - public ShellyCoIoTProtocol(String thingName, ShellyBaseHandler thingHandler, Map blkMap, + public ShellyCoIoTProtocol(String thingName, ShellyThingInterface thingHandler, Map blkMap, Map sensorMap) { this.thingName = thingName; this.thingHandler = thingHandler; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java index cf499df85c8..9412162c71e 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java @@ -24,8 +24,8 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor; -import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler; import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; +import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.unit.ImperialUnits; import org.openhab.core.library.unit.SIUnits; @@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory; public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface { private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion1.class); - public ShellyCoIoTVersion1(String thingName, ShellyBaseHandler thingHandler, Map blkMap, + public ShellyCoIoTVersion1(String thingName, ShellyThingInterface thingHandler, Map blkMap, Map sensorMap) { super(thingName, thingHandler, blkMap, sensorMap); } @@ -207,7 +207,8 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo getStringType(s.valueStr)); break; case "concentration":// Shelly Gas - updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, getDecimal(s.value)); + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, + toQuantityType(getDouble(s.value), DIGITS_NONE, Units.PARTS_PER_MILLION)); break; case "sensorerror": updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, getStringType(s.valueStr)); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java index 3b7f410697d..efbe077fc36 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java @@ -29,8 +29,8 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor; -import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler; import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; +import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.unit.SIUnits; @@ -49,7 +49,7 @@ import org.slf4j.LoggerFactory; public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface { private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion2.class); - public ShellyCoIoTVersion2(String thingName, ShellyBaseHandler thingHandler, Map blkMap, + public ShellyCoIoTVersion2(String thingName, ShellyThingInterface thingHandler, Map blkMap, Map sensorMap) { super(thingName, thingHandler, blkMap, sensorMap); } @@ -114,12 +114,13 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED); break; case "3121": // valvePos, Type=S, Range=0/100; - updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_POSITION, + boolean updated = updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_POSITION, s.value != -1 ? toQuantityType(getDouble(s.value), 0, Units.PERCENT) : UnDefType.UNDEF); - break; - case "3122": // boostMinutes - updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_BTIMER, - s.value != -1 ? toQuantityType(s.value, DIGITS_NONE, Units.MINUTE) : UnDefType.UNDEF); + if (updated && s.value >= 0 && s.value != thingHandler.getChannelDouble(CHANNEL_GROUP_CONTROL, + CHANNEL_CONTROL_POSITION)) { + logger.debug("{}: Valve position changed, force update", thingName); + thingHandler.requestUpdates(1, false); + } break; default: processed = false; @@ -197,9 +198,8 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo // H&T, Fllod, DW only have 1 channel, 1/1PM with Addon have up to to 3 sensors String channel = profile.isSensor ? CHANNEL_SENSOR_TEMP : CHANNEL_SENSOR_TEMP + idx; // Some devices report values = -999 or 99 during fw update - boolean valid = value > -50 && value < 90; updateChannel(updates, CHANNEL_GROUP_SENSOR, channel, - valid ? toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS) : UnDefType.UNDEF); + toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS)); } else { logger.debug("{}: Unable to get extSensorId {} from {}/{}", thingName, sen.id, sen.type, sen.desc); } @@ -258,7 +258,6 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo case "4305": // emeter_2: P, power, W case "4102": // roller_0: P, rollerPower, W, 0-2300, unknown -1 case "4202": // roller_1: P, rollerPower, W, 0-2300, unknown -1 - logger.debug("{}: Updating {}:currentWatts with {}", thingName, mGroup, s.value); updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS, toQuantityType(s.value, DIGITS_WATT, Units.WATT)); if (!profile.isRGBW2 && !profile.isRoller) { @@ -386,7 +385,7 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo } break; case "9103": // EVC, cfgChanged, U16 - if ((lastCfgCount != -1) && (lastCfgCount != s.value)) { + if (lastCfgCount == -1 || lastCfgCount != s.value) { thingHandler.requestUpdates(1, true); // refresh config } lastCfgCount = (int) s.value; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java index d63d2987272..9b466c9b0de 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java @@ -36,8 +36,8 @@ import org.eclipse.californium.core.network.Endpoint; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; 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.api.ShellyHttpApi; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDevDescrTypeAdapter; @@ -46,8 +46,8 @@ import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotGenericSe import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensorTypeAdapter; import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; -import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler; import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; +import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; import org.openhab.core.library.unit.Units; import org.openhab.core.types.State; import org.slf4j.Logger; @@ -67,7 +67,7 @@ public class ShellyCoapHandler implements ShellyCoapListener { private static final byte[] EMPTY_BYTE = new byte[0]; private final Logger logger = LoggerFactory.getLogger(ShellyCoapHandler.class); - private final ShellyBaseHandler thingHandler; + private final ShellyThingInterface thingHandler; private ShellyThingConfiguration config = new ShellyThingConfiguration(); private final GsonBuilder gsonBuilder = new GsonBuilder(); private final Gson gson; @@ -91,11 +91,11 @@ public class ShellyCoapHandler implements ShellyCoapListener { private Map blkMap = new LinkedHashMap<>(); private Map sensorMap = new LinkedHashMap<>(); private ShellyDeviceProfile profile; - private ShellyHttpApi api; + private ShellyApiInterface api; - public ShellyCoapHandler(ShellyBaseHandler thingHandler, ShellyCoapServer coapServer) { + public ShellyCoapHandler(ShellyThingInterface thingHandler, ShellyCoapServer coapServer) { this.thingHandler = thingHandler; - this.thingName = thingHandler.thingName; + this.thingName = thingHandler.getThingName(); this.profile = thingHandler.getProfile(); this.api = thingHandler.getApi(); this.coapServer = coapServer; @@ -506,15 +506,11 @@ public class ShellyCoapHandler implements ShellyCoapListener { String meter = CHANNEL_GROUP_METER + i; double current = thingHandler.getChannelDouble(meter, CHANNEL_METER_CURRENTWATTS); double total = thingHandler.getChannelDouble(meter, CHANNEL_METER_TOTALKWH); - logger.debug("{}: {}#{}={}, total={}", thingName, meter, CHANNEL_METER_CURRENTWATTS, current, - totalCurrent); totalCurrent += current >= 0 ? current : 0; totalKWH += total >= 0 ? total : 0; updateMeter |= current >= 0 | total >= 0; i++; } - logger.debug("{}: totalCurrent={}, totalKWH={}, update={}", thingName, totalCurrent, totalKWH, - updateMeter); if (updateMeter) { thingHandler.updateChannel(CHANNEL_GROUP_METER, CHANNEL_METER_CURRENTWATTS, toQuantityType(totalCurrent, DIGITS_WATT, Units.WATT)); @@ -525,10 +521,7 @@ public class ShellyCoapHandler implements ShellyCoapListener { // Old firmware release are lacking various status values, which are not updated using CoIoT. // In this case we keep a refresh so it gets polled using REST. Beginning with Firmware 1.6 most // of the values are available - if ((!thingHandler.autoCoIoT && (thingHandler.scheduledUpdates < 1)) - || (thingHandler.autoCoIoT && !profile.isLight && !profile.hasBattery)) { - thingHandler.requestUpdates(1, false); - } + thingHandler.triggerUpdateFromCoap(); } else { if (failed == sensorUpdates.size()) { logger.debug("{}: Device description problem detected, re-discover", thingName); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java index 71ef74bd498..588be2e195e 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java @@ -38,6 +38,7 @@ public class ShellyBindingConfiguration { public String defaultUserId = "admin"; // default for http basic user id public String defaultPassword = "admin"; // default for http basic auth password public String localIP = ""; // default:use OH network config + public int httpPort = -1; public boolean autoCoIoT = true; public void updateFromProperties(Map properties) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java index 027359e274e..aa40baa5bd8 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java @@ -41,4 +41,5 @@ public class ShellyThingConfiguration { public String localIp = ""; // local ip addresses used to create callback url public String localPort = "8080"; + public String serviceName = ""; } 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 0353cc772d8..c185d34d93f 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 @@ -13,7 +13,7 @@ package org.openhab.binding.shelly.internal.discovery; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; -import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; +import static org.openhab.binding.shelly.internal.util.ShellyUtils.substringBeforeLast; import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; import java.io.IOException; @@ -100,7 +100,7 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant { @Nullable @Override public DiscoveryResult createResult(final ServiceInfo service) { - String name = service.getName().toLowerCase(); // Duao: Name starts with" Shelly" rather than "shelly" + String name = service.getName().toLowerCase(); // Shelly Duo: Name starts with" Shelly" rather than "shelly" if (!name.startsWith("shelly")) { return null; } @@ -111,7 +111,7 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant { String model = "unknown"; String deviceName = ""; ThingUID thingUID = null; - ShellyDeviceProfile profile = null; + ShellyDeviceProfile profile; Map properties = new TreeMap<>(); name = service.getName().toLowerCase(); @@ -144,8 +144,8 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant { profile = api.getDeviceProfile(thingType); logger.debug("{}: Shelly settings : {}", name, profile.settingsJson); - deviceName = getString(profile.settings.name); - model = getString(profile.settings.device.type); + deviceName = profile.name; + model = profile.deviceType; mode = profile.mode; properties = ShellyBaseHandler.fillDeviceProperties(profile); @@ -174,6 +174,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_MODE, mode); logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString()); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java index 24753741941..924d6b84750 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java @@ -84,6 +84,7 @@ public class ShellyThingCreator { THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON1_STR, THING_TYPE_SHELLYBUTTON1_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON2_STR, THING_TYPE_SHELLYBUTTON2_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYUNI_STR, THING_TYPE_SHELLYUNI_STR); + THING_TYPE_MAPPING.put(THING_TYPE_SHELLYMOTION2_STR, THING_TYPE_SHELLYMOTION_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPROTECTED_STR, THING_TYPE_SHELLYPROTECTED_STR); } @@ -144,11 +145,19 @@ public class ShellyThingCreator { // Check general mapping if (!deviceType.isEmpty()) { - String res = THING_TYPE_MAPPING.get(deviceType); + String res = THING_TYPE_MAPPING.get(deviceType); // by device type + if (res != null) { + return res; + } + + String dt = mode.equals(SHELLY_MODE_RELAY) || mode.equals(SHELLY_MODE_ROLLER) ? deviceType + "-" + mode + : deviceType; + res = THING_TYPE_MAPPING.get(dt); //
-relay /
-roller if (res != null) { return res; } } + String res = THING_TYPE_MAPPING.get(type); if (res != null) { return res; 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 ab4b2024ac2..f0a4ddd752a 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 @@ -30,6 +30,7 @@ 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.ShellyApiJsonDTO; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyInputState; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyOtaCheckResult; @@ -74,14 +75,15 @@ import org.slf4j.LoggerFactory; * @author Markus Michels - Initial contribution */ @NonNullByDefault -public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceListener, ShellyManagerInterface { +public class ShellyBaseHandler extends BaseThingHandler + implements ShellyDeviceListener, ShellyManagerInterface, ShellyThingInterface { protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class); protected final ShellyChannelDefinitions channelDefinitions; public String thingName = ""; public String thingType = ""; - protected final ShellyHttpApi api; + protected final ShellyApiInterface api; protected ShellyBindingConfiguration bindingConfig; protected ShellyThingConfiguration config = new ShellyThingConfiguration(); protected ShellyDeviceProfile profile = new ShellyDeviceProfile(); // init empty profile to avoid NPE @@ -126,10 +128,12 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL int httpPort, final HttpClient httpClient) { super(thing); + this.thingName = getString(thing.getLabel()); this.messages = translationProvider; this.cache = new ShellyChannelCache(this); this.channelDefinitions = new ShellyChannelDefinitions(messages); this.bindingConfig = bindingConfig; + this.config = getConfigAs(ShellyThingConfiguration.class); this.localIP = localIP; this.localPort = String.valueOf(httpPort); @@ -138,6 +142,16 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL coap = new ShellyCoapHandler(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()); + } + + public String getUID() { + return getThing().getUID().getAsString(); + } + /** * Schedule asynchronous Thing initialization, register thing to event dispatcher */ @@ -177,6 +191,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL }, 2, TimeUnit.SECONDS); } + @Override + public ShellyThingConfiguration getThingConfig() { + return config; + } + /** * This routine is called every time the Thing configuration has been changed */ @@ -222,11 +241,14 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL } // Initialize API access, exceptions will be catched by initialize() - ShellySettingsDevice devInfo = api.getDevInfo(); - if (devInfo.auth && config.userId.isEmpty()) { + ShellySettingsDevice devInfo = api.getDeviceInfo(); + if (getBool(devInfo.auth) && config.userId.isEmpty()) { setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials"); return false; } + if (config.serviceName.isEmpty()) { + config.serviceName = getString(profile.hostname).toLowerCase(); + } ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType); if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) { @@ -244,24 +266,12 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL tmpPrf.coiotEndpoint = devInfo.coiot; } tmpPrf.auth = devInfo.auth; // missing in /settings - - logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {}", thingName, - tmpPrf.hostname, tmpPrf.deviceType, tmpPrf.hwRev, tmpPrf.hwBatchId, tmpPrf.fwVersion, tmpPrf.fwDate); - logger.debug("{}: Shelly settings info for {}: {}", thingName, tmpPrf.hostname, tmpPrf.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, tmpPrf.hasRelays, tmpPrf.numRelays, tmpPrf.isRoller, - tmpPrf.numRollers, tmpPrf.isDimmer, tmpPrf.numMeters, tmpPrf.isEMeter, tmpPrf.isSensor, tmpPrf.isDW, - tmpPrf.hasBattery, tmpPrf.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "", - tmpPrf.isSense, tmpPrf.isMotion, tmpPrf.isLight, profile.isBulb, tmpPrf.isDuo, tmpPrf.isRGBW2, - tmpPrf.inColor, tmpPrf.alwaysOn, tmpPrf.updatePeriod); - - // update thing properties tmpPrf.status = api.getStatus(); tmpPrf.updateFromStatus(tmpPrf.status); - updateProperties(tmpPrf, tmpPrf.status); + + showThingConfig(tmpPrf); 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 + ":" + ShellyCoapJSonDTO.COIOT_PORT; @@ -298,11 +308,28 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL logger.debug("{}: Thing successfully initialized.", thingName); profile = tmpPrf; - setThingOnline(); // if API call was successful the thing must be online + updateProperties(tmpPrf, tmpPrf.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 */ @@ -356,7 +383,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL logger.warn("{}: Invalid profile Id {} requested", thingName, profile); break; } - api.setProfile(0, profile); + api.setValveProfile(0, profile); break; case CHANNEL_CONTROL_MODE: logger.debug("{}: Set mode to {}", thingName, command); @@ -364,7 +391,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL break; case CHANNEL_CONTROL_SETTEMP: logger.debug("{}: Set temperature to {}", thingName, command); - api.setTemperature(0, (int) getNumber(command)); + api.setValveTemperature(0, (int) getNumber(command)); break; case CHANNEL_CONTROL_POSITION: logger.debug("{}: Set position to {}", thingName, command); @@ -372,11 +399,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL break; case CHANNEL_CONTROL_BCONTROL: logger.debug("{}: Set boost mode to {}", thingName, command); - api.startBoost(0, command == OnOffType.ON ? -1 : 0); + api.startValveBoost(0, command == OnOffType.ON ? -1 : 0); break; case CHANNEL_CONTROL_BTIMER: logger.debug("{}: Set boost timer to {}", thingName, command); - api.setBoostTime(0, (int) getNumber(command)); + api.setValveBoostTime(0, (int) getNumber(command)); break; default: @@ -386,7 +413,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL restartWatchdog(); if (update && !autoCoIoT && !isUpdateScheduled()) { - logger.debug("{}: Command process, request status update", thingName); + logger.debug("{}: Command processed, request status update", thingName); requestUpdates(1, false); } } catch (ShellyApiException e) { @@ -467,9 +494,6 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL // All channels must be created after the first cycle channelsCreated = true; - - // Restart watchdog when status update was successful (no exception) - restartWatchdog(); } } catch (ShellyApiException e) { // http call failed: go offline except for battery devices, which might be in @@ -526,6 +550,10 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL @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); @@ -538,14 +566,22 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL @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; + } + if (!isThingOffline()) { - updateStatus(ThingStatus.OFFLINE, detail, messages.get(messageKey)); + updateStatus(ThingStatus.OFFLINE, detail, message); watchdog = 0; channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new } } - public synchronized void restartWatchdog() { + @Override + public void restartWatchdog() { watchdog = now(); updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_HEARTBEAT, getTimestamp()); logger.trace("{}: Watchdog restarted (expires in {} sec)", thingName, profile.updatePeriod); @@ -564,13 +600,20 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL return watchdog > 0; } + @Override public void reinitializeThing() { logger.debug("{}: Re-Initialize Thing", thingName); - updateStatus(ThingStatus.UNKNOWN); + if (stopping) { + logger.debug("{}: Handler is shutting down, ignore", thingName); + return; + } + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING, + messages.get("offline.status-error-restarted")); requestUpdates(0, true); } - private void fillDeviceStatus(ShellySettingsStatus status, boolean updated) { + @Override + public void fillDeviceStatus(ShellySettingsStatus status, boolean updated) { String alarm = ""; // Update uptime and WiFi, internal temp @@ -586,9 +629,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL // Check various device indicators like overheating if (checkRestarted(status)) { // Force re-initialization on next status update - if (profile.alwaysOn) { - reinitializeThing(); - } + reinitializeThing(); } else if (getBool(status.overtemperature)) { alarm = ALARM_TYPE_OVERTEMP; } else if (getBool(status.overload)) { @@ -600,12 +641,13 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL if (internalTemp != UnDefType.NULL) { int temp = ((Number) internalTemp).intValue(); if (temp > stats.maxInternalTemp) { - logger.debug("{}: Max Internal Temp for device changed to {}", thingName, temp); stats.maxInternalTemp = temp; } } - stats.lastUptime = getLong(status.uptime); + if (status.uptime != null) { + stats.lastUptime = getLong(status.uptime); + } stats.coiotMessages = coap.getMessageCount(); stats.coiotErrors = coap.getErrorCount(); @@ -622,8 +664,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL private boolean checkRestarted(ShellySettingsStatus status) { if (profile.isInitialized() && profile.alwaysOn /* exclude battery powered devices */ - && (status.uptime < stats.lastUptime || !profile.status.update.oldVersion.isEmpty() - && !status.update.oldVersion.equals(profile.status.update.oldVersion))) { + && (status.uptime != null && status.uptime < stats.lastUptime + || !profile.status.update.oldVersion.isEmpty() + && !status.update.oldVersion.equals(profile.status.update.oldVersion))) { updateProperties(profile, status); return true; } @@ -635,6 +678,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL * * @param alarm Alarm Message */ + @Override public void postEvent(String event, boolean force) { String channelId = mkChannelId(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ALARM); State value = cache.getValue(channelId); @@ -643,20 +687,21 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL if (force || !lastAlarm.equals(event) || (lastAlarm.equals(event) && now() > stats.lastAlarmTs + HEALTH_CHECK_INTERVAL_SEC)) { switch (event) { + case "": case "0": // DW2 1.8 case SHELLY_WAKEUPT_SENSOR: case SHELLY_WAKEUPT_PERIODIC: case SHELLY_WAKEUPT_BUTTON: case SHELLY_WAKEUPT_POWERON: + case SHELLY_WAKEUPT_EXT_POWER: case SHELLY_WAKEUPT_UNKNOWN: logger.debug("{}: {}", thingName, messages.get("event.filtered", event)); - case "": case ALARM_TYPE_NONE: break; default: logger.debug("{}: {}", thingName, messages.get("event.triggered", event)); triggerChannel(channelId, event); - cache.updateChannel(channelId, getStringType(event)); + cache.updateChannel(channelId, getStringType(event.toUpperCase())); stats.lastAlarm = event; stats.lastAlarmTs = now(); stats.alarms++; @@ -808,7 +853,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL config = getConfigAs(ShellyThingConfiguration.class); if (config.deviceIp.isEmpty()) { - logger.warn("{}: IP address for the device must not be empty", thingName); // may not set in .things file + logger.debug("{}: IP address for the device must not be empty", thingName); // may not set in .things file return; } try { @@ -822,6 +867,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL logger.debug("{}: Unable to resolve hostname {}", thingName, config.deviceIp); } + config.serviceName = getString(properties.get(PROPERTY_SERVICE_NAME)); config.localIp = localIP; config.localPort = localPort; if (config.userId.isEmpty() && !bindingConfig.defaultUserId.isEmpty()) { @@ -866,7 +912,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL if (bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0) || (prf.fwVersion.equalsIgnoreCase("production_test"))) { if (!config.eventsCoIoT) { - logger.debug("{}: {}", thingName, messages.get("versioncheck.autocoiot")); + logger.info("{}: {}", thingName, messages.get("versioncheck.autocoiot")); } autoCoIoT = true; } @@ -887,10 +933,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL * @param response exception details including the http respone * @return true if the authorization failed */ - private boolean isAuthorizationFailed(ShellyApiResult result) { + protected boolean isAuthorizationFailed(ShellyApiResult result) { if (result.isHttpAccessUnauthorized()) { // If the device is password protected the API doesn't provide settings to the device settings - logger.warn("{}: {}", thingName, messages.get("init.protected")); setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-access-denied"); return true; } @@ -972,6 +1017,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL * @param status Shelly device status * @return true: one or more inputs were updated */ + @Override public boolean updateInputs(ShellySettingsStatus status) { boolean updated = false; @@ -1004,6 +1050,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL return updated; } + @Override public boolean updateWakeupReason(@Nullable List valueArray) { boolean changed = false; if (valueArray != null && !valueArray.isEmpty()) { @@ -1019,6 +1066,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL return changed; } + @Override public void triggerButton(String group, int idx, String value) { String trigger = mapButtonEvent(value); if (trigger.isEmpty()) { @@ -1036,6 +1084,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL } } + @Override public void publishState(String channelId, State value) { String id = channelId.contains("$") ? substringBefore(channelId, "$") : channelId; if (!stopping && isLinked(id)) { @@ -1044,10 +1093,12 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL } } + @Override public boolean updateChannel(String group, String channel, State value) { return updateChannel(mkChannelId(group, channel), value, false); } + @Override public boolean updateChannel(String channelId, State value, boolean force) { return !stopping && cache.updateChannel(channelId, value, force); } @@ -1057,6 +1108,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL return cache.getValue(group, channel); } + @Override public double getChannelDouble(String group, String channel) { State value = getChannelValue(group, channel); if (value != UnDefType.NULL) { @@ -1075,7 +1127,8 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL * * @param thingHandler */ - protected void updateChannelDefinitions(Map dynChannels) { + @Override + public void updateChannelDefinitions(Map dynChannels) { if (channelsCreated) { return; // already done } @@ -1106,6 +1159,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL } } + @Override public boolean areChannelsCreated() { return channelsCreated; } @@ -1119,13 +1173,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL protected void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus status) { logger.debug("{}: Update properties", thingName); Map properties = fillDeviceProperties(profile); - String serviceName = getString(getThing().getProperties().get(PROPERTY_SERVICE_NAME)); - String hostname = getString(profile.settings.device.hostname).toLowerCase(); - if (serviceName.isEmpty()) { - properties.put(PROPERTY_SERVICE_NAME, hostname); - logger.trace("{}: Updated serrviceName to {}", thingName, hostname); - } 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); } @@ -1155,6 +1205,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL * @param key Name of the property * @param value Value of the property */ + @Override public void updateProperties(String key, String value) { Map thingProperties = editProperties(); if (thingProperties.containsKey(key)) { @@ -1184,6 +1235,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL * @param key property name * @return property value or "" if property is not set */ + @Override public String getProperty(String key) { Map thingProperties = getThing().getProperties(); return getString(thingProperties.get(key)); @@ -1230,7 +1282,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL if (refreshSettings) { profile = api.getDeviceProfile(thingType); if (!isThingOnline()) { - logger.debug("{}:Device profile re-initialized (thingType={})", thingName, thingType); + logger.debug("{}: Device profile re-initialized (thingType={})", thingName, thingType); } } } finally { @@ -1244,14 +1296,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL return profile; } - protected ShellyHttpApi getShellyApi() { - return api; - } - protected ShellyDeviceProfile getDeviceProfile() { return profile; } + @Override public void triggerChannel(String group, String channel, String payload) { String triggerCh = mkChannelId(group, channel); logger.debug("{}: Send event {} to channel {}", thingName, triggerCh, payload); @@ -1324,7 +1373,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL } @Override - public ShellyHttpApi getApi() { + public ShellyApiInterface getApi() { return api; } @@ -1332,6 +1381,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL return stats.asProperties(); } + @Override + public long getScheduledUpdates() { + return scheduledUpdates; + } + public String checkForUpdate() { try { ShellyOtaCheckResult result = api.checkForUpdate(); @@ -1340,4 +1394,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL return ""; } } + + @Override + public void triggerUpdateFromCoap() { + if ((!autoCoIoT && (getScheduledUpdates() < 1)) || (autoCoIoT && !profile.isLight && !profile.hasBattery)) { + requestUpdates(1, false); + } + } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java index 188b3de2351..9d68b0b91ab 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java @@ -49,29 +49,35 @@ public class ShellyComponents { * @param th Thing Handler instance * @param profile ShellyDeviceProfile */ - public static boolean updateDeviceStatus(ShellyBaseHandler thingHandler, ShellySettingsStatus status) { + public static boolean updateDeviceStatus(ShellyThingInterface thingHandler, ShellySettingsStatus status) { + ShellyDeviceProfile profile = thingHandler.getProfile(); + if (!thingHandler.areChannelsCreated()) { thingHandler.updateChannelDefinitions(ShellyChannelDefinitions.createDeviceChannels(thingHandler.getThing(), thingHandler.getProfile(), status)); } + if (getLong(status.uptime) > 10) { + thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME, + toQuantityType((double) getLong(status.uptime), DIGITS_NONE, Units.SECOND)); + } + Integer rssi = getInteger(status.wifiSta.rssi); - thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME, - toQuantityType((double) getLong(status.uptime), DIGITS_NONE, Units.SECOND)); thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI, mapSignalStrength(rssi)); - if ((status.tmp != null) && !thingHandler.getProfile().isSensor) { - thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, - toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS)); - } else if (status.temperature != null) { - thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, - toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS)); + if (getDouble(status.temperature) != SHELLY_API_INVTEMP) { + if (status.tmp != null && !thingHandler.getProfile().isSensor) { + thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, + toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS)); + } else if (status.temperature != null) { + thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, + toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS)); + } } thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SLEEPTIME, toQuantityType(getInteger(status.sleepTime), Units.SECOND)); thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE, getOnOff(status.hasUpdate)); - ShellyDeviceProfile profile = thingHandler.getProfile(); if (profile.settings.calibrated != null) { thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CALIBRATED, getOnOff(profile.settings.calibrated)); @@ -87,7 +93,7 @@ public class ShellyComponents { * @param profile ShellyDeviceProfile * @param status Last ShellySettingsStatus */ - public static boolean updateMeters(ShellyBaseHandler thingHandler, ShellySettingsStatus status) { + public static boolean updateMeters(ShellyThingInterface thingHandler, ShellySettingsStatus status) { ShellyDeviceProfile profile = thingHandler.getProfile(); double accumulatedWatts = 0.0; @@ -99,25 +105,15 @@ public class ShellyComponents { // We need to differ // Roler+RGBW2 have multiple meters -> aggregate consumption to the functional device // Meter and EMeter have a different set of channels - if ((profile.numMeters > 0) && ((status.meters != null) || (status.emeters != null))) { + if (status.meters != null || status.emeters != null) { if (!profile.isRoller && !profile.isRGBW2) { - thingHandler.logger.trace("{}: Updating {} {}meter(s)", thingHandler.thingName, profile.numMeters, - !profile.isEMeter ? "standard " : "e-"); - // In Relay mode we map eacher meter to the matching channel group int m = 0; if (!profile.isEMeter) { for (ShellySettingsMeter meter : status.meters) { - Integer meterIndex = m + 1; if (getBool(meter.isValid) || profile.isLight) { // RGBW2-white doesn't report valid flag // correctly in white mode - String groupName = ""; - if (profile.numMeters > 1) { - groupName = CHANNEL_GROUP_METER + meterIndex.toString(); - } else { - groupName = CHANNEL_GROUP_METER; - } - + String groupName = profile.getMeterGroup(m); if (!thingHandler.areChannelsCreated()) { // skip for Shelly Bulb: JSON has a meter, but values don't get updated if (!profile.isBulb) { @@ -141,17 +137,17 @@ public class ShellyComponents { updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1, toQuantityType(getDouble(meter.counters[0]), DIGITS_WATT, Units.WATT)); } - thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE, - getTimestamp(getString(profile.settings.timezone), getLong(meter.timestamp))); + if (meter.timestamp != null) { + thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE, + getTimestamp(getString(profile.settings.timezone), meter.timestamp)); + } } m++; } } else { for (ShellySettingsEMeter emeter : status.emeters) { - Integer meterIndex = m + 1; if (getBool(emeter.isValid)) { - String groupName = profile.numMeters > 1 ? CHANNEL_GROUP_METER + meterIndex.toString() - : CHANNEL_GROUP_METER; + String groupName = profile.getMeterGroup(m); if (!thingHandler.areChannelsCreated()) { thingHandler.updateChannelDefinitions(ShellyChannelDefinitions .createEMeterChannels(thingHandler.getThing(), emeter, groupName)); @@ -185,14 +181,23 @@ public class ShellyComponents { } } else { // In Roller Mode we accumulate all meters to a single set of meters - thingHandler.logger.trace("{}: Updating Meter (accumulated)", thingHandler.thingName); double currentWatts = 0.0; double totalWatts = 0.0; double lastMin1 = 0.0; long timestamp = 0l; String groupName = CHANNEL_GROUP_METER; + + if (!thingHandler.areChannelsCreated()) { + ShellySettingsMeter m = status.meters.get(0); + if (getBool(m.isValid)) { + // Create channels for 1 Meter + thingHandler.updateChannelDefinitions( + ShellyChannelDefinitions.createMeterChannels(thingHandler.getThing(), m, groupName)); + } + } + for (ShellySettingsMeter meter : status.meters) { - if (meter.isValid) { + if (getBool(meter.isValid)) { currentWatts += getDouble(meter.power); totalWatts += getDouble(meter.total); if (meter.counters != null) { @@ -203,11 +208,6 @@ public class ShellyComponents { } } } - // Create channels for 1 Meter - if (!thingHandler.areChannelsCreated()) { - thingHandler.updateChannelDefinitions(ShellyChannelDefinitions - .createMeterChannels(thingHandler.getThing(), status.meters.get(0), groupName)); - } updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1, toQuantityType(getDouble(lastMin1), DIGITS_WATT, Units.WATT)); @@ -244,7 +244,7 @@ public class ShellyComponents { } // EM: compute from provided values - if (Math.abs(emeter.power) + Math.abs(emeter.reactive) > 1.5) { + if (emeter.reactive != null && Math.abs(emeter.power) + Math.abs(emeter.reactive) > 1.5) { double pf = emeter.power / Math.sqrt(emeter.power * emeter.power + emeter.reactive * emeter.reactive); return pf; } @@ -260,15 +260,14 @@ public class ShellyComponents { * * @throws ShellyApiException */ - public static boolean updateSensors(ShellyBaseHandler thingHandler, ShellySettingsStatus status) + public static boolean updateSensors(ShellyThingInterface thingHandler, ShellySettingsStatus status) throws ShellyApiException { ShellyDeviceProfile profile = thingHandler.getProfile(); boolean updated = false; if (profile.isSensor || profile.hasBattery) { - ShellyStatusSensor sdata = thingHandler.api.getSensorStatus(); + ShellyStatusSensor sdata = thingHandler.getApi().getSensorStatus(); if (!thingHandler.areChannelsCreated()) { - thingHandler.logger.trace("{}: Create missing sensor channel(s)", thingHandler.thingName); thingHandler.updateChannelDefinitions( ShellyChannelDefinitions.createSensorChannels(thingHandler.getThing(), profile, sdata)); } @@ -283,13 +282,12 @@ public class ShellyComponents { String sensorError = sdata.sensorError; boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, getStringType(sensorError)); - if (!"0".equals(sensorError) && changed) { + if (changed && !"0".equals(sensorError)) { thingHandler.postEvent(getString(sdata.sensorError), true); - updated |= changed; } + updated |= changed; } if ((sdata.tmp != null) && getBool(sdata.tmp.isValid)) { - thingHandler.logger.trace("{}: Updating temperature", thingHandler.thingName); Double temp = getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_CELSIUS) ? getDouble(sdata.tmp.tC) : getDouble(sdata.tmp.tF); @@ -300,7 +298,7 @@ public class ShellyComponents { temp = convertToC(temp, getString(sdata.tmp.units)); updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP, toQuantityType(temp.doubleValue(), DIGITS_TEMP, SIUnits.CELSIUS)); - } else if (status.thermostats != null && status.thermostats.size() > 0) { + } else if (status.thermostats != null && profile.settings.thermostats != null) { // Shelly TRV ShellyThermnostat t = status.thermostats.get(0); ShellyThermnostat ps = profile.settings.thermostats.get(0); @@ -313,13 +311,11 @@ public class ShellyComponents { updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_MODE, getStringType(getBool(t.targetTemp.enabled) ? SHELLY_TRV_MODE_AUTO : SHELLY_TRV_MODE_MANUAL)); updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE, - getDecimal(getBool(t.schedule) ? t.profile : 0)); + getDecimal(getBool(t.schedule) ? t.profile + 1 : 0)); updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SCHEDULE, getOnOff(t.schedule)); if (t.tmp != null) { Double temp = convertToC(t.tmp.value, getString(t.tmp.units)); - // Some devices report values = -999 or 99 during fw update - boolean valid = temp.intValue() > -50 && temp.intValue() < 90; updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP, toQuantityType(temp.doubleValue(), DIGITS_TEMP, SIUnits.CELSIUS)); temp = convertToC(t.targetTemp.value, getString(t.targetTemp.unit)); @@ -333,14 +329,13 @@ public class ShellyComponents { getDouble(t.pos) > 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED); } } + if (sdata.hum != null) { - thingHandler.logger.trace("{}: Updating humidity", thingHandler.thingName); updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM, toQuantityType(getDouble(sdata.hum.value), DIGITS_PERCENT, Units.PERCENT)); } if ((sdata.lux != null) && getBool(sdata.lux.isValid)) { // “lux”:{“value”:30, “illumination”: “dark”, “is_valid”:true}, - thingHandler.logger.trace("{}: Updating lux", thingHandler.thingName); updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX, toQuantityType(getDouble(sdata.lux.value), DIGITS_LUX, Units.LUX)); if (sdata.lux.illumination != null) { @@ -369,8 +364,8 @@ public class ShellyComponents { getStringType(sdata.gasSensor.sensorState)); } if ((sdata.concentration != null) && sdata.concentration.isValid) { - updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, - getDecimal(sdata.concentration.ppm)); + updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, toQuantityType( + getInteger(sdata.concentration.ppm).doubleValue(), DIGITS_NONE, Units.PARTS_PER_MILLION)); } if ((sdata.adcs != null) && (sdata.adcs.size() > 0)) { ShellyADC adc = sdata.adcs.get(0); @@ -384,18 +379,14 @@ public class ShellyComponents { charger ? OnOffType.ON : OnOffType.OFF); } if (sdata.bat != null) { // no update for Sense - // Shelly HT has external_power under settings, Sense and Motion charger under status - if (!charger || !profile.isHT) { - updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL, - toQuantityType(getDouble(sdata.bat.value), 0, Units.PERCENT)); - } else { - updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL, - UnDefType.UNDEF); - } + updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL, + toQuantityType(getDouble(sdata.bat.value), 0, Units.PERCENT)); + + int lowBattery = thingHandler.getThingConfig().lowBattery; boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW, - getDouble(sdata.bat.value) < thingHandler.config.lowBattery ? OnOffType.ON : OnOffType.OFF); + !charger && getDouble(sdata.bat.value) < lowBattery ? OnOffType.ON : OnOffType.OFF); updated |= changed; - if (changed && getDouble(sdata.bat.value) < thingHandler.config.lowBattery) { + if (!charger && changed && getDouble(sdata.bat.value) < lowBattery) { thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false); } } 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 c8f77c18a71..54f722cb711 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 @@ -143,7 +143,7 @@ public class ShellyLightHandler extends ShellyBaseHandler { int value = -1; if (command instanceof OnOffType) { // Switch logger.debug("{}: Switch light {}", thingName, command); - ShellyShortLightStatus light = api.setRelayTurn(lightId, + ShellyShortLightStatus light = api.setLightTurn(lightId, command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF); col.power = getOnOff(light.ison); col.setBrightness(light.brightness); @@ -164,7 +164,7 @@ public class ShellyLightHandler extends ShellyBaseHandler { } if (value == 0) { logger.debug("{}: Brightness=0 -> switch light OFF", thingName); - api.setRelayTurn(lightId, SHELLY_API_OFF); + api.setLightTurn(lightId, SHELLY_API_OFF); update = false; } else { if (command instanceof IncreaseDecreaseType) { @@ -347,12 +347,14 @@ public class ShellyLightHandler extends ShellyBaseHandler { ShellyColorUtils col = getCurrentColors(lightId); col.power = getOnOff(light.ison); - // Channel control/timer - ShellySettingsRgbwLight ls = profile.settings.lights.get(lightId); - updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(ls.autoOn)); - updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(ls.autoOff)); - updated |= updateChannel(controlGroup, CHANNEL_LIGHT_POWER, col.power); - updated |= updateChannel(controlGroup, CHANNEL_TIMER_ACTIVE, getOnOff(light.hasTimer)); + if (profile.settings.lights != null) { + // Channel control/timer + ShellySettingsRgbwLight ls = profile.settings.lights.get(lightId); + updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(ls.autoOn)); + updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(ls.autoOff)); + updated |= updateChannel(controlGroup, CHANNEL_TIMER_ACTIVE, getOnOff(light.hasTimer)); + updated |= updateChannel(controlGroup, CHANNEL_LIGHT_POWER, col.power); + } if (getBool(light.overpower)) { postEvent(ALARM_TYPE_OVERPOWER, false); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyManagerInterface.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyManagerInterface.java index d6006efc88f..a7b48286043 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyManagerInterface.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyManagerInterface.java @@ -14,8 +14,8 @@ package org.openhab.binding.shelly.internal.handler; import org.eclipse.jdt.annotation.NonNullByDefault; 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.api.ShellyHttpApi; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.types.State; @@ -36,7 +36,7 @@ public interface ShellyManagerInterface { public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException; - public ShellyHttpApi getApi(); + public ShellyApiInterface getApi(); public ShellyDeviceStats getStats(); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java index 0f5a08a97a8..8fbe6210e75 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java @@ -335,7 +335,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler { public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException { boolean updated = false; // Check for Relay in Standard Mode - if (profile.hasRelays && !profile.isRoller && !profile.isDimmer) { + if (profile.hasRelays && !profile.isDimmer) { double voltage = -1; if (status.voltage == null && profile.settings.supplyVoltage != null) { // Shelly 1PM/1L (fix) @@ -348,7 +348,9 @@ 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 && !profile.isDimmer) { logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays); int i = 0; ShellyStatusRelay rstatus = api.getRelayStatus(i); @@ -479,12 +481,14 @@ public class ShellyRelayHandler extends ShellyBaseHandler { toQuantityType(0.0, DIGITS_NONE, Units.PERCENT)); } - ShellySettingsDimmer dsettings = profile.settings.dimmers.get(l); - if (dsettings != null) { - updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON, - toQuantityType(getDouble(dsettings.autoOn), Units.SECOND)); - updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF, - toQuantityType(getDouble(dsettings.autoOff), Units.SECOND)); + if (profile.settings.dimmers != null) { + ShellySettingsDimmer dsettings = profile.settings.dimmers.get(l); + if (dsettings != null) { + updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON, + toQuantityType(getDouble(dsettings.autoOn), Units.SECOND)); + updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF, + toQuantityType(getDouble(dsettings.autoOff), Units.SECOND)); + } } l++; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyThingInterface.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyThingInterface.java new file mode 100644 index 00000000000..37a43c780b5 --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyThingInterface.java @@ -0,0 +1,99 @@ +/** + * 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.handler; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.shelly.internal.api.ShellyApiException; +import org.openhab.binding.shelly.internal.api.ShellyApiInterface; +import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus; +import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; +import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.types.State; + +/** + * The {@link ShellyThingInterface} implements the interface for Shelly Manager to access the thing handler + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +public interface ShellyThingInterface { + + public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException; + + public double getChannelDouble(String group, String channel); + + public boolean updateChannel(String group, String channel, State value); + + public boolean updateChannel(String channelId, State value, boolean force); + + public void setThingOnline(); + + public void setThingOffline(ThingStatusDetail detail, String messageKey); + + public boolean requestUpdates(int requestCount, boolean refreshSettings); + + public void triggerUpdateFromCoap(); + + public void reinitializeThing(); + + public void restartWatchdog(); + + public void publishState(String channelId, State value); + + public boolean areChannelsCreated(); + + public State getChannelValue(String group, String channel); + + public boolean updateInputs(ShellySettingsStatus status); + + public void updateChannelDefinitions(Map dynChannels); + + public void postEvent(String event, boolean force); + + public void triggerChannel(String group, String channelID, String event); + + public void triggerButton(String group, int idx, String value); + + public ShellyDeviceStats getStats(); + + public void resetStats(); + + public Thing getThing(); + + public String getThingName(); + + public ShellyThingConfiguration getThingConfig(); + + public String getProperty(String key); + + public void updateProperties(String key, String value); + + public boolean updateWakeupReason(@Nullable List valueArray); + + public ShellyApiInterface getApi(); + + public ShellyDeviceProfile getProfile(); + + public long getScheduledUpdates(); + + public void fillDeviceStatus(ShellySettingsStatus status, boolean updated); + + public boolean checkRepresentation(String key); +} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyThingTable.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyThingTable.java new file mode 100644 index 00000000000..a7e53567e22 --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyThingTable.java @@ -0,0 +1,64 @@ +/** + * 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.handler; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; + +/*** + * The{@link ShellyThingTable} implements a simple table to allow dispatching incoming events to the proper thing + * handler + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +@Component(service = ShellyThingTable.class, configurationPolicy = ConfigurationPolicy.OPTIONAL) +public class ShellyThingTable { + private Map thingTable = new ConcurrentHashMap<>(); + + public void addThing(String key, ShellyThingInterface thing) { + thingTable.put(key, thing); + } + + public ShellyThingInterface getThing(String key) { + ShellyThingInterface t = thingTable.get(key); + if (t != null) { + return t; + } + for (Map.Entry entry : thingTable.entrySet()) { + t = entry.getValue(); + if (t.checkRepresentation(key)) { + return t; + } + } + throw new IllegalArgumentException(); + } + + public void removeThing(String key) { + if (thingTable.containsKey(key)) { + thingTable.remove(key); + } + } + + public Map getTable() { + return thingTable; + } + + public int size() { + return thingTable.size(); + } +} 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 a942ccae255..e13df68b42c 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 @@ -26,6 +26,7 @@ import org.eclipse.jetty.client.HttpClient; 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.ShellyApiJsonDTO.ShellyOtaCheckResult; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsLogin; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; @@ -81,7 +82,7 @@ public class ShellyManagerActionPage extends ShellyManagerPage { ShellyThingConfiguration config = getThingConfig(th, properties); ShellyDeviceProfile profile = th.getProfile(); - ShellyHttpApi api = th.getApi(); + ShellyApiInterface api = th.getApi(); new ShellyHttpApi(uid, config, httpClient); int refreshTimer = 0; @@ -326,7 +327,7 @@ public class ShellyManagerActionPage extends ShellyManagerPage { !profile.settings.wifiRecoveryReboot ? "Enable WiFi Recovery" : "Disable WiFi Recovery"); } - boolean set = (profile.settings.cloud != null) && profile.settings.cloud.enabled; + 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"); diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerServlet.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerServlet.java index 29ab82997f2..2ac54c81e8b 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerServlet.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerServlet.java @@ -78,7 +78,8 @@ public class ShellyManagerServlet extends HttpServlet { try { httpService.registerServlet(SERVLET_URI, this, null, httpService.createDefaultHttpContext()); - logger.debug("{}: Started at '{}'", className, SERVLET_URI); + // Promote Shelly Manager usage + logger.info("{}", translationProvider.get("status.managerstarted", localIp, localPort + "")); } catch (NamespaceException | ServletException | IllegalArgumentException e) { logger.warn("{}: Unable to initialize bindingConfig", className, e); } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java index c16e9bc90e9..53f6c85508e 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java @@ -13,6 +13,7 @@ package org.openhab.binding.shelly.internal.provider; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; +import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.SHELLY_API_INVTEMP; import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import java.util.HashMap; @@ -41,7 +42,7 @@ import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLigh import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; -import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler; +import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -261,26 +262,24 @@ public class ShellyChannelDefinitions { addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME); - if (!profile.isSensor) { + if (!profile.isSensor && !profile.isIX3 && getDouble(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); + addChannel(thing, add, status.tmp != null || status.temperature != null, CHGR_DEVST, CHANNEL_DEVST_ITEMP); } addChannel(thing, add, profile.settings.sleepTime != null, CHGR_SENSOR, CHANNEL_SENSOR_SLEEPTIME); // If device has more than 1 meter the channel accumulatedWatts receives the accumulated value - boolean accuChannel = !profile.isRoller && !profile.isRGBW2 - && (((status.meters != null) && (status.meters.size() > 1)) - || ((status.emeters != null && status.emeters.size() > 1))); + boolean accuChannel = (((status.meters != null) && (status.meters.size() > 1) && !profile.isRoller + && !profile.isRGBW2) || ((status.emeters != null && status.emeters.size() > 1))); addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS); addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL); addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED); + addChannel(thing, add, status.voltage != null || profile.settings.supplyVoltage != null, CHGR_DEVST, + CHANNEL_DEVST_VOLTAGE); addChannel(thing, add, - !profile.isRoller && !profile.isRGBW2 - && (status.voltage != null || profile.settings.supplyVoltage != null), - CHGR_DEVST, CHANNEL_DEVST_VOLTAGE); + profile.status.uptime != null && (!profile.hasBattery || profile.isMotion || profile.isTRV), CHGR_DEVST, + CHANNEL_DEVST_UPTIME); addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE); - addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPTIME); addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT); addChannel(thing, add, profile.settings.ledPowerDisable != null, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE); addChannel(thing, add, profile.settings.ledStatusDisable != null, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi @@ -360,8 +359,7 @@ public class ShellyChannelDefinitions { 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); + 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); @@ -390,7 +388,7 @@ public class ShellyChannelDefinitions { addChannel(thing, add, roller.stopReason != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STOPR); addChannel(thing, add, roller.safetySwitch != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_SAFETY); - ShellyBaseHandler handler = (ShellyBaseHandler) thing.getHandler(); + ShellyThingInterface handler = (ShellyThingInterface) thing.getHandler(); if (handler != null) { ShellySettingsGlobal settings = handler.getProfile().settings; if (getBool(settings.favoritesEnabled) && (settings.favorites != null)) { @@ -404,7 +402,7 @@ public class ShellyChannelDefinitions { Map newChannels = new LinkedHashMap<>(); addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS); addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH); - addChannel(thing, newChannels, (meter.counters != null) && (meter.counters[0] != null), group, + addChannel(thing, newChannels, meter.counters != null && meter.counters[0] != null, group, CHANNEL_METER_LASTMIN1); addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE); return newChannels; @@ -509,14 +507,23 @@ public class ShellyChannelDefinitions { ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:") ? new ChannelTypeUID(channelDef.typeId) : new ChannelTypeUID(BINDING_ID, channelDef.typeId); - Channel channel; + ChannelBuilder builder; if (channelDef.typeId.equalsIgnoreCase("system:button")) { - channel = ChannelBuilder.create(channelUID, null).withKind(ChannelKind.TRIGGER) - .withType(channelTypeUID).build(); + builder = ChannelBuilder.create(channelUID, null).withKind(ChannelKind.TRIGGER); } else { - channel = ChannelBuilder.create(channelUID, channelDef.itemType).withType(channelTypeUID).build(); + builder = ChannelBuilder.create(channelUID, channelDef.itemType); } - newChannels.put(channelId, channel); + if (!channelDef.label.isEmpty()) { + char grseq = lastChar(group); + char chseq = lastChar(channelName); + char sequence = isDigit(chseq) ? chseq : grseq; + String label = !isDigit(sequence) ? channelDef.label : channelDef.label + " " + sequence; + builder.withLabel(label); + } + if (!channelDef.description.isEmpty()) { + builder.withDescription(channelDef.description); + } + newChannels.put(channelId, builder.withType(channelTypeUID).build()); } } } @@ -549,9 +556,21 @@ public class ShellyChannelDefinitions { this.typeId = typeId; groupLabel = getText(PREFIX_GROUP + group + ".label"); + if (groupLabel.contains(PREFIX_GROUP)) { + groupLabel = ""; + } groupDescription = getText(PREFIX_GROUP + group + ".description"); - label = getText(PREFIX_CHANNEL + channel + ".label"); - description = getText(PREFIX_CHANNEL + channel + ".description"); + if (groupDescription.contains(PREFIX_GROUP)) { + groupDescription = ""; + } + label = getText(PREFIX_CHANNEL + typeId.replace(':', '.') + ".label"); + if (label.contains(PREFIX_CHANNEL)) { + label = ""; + } + description = getText(PREFIX_CHANNEL + typeId + ".description"); + if (description.contains(PREFIX_CHANNEL)) { + description = ""; + } } public String getChanneId() { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java index 67609d9d0bc..5ca4780f655 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java @@ -18,7 +18,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler; +import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; import org.openhab.core.types.State; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; @@ -33,14 +33,14 @@ import org.slf4j.LoggerFactory; public class ShellyChannelCache { private final Logger logger = LoggerFactory.getLogger(ShellyChannelCache.class); - private final ShellyBaseHandler thingHandler; + private final ShellyThingInterface thingHandler; private final Map channelData = new ConcurrentHashMap<>(); private String thingName = ""; private boolean enabled = false; - public ShellyChannelCache(ShellyBaseHandler thingHandler) { + public ShellyChannelCache(ShellyThingInterface thingHandler) { this.thingHandler = thingHandler; - setThingName(thingHandler.thingName); + setThingName(thingHandler.getThingName()); } public void setThingName(String thingName) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java index 7c481cc52a2..dd7b1ea9296 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java @@ -289,9 +289,6 @@ public class ShellyUtils { public static DateTimeType getTimestamp(String zone, long timestamp) { try { - if (timestamp == 0) { - throw new IllegalArgumentException("Timestamp value 0 is invalid"); - } ZoneId zoneId = !zone.isEmpty() ? ZoneId.of(zone) : ZoneId.systemDefault(); ZonedDateTime zdt = LocalDateTime.now().atZone(zoneId); int delta = zdt.getOffset().getTotalSeconds(); @@ -346,4 +343,12 @@ public class ShellyUtils { } return new DecimalType(strength); } + + public static boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + public static char lastChar(String s) { + return s.length() > 1 ? s.charAt(s.length() - 1) : '*'; + } } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyVersionDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyVersionDTO.java index 302724b306e..d8562f51de1 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyVersionDTO.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyVersionDTO.java @@ -163,6 +163,6 @@ public class ShellyVersionDTO { return false; } return version.isEmpty() || version.contains("???") || version.toLowerCase().contains("master") - || (version.toLowerCase().contains("-rc")); + || (version.toLowerCase().contains("-rc") || version.toLowerCase().contains("beta")); } } diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml index 6cf062627c1..f8402396281 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml @@ -78,7 +78,7 @@ 0 - + @text/thing-type.config.shelly.roller.eventsRoller.description true false diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties index 0978d4f3781..9e1d48a37f2 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties @@ -18,7 +18,8 @@ message.config-status.error.missing-userid = No user ID in the Thing configurati 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.status-error-timeout = Device is not reachable (API timeout). +message.offline.status-error-timeout = Device is not reachable (API timeout) +message.offline.status-error-unexpected-error = Unexpected error 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. @@ -31,12 +32,11 @@ message.versioncheck.tooold = WARNING: Firmware might be too old, installed: {0} 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. -message.init.protected = Device is password protected, enter correct credentials in thing configuration. message.command.failed = ERROR: Unable to process command {0} for channel {1} -message.command.init = Thing not yet initialized, command {0} triggers initialization +message.command.init = Thing not yet initialized, command {0}triggered initialization message.status.unknown.initializing = Initializing or device in sleep mode. message.statusupdate.failed = Unable to update status -message.status.managerstarted = Shelly Manager started at http(s)://{0}:{1}/shelly/manager" +message.status.managerstarted = Shelly Manager started at http(s)://{0}:{1}/shelly/manager message.event.triggered = Event triggered: {0} message.event.filtered = Event filtered: {0} message.coap.init.failed = Unable to start CoIoT: {0} @@ -106,7 +106,6 @@ thing-type.config.shelly.eventsCoIoT.description = Activates the CoIoT-Protocol thing-type.config.shelly.eventsSensorReport.label = Enable Sensor Events thing-type.config.shelly.eventsSensorReport.description = True: Register event URL for sensor updates. - # thing config - roller thing-type.config.shelly.roller.favoriteUP.label = Favorite ID for UP thing-type.config.shelly.roller.favoriteUP.description = Specifies the favorite ID that is used during the command UP on the roller#control channel (use the Shelly App to configure favorites) @@ -167,6 +166,7 @@ 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.rollerControl.label = Roller Control channel-group-type.shelly.rollerControl.description = Controlling the roller mode @@ -183,9 +183,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 relay is switched off, it will be switched on automatically after n seconds +channel-type.shelly.timerAutoOn.description = When the output of the relay is switched on, it will be switched off automatically after n seconds channel-type.shelly.timerAutoOff.label = Auto-OFF Timer -channel-type.shelly.timerAutoOff.description = When the relay is switched on, it will be switched off automatically after n seconds +channel-type.shelly.timerAutoOff.description = When the output of the relay is switched off, it will be switched on 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 @@ -208,9 +208,12 @@ channel-type.shelly.rollerFavorite.label = Position Favorite channel-type.shelly.rollerFavorite.description = Set roller position by selecting favorite 1-4 (needs to be defined in the Shelly App, 0=n/a) channel-type.shelly.rollerState.label = Roller State channel-type.shelly.rollerState.description = State of the roller (open/close/stop) -channel-type.shelly.rollerState.state.option.open = opening -channel-type.shelly.rollerState.state.option.close = closing +channel-type.shelly.rollerState.state.option.opening = opening +channel-type.shelly.rollerState.state.option.open = open +channel-type.shelly.rollerState.state.option.closing = closing +channel-type.shelly.rollerState.state.option.close = closed channel-type.shelly.rollerState.state.option.stop = stopped +channel-type.shelly.rollerState.state.option.calibrating = calibrating channel-type.shelly.rollerStop.label = Roller stop reason channel-type.shelly.rollerStop.description = Last reason for stopping the Roller Shutter (normal, safety switch, obstacle detected) channel-type.shelly.rollerStop.state.option.normal = normal @@ -223,12 +226,12 @@ channel-type.shelly.rollerDirection.state.option.close = close channel-type.shelly.rollerDirection.state.option.stop = stopped channel-type.shelly.rollerSafety.label = Safety Switch channel-type.shelly.rollerSafety.description = Status of the safety switch -channel-type.shelly.inputState.label = Input -channel-type.shelly.inputState.description = Input/Button state +channel-type.shelly.inputState.label = Input/Button +channel-type.shelly.inputState.description = Current state of the Input/Button channel-type.shelly.inputState1.label = Input #1 -channel-type.shelly.inputState1.description = Input/Button state #1 +channel-type.shelly.inputState1.description = Current state of the Input #1 channel-type.shelly.inputState2.label = Input #2 -channel-type.shelly.inputState2.description = Input/Button state #2 +channel-type.shelly.inputState2.description = Current state of the Input #2 channel-type.shelly.dimmerBrightness.label = Brightness channel-type.shelly.dimmerBrightness.description = Light Brightness in percent (0-100%, 0=OFF) channel-type.shelly.whiteBrightness.label = Brightness @@ -243,8 +246,8 @@ channel-type.shelly.meterAccuReturned.label = Accumulated Returned Power channel-type.shelly.meterAccuReturned.description = Accumulated Returned Power in kW/h of the device (including all meters) channel-type.shelly.meterReactive.label = Reactive Energy channel-type.shelly.meterReactive.description = Instantaneous reactive power in Watts (W) -channel-type.shelly.lastPower1.label = Last Power #1 -channel-type.shelly.lastPower1.description = Last power consumption #1 - one rounded minute +channel-type.shelly.lastPower1.label = Last Power +channel-type.shelly.lastPower1.description = Rounded power consumption during last minute channel-type.shelly.meterTotal.label = Total Energy Consumption channel-type.shelly.meterTotal.description = Total energy consumption in kW/h since the device powered up (resets on restart) channel-type.shelly.meterReturned.label = Total Returned Energy @@ -398,7 +401,7 @@ channel-type.shelly.supplyVoltage.label = Supply Voltage channel-type.shelly.supplyVoltage.description = External voltage supplied to the device channel-type.shelly.lastUpdate.label = Last Update channel-type.shelly.lastUpdate.description = Timestamp of last status update -channel-type.shelly.lastEvent.label = Event +channel-type.shelly.lastEvent.label = Last Event channel-type.shelly.lastEvent.description = Event Type (S=Short push, SS=Double-Short push, SSS=Triple-Short push, L=Long push, SL=Short-Long push, LS=Long-Short push) channel-type.shelly.lastEvent.state.option.S = Short push channel-type.shelly.lastEvent.state.option.SS = Double-Short push @@ -448,6 +451,9 @@ channel-type.shelly.sensorSleepTime.label = Sensor Sleep Time channel-type.shelly.sensorSleepTime.description = The sensor will not send notifications and will not perform actions until the specified time expires (0=disable) channel-type.shelly.deviceSchedule.label = Schedule active channel-type.shelly.deviceSchedule.description = ON: A scheduled program is active +channel-type.shelly.system.power.label = Power +channel-type.shelly.system.button.label = Event Trigger +channel-type.shelly.system.brightness.label = Brightness # Shelly Manager message.manager.invalid-url = Invalid URL or syntax diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml index 088c13a3f85..3dc2cce3cab 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml @@ -10,7 +10,6 @@ - @@ -123,7 +122,7 @@ - Number:ElectricPotentia + Number:ElectricPotential @text/channel-type.shelly.supplyVoltage.description Energy @@ -131,6 +130,7 @@ Measurement Voltage + String diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml index ea20f72115a..1d37aa16271 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml @@ -379,7 +379,6 @@ Dimmer @text/channel-type.shelly.rollerPosition.description - Blinds Measurement Level diff --git a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml index 06bb86daef6..f95fc271ce7 100644 --- a/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml +++ b/bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml @@ -162,7 +162,7 @@ String @text/channel-type.shelly.controlMode.description - + @@ -433,7 +433,7 @@ - Number:Density + Number:Dimensionless @text/channel-type.shelly.sensorPPM.description Gas @@ -441,7 +441,7 @@ Measurement Gas - +