]> git.basschouten.com Git - openhab-addons.git/commitdiff
[shelly] Auto-numbering for channel labels & bugfixes (#13066)
authorMarkus Michels <markus7017@gmail.com>
Tue, 19 Jul 2022 17:37:06 +0000 (19:37 +0200)
committerGitHub <noreply@github.com>
Tue, 19 Jul 2022 17:37:06 +0000 (19:37 +0200)
* - new device types added
- min firmware set to 1.8.2
- unit for gas concentration fixed (ppm)
- Auto numbering on channel labels for groups with multiple instances
(add sequence suffix)
- API and Thing interfaces defined to restrict access to those classes
- fix on TRV boost update via CoAP
- fix for status.temperature and status.uptime, internalTemp channels
- don’t use meter timestamp if not present (RGBW2)
- low battery indicator for sensor devices fixed
- device detection based on model/type improved
- various messages/translations fixed/improved
- README updated (missing thing types added)

Signed-off-by: Markus Michels <markus7017@gmail.com> (github: markus7017)
Signed-off-by: Markus Michels <markus7017@gmail.com>
* missing properties added

Signed-off-by: Markus Michels <markus7017@gmail.com> (github: markus7017)
Signed-off-by: Markus Michels <markus7017@gmail.com>
* minor changes

Signed-off-by: Markus Michels <markus7017@gmail.com> (github: markus7017)
Signed-off-by: Markus Michels <markus7017@gmail.com>
* markdown fixed

Signed-off-by: Markus Michels <markus7017@gmail.com> (github: markus7017)
Signed-off-by: Markus Michels <markus7017@gmail.com>
* review changes applied

Signed-off-by: Markus Michels <markus7017@gmail.com>
* shelly_de.properties restored from main branch

Signed-off-by: Markus Michels <markus7017@gmail.com>
33 files changed:
bundles/org.openhab.binding.shelly/README.md
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java [new file with mode: 0644]
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTProtocol.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion1.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoIoTVersion2.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/coap/ShellyCoapHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyBindingConfiguration.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/config/ShellyThingConfiguration.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyDiscoveryParticipant.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/discovery/ShellyThingCreator.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyBaseHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyComponents.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyLightHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyManagerInterface.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyRelayHandler.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyThingInterface.java [new file with mode: 0644]
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/handler/ShellyThingTable.java [new file with mode: 0644]
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerActionPage.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/manager/ShellyManagerServlet.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/provider/ShellyChannelDefinitions.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyChannelCache.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyUtils.java
bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/util/ShellyVersionDTO.java
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/config/config.xml
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/i18n/shelly.properties
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/device.xml
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/relay.xml
bundles/org.openhab.binding.shelly/src/main/resources/OH-INF/thing/sensor.xml

index 6f6ae77442a293be6531ec1db5c589b8c7d701de..1f686d63a32c25ff4f3cc60384a5c6cfb00767db 100644 (file)
@@ -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). 
index 3b57275db70db935b9238ac803ef4fb55f516e84..e9efbaffeb942af194ac7d3905f33d3094b5c477 100755 (executable)
@@ -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
index 444c53beff080f288efd16138c94a579eb7e3344..4f5bb53a78e8cece8241ce3bcbd722f9b64bb048 100755 (executable)
@@ -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<String, ShellyBaseHandler> 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<String, Object> configProperties) {
+            @Reference ShellyTranslationProvider translationProvider, @Reference ShellyThingTable thingTable,
+            @Reference HttpClientFactory httpClientFactory, ComponentContext componentContext,
+            Map<String, Object> 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<String, ShellyManagerInterface> getThingHandlers() {
-        return new HashMap<>(deviceListeners);
+        Map<String, ShellyManagerInterface> table = new HashMap<>();
+        for (Map.Entry<String, ShellyThingInterface> 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<String, String> parameters) {
         logger.trace("{}: Dispatch event to thing handler", deviceName);
-        for (Map.Entry<String, ShellyBaseHandler> listener : deviceListeners.entrySet()) {
-            ShellyBaseHandler thingHandler = listener.getValue();
+        for (Map.Entry<String, ShellyThingInterface> 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 (file)
index 0000000..a7238a8
--- /dev/null
@@ -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<String, String> 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;
+}
index 83ea6a763c5fdce5bf88b7c2bab80088a37a6d45..39158923a03dff1adee770d2b33d7827ca103ec1 100644 (file)
@@ -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<ShellySettingsRelay> 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<ShellySettingsRoller> rollers;
         public ArrayList<ShellySettingsDimmer> dimmers;
         public ArrayList<ShellySettingsRgbwLight> lights;
         public ArrayList<ShellySettingsEMeter> emeters;
         public ArrayList<ShellySettingsInput> inputs; // ix3
-
         public ArrayList<ShellyThermnostat> 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<ShellyFavPos> 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;
     }
index ad3b34ae5c605faaa8780a8291887879b8cc5d06..774fc88ed9a45c3a5d2b5bddcc3c043ef4a83c72 100644 (file)
@@ -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);
index a8906c84924d4cddfa5741ef71a51ff60bf7c79c..38cb916d066c1a5704fe194f38f9fa34debfeeff 100644 (file)
@@ -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: /<color mode>/<light id>?parm?value
         // Dimmer: /light/<light id>?parm=value
         request(getControlUriPrefix(lightIndex) + "?" + parm + "=" + value);
     }
 
-    public void setLightParms(Integer lightIndex, Map<String, String> parameters) throws ShellyApiException {
+    @Override
+    public void setLightParms(int lightIndex, Map<String, String> 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;
     }
index 0ffe7faed82c058e38b9fa9ddfe07847f406ce4a..ce51241893d9042e7e04452d68a59d930f947b1a 100644 (file)
@@ -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<String, CoIotDescrBlk> blkMap;
     protected final Map<String, CoIotDescrSen> 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<String, CoIotDescrBlk> blkMap,
+    public ShellyCoIoTProtocol(String thingName, ShellyThingInterface thingHandler, Map<String, CoIotDescrBlk> blkMap,
             Map<String, CoIotDescrSen> sensorMap) {
         this.thingName = thingName;
         this.thingHandler = thingHandler;
index cf499df85c889853302b9c57b4b97852a4df9908..9412162c71e0cb887d9957d0d9065c64d5a9bff6 100644 (file)
@@ -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<String, CoIotDescrBlk> blkMap,
+    public ShellyCoIoTVersion1(String thingName, ShellyThingInterface thingHandler, Map<String, CoIotDescrBlk> blkMap,
             Map<String, CoIotDescrSen> 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));
index 3b7f410697db230ff6c038517ee97a4b27d755d5..efbe077fc3617b7d23c73d4115ac7de250de034a 100644 (file)
@@ -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<String, CoIotDescrBlk> blkMap,
+    public ShellyCoIoTVersion2(String thingName, ShellyThingInterface thingHandler, Map<String, CoIotDescrBlk> blkMap,
             Map<String, CoIotDescrSen> 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;
index d63d2987272377005c4077364bdb04c4d32e22ef..9b466c9b0de533d888f047c73820ac395fb831fc 100644 (file)
@@ -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<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
     private Map<String, CoIotDescrSen> 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);
index 71ef74bd4980ecee1b870867ad8d6891b8511982..588be2e195e42278015e480f606fd6fe6fd0850a 100644 (file)
@@ -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<String, Object> properties) {
index 027359e274e1698df2f76ced88a4260fa7023177..aa40baa5bd8d3698736eddab90d18634ce1b2515 100755 (executable)
@@ -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 = "";
 }
index 0353cc772d8c95d7f6038dd00d81859e46e01ea4..c185d34d93ff93822da9bbb070469bd56c22fcf2 100755 (executable)
@@ -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<String, Object> 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());
index 247537419411df93be785e0ee4fe5da666e434fa..924d6b847509bb184499d997ed63befb98bcbf10 100644 (file)
@@ -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); // <DT>-relay / <DT>-roller
             if (res != null) {
                 return res;
             }
         }
+
         String res = THING_TYPE_MAPPING.get(type);
         if (res != null) {
             return res;
index ab4b2024ac2f1c8e8e6d28c79fb7c78a873eed04..f0a4ddd752aa970f42fd9cf1ca1e6802906feb79 100755 (executable)
@@ -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<Object> 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<String, Channel> dynChannels) {
+    @Override
+    public void updateChannelDefinitions(Map<String, Channel> 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<String, Object> 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<String, String> 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<String, String> 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);
+        }
+    }
 }
index 188b3de23513dcf7838b4c465fbeddef314792cf..9d68b0b91ab831139ed2776b23e9f23fe0f3fa64 100644 (file)
@@ -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);
                 }
             }
index c8f77c18a712e9d637ddc2f9c2ff2d8871e9293a..54f722cb7117ce36d302c7a1cde756a11a20785f 100644 (file)
@@ -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);
index d6006efc88ff46eb824bf440ad14fa4f44c500b7..a7b48286043e9799d0d5425f6d42d2752e18e880 100644 (file)
@@ -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();
 
index 0f5a08a97a8e163cdb059304413374424a0d3f72..8fbe6210e7570e191472d50aaab8450e95334043 100644 (file)
@@ -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 (file)
index 0000000..37a43c7
--- /dev/null
@@ -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<String, Channel> 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<Object> 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 (file)
index 0000000..a7e5356
--- /dev/null
@@ -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<String, ShellyThingInterface> 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<String, ShellyThingInterface> 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<String, ShellyThingInterface> getTable() {
+        return thingTable;
+    }
+
+    public int size() {
+        return thingTable.size();
+    }
+}
index a942ccae2559798e0a23244c4e5b685ac4ab0326..e13df68b42c050626f37d0cde92b17ab4b66c57c 100644 (file)
@@ -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");
index 29ab82997f268c68010b0e91188db30f2b31ad63..2ac54c81e8b491138151e8a5e66b5b8fc96dd6c5 100644 (file)
@@ -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);
         }
index c16e9bc90e9b2fb20c9d4ab8e2b7e93c8befc9b1..53f6c85508ea860d0e0d477dd5f132b36c7241aa 100644 (file)
@@ -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<String, Channel> 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() {
index 67609d9d0bc7419d8fd5ba070ae63b19d31db497..5ca4780f6559ea17156706cd6967d9f1c5e1c1e7 100644 (file)
@@ -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<String, State> 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) {
index 7c481cc52a26fa734855391eff607ea7ce116f04..dd7b1ea9296686e1b64aa6550a4bbbf50c8fbb7e 100644 (file)
@@ -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) : '*';
+    }
 }
index 302724b306ea64c09a6b81a9988854da8f94e3ac..d8562f51de13e237ac5f86a7e0c27a28e5ecc88f 100644 (file)
@@ -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"));
     }
 }
index 6cf062627c17a0b10e46dde6ff639559d40ac40e..f84023962818a585ed40b4ab020902a205a5c819 100644 (file)
@@ -78,7 +78,7 @@
                        <default>0</default>
                </parameter>
                <parameter name="eventsRoller" type="boolean" required="false">
-                       <label>@text/thing-type.config.shelly.roller.eventsRoller.label)</label>
+                       <label>@text/thing-type.config.shelly.roller.eventsRoller.label</label>
                        <description>@text/thing-type.config.shelly.roller.eventsRoller.description</description>
                        <advanced>true</advanced>
                        <default>false</default>
index 0978d4f3781b60f1a75f327788dd399c4a0e53d2..9e1d48a37f286dd9ec070ab687525120cfa1be19 100644 (file)
@@ -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
index 088c13a3f85250051f62151c7aa0b9703d0f2975..3dc2cce3cab034d451ebc2aada871e877f6022e1 100644 (file)
@@ -10,7 +10,6 @@
                <channels>
                        <channel id="alarm" typeId="alarmTrigger"/>
                        <channel id="wifiSignal" typeId="system.signal-strength"/>
-                       <channel id="uptime" typeId="uptime"/>
                </channels>
        </channel-group-type>
 
                </state>
        </channel-type>
        <channel-type id="supplyVoltage" advanced="true">
-               <item-type>Number:ElectricPotentia</item-type>
+               <item-type>Number:ElectricPotential</item-type>
                <label>@text/channel-type.shelly.supplyVoltage.label</label>
                <description>@text/channel-type.shelly.supplyVoltage.description</description>
                <category>Energy</category>
                        <tag>Measurement</tag>
                        <tag>Voltage</tag>
                </tags>
+               <state readOnly="true" pattern="%.0f %unit%"></state>
        </channel-type>
        <channel-type id="selfTest">
                <item-type>String</item-type>
index ea20f72115a82cf9e3f25c947b6b1156cd1cc330..1d37aa162713232eb6de3fc787c70c06a5ffd0a2 100644 (file)
                <item-type>Dimmer</item-type>
                <label>@text/channel-type.shelly.rollerPosition.label</label>
                <description>@text/channel-type.shelly.rollerPosition.description</description>
-               <category>Blinds</category>
                <tags>
                        <tag>Measurement</tag>
                        <tag>Level</tag>
index 06bb86daef648a73a538559d724b15e8d7e55b36..f95fc271ce7031669b89fcb52673fa6f80853f50 100644 (file)
                <item-type>String</item-type>
                <label>@text/channel-type.shelly.controlMode.label</label>
                <description>@text/channel-type.shelly.controlMode.description</description>
-               <state>
+               <state readOnly="false">
                        <options>
                                <option value="manual">@text/channel-type.shelly.controlMode.state.option.manual</option>
                                <option value="automatic">@text/channel-type.shelly.controlMode.state.option.automatic</option>
        </channel-type>
 
        <channel-type id="sensorPPM">
-               <item-type>Number:Density</item-type>
+               <item-type>Number:Dimensionless</item-type>
                <label>@text/channel-type.shelly.sensorPPM.label</label>
                <description>@text/channel-type.shelly.sensorPPM.description</description>
                <category>Gas</category>
                        <tag>Measurement</tag>
                        <tag>Gas</tag>
                </tags>
-               <state readOnly="true" pattern="%.0f %unit%">
+               <state readOnly="true" pattern="%.0f ppm">
                </state>
        </channel-type>