From: Markus Michels Date: Fri, 29 Jul 2022 17:48:10 +0000 (+0200) Subject: This PR is just to reduce the delta before I create the PR for Plus/Pro (#13176) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=8aa88bc3d9066f1e4627b75f24d43399a63443a5;p=openhab-addons.git This PR is just to reduce the delta before I create the PR for Plus/Pro (#13176) support. In general Gen1 and Gen2 devices have a total different API so I spliced the API code in version/gen 1 and 2. This means restructuring the classes (e.h. api+coap became api+api1+api2) and therefor dummy changes in the code, e.g. import statements. This creates a lot of overhead and separating those "dummy changes" simplifies merging the actual PR. Signed-off-by: Markus Michels --- diff --git a/bundles/org.openhab.binding.shelly/README.md b/bundles/org.openhab.binding.shelly/README.md index 1f686d63a3..e4d3ffa5bd 100644 --- a/bundles/org.openhab.binding.shelly/README.md +++ b/bundles/org.openhab.binding.shelly/README.md @@ -27,6 +27,8 @@ Also check out the [Shelly Manager](doc/ShellyManager.md), which ## Supported Devices +### Generation 1: + | thing-type | Model | Vendor ID | |--------------------|--------------------------------------------------------|-----------| | shelly1 | Shelly 1 Single Relay Switch | SHSW-1 | @@ -745,22 +747,28 @@ 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) +### Shelly RGBW2 in Color 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 | +| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF command; in seconds| +| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON command; in seconds| | |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. +|color |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, bight not white | +| |full |String |r/w |Set Red / Green / Blue / Yellow / White mode and switch mode | +| | | |r/w |Valid settings: "red", "green", "blue", "yellow", "white" or "r,g,b,w" | +| |red |Dimmer |r/w |Red brightness: 0..100% or 0..255 (control only the red channel) | +| |green |Dimmer |r/w |Green brightness: 0..100% or 0..255 (control only the green channel) | +| |blue |Dimmer |r/w |Blue brightness: 0..100% or 0..255 (control only the blue channel) | +| |white |Dimmer |r/w |White brightness: 0..100% or 0..255 (control only the white channel) | +| |gain |Dimmer |r/w |Gain setting: 0..100% or 0..100 | +| |effect |Number |r/w |Puts the light into effect mode: 0..3) | +| | | | |0=No effect, 1=Meteor Shower, 2=Gradual Change, 3=Flash | +|meter |currentWatts |Number |yes |Current power consumption in Watts | +| |lastPower1 |Number |yes |Energy consumption for a round minute, 1 minute ago | +| |totalKWH |Number |yes |Total energy consumption in kWh since the device powered up (resets on restart)| +| |lastUpdate |DateTime |yes |Timestamp of the last measurement | ### Shelly RGBW2 in White Mode (thing-type: shellyrgbw2-white) diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java index e9efbaffeb..2c0e00f10a 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyBindingConstants.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.shelly.internal; +import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*; + import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; @@ -33,174 +35,6 @@ public class ShellyBindingConstants { public static final String BINDING_ID = "shelly"; public static final String SYSTEM_ID = "system"; - // Type names - public static final String THING_TYPE_SHELLY1_STR = "shelly1"; - public static final String THING_TYPE_SHELLY1L_STR = "shelly1l"; - public static final String THING_TYPE_SHELLY1PM_STR = "shelly1pm"; - public static final String THING_TYPE_SHELLYEM_STR = "shellyem"; - public static final String THING_TYPE_SHELLY3EM_STR = "shellyem3"; // bad: misspelled product name, it's 3EM - public static final String THING_TYPE_SHELLY2_PREFIX = "shellyswitch"; - public static final String THING_TYPE_SHELLY2_RELAY_STR = "shelly2-relay"; - public static final String THING_TYPE_SHELLY2_ROLLER_STR = "shelly2-roller"; - public static final String THING_TYPE_SHELLY25_PREFIX = "shellyswitch25"; - public static final String THING_TYPE_SHELLY25_RELAY_STR = "shelly25-relay"; - public static final String THING_TYPE_SHELLY25_ROLLER_STR = "shelly25-roller"; - public static final String THING_TYPE_SHELLY4PRO_STR = "shelly4pro"; - public static final String THING_TYPE_SHELLYPLUG_STR = "shellyplug"; - public static final String THING_TYPE_SHELLYPLUGS_STR = "shellyplugs"; - public static final String THING_TYPE_SHELLYPLUGU1_STR = "shellyplugu1"; // Shely Plug US - public static final String THING_TYPE_SHELLYDIMMER_STR = "shellydimmer"; - public static final String THING_TYPE_SHELLYDIMMER2_STR = "shellydimmer2"; - public static final String THING_TYPE_SHELLYIX3_STR = "shellyix3"; - public static final String THING_TYPE_SHELLYBULB_STR = "shellybulb"; - public static final String THING_TYPE_SHELLYDUO_STR = "shellybulbduo"; - public static final String THING_TYPE_SHELLYVINTAGE_STR = "shellyvintage"; - public static final String THING_TYPE_SHELLYRGBW2_PREFIX = "shellyrgbw2"; - public static final String THING_TYPE_SHELLYRGBW2_COLOR_STR = THING_TYPE_SHELLYRGBW2_PREFIX + "-color"; - public static final String THING_TYPE_SHELLYRGBW2_WHITE_STR = THING_TYPE_SHELLYRGBW2_PREFIX + "-white"; - public static final String THING_TYPE_SHELLYDUORGBW_STR = "shellycolorbulb"; - public static final String THING_TYPE_SHELLYHT_STR = "shellyht"; - public static final String THING_TYPE_SHELLYSMOKE_STR = "shellysmoke"; - public static final String THING_TYPE_SHELLYGAS_STR = "shellygas"; - public static final String THING_TYPE_SHELLYFLOOD_STR = "shellyflood"; - public static final String THING_TYPE_SHELLYDOORWIN_STR = "shellydw"; - public static final String THING_TYPE_SHELLYDOORWIN2_STR = "shellydw2"; - public static final String THING_TYPE_SHELLYEYE_STR = "shellyseye"; - 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"; - - // Device Types - public static final String SHELLYDT_1 = "SHSW-1"; - public static final String SHELLYDT_1PM = "SHSW-PM"; - public static final String SHELLYDT_1L = "SHSW-L"; - public static final String SHELLYDT_SHPLG = "SHPLG-1"; - public static final String SHELLYDT_SHPLG_S = "SHPLG-S"; - public static final String SHELLYDT_SHPLG_U1 = "SHPLG-U1"; - public static final String SHELLYDT_SHELLY2 = "SHSW-21"; - public static final String SHELLYDT_SHELLY25 = "SHSW-25"; - public static final String SHELLYDT_SHPRO = "SHSW-44"; - public static final String SHELLYDT_EM = "SHEM"; - public static final String SHELLYDT_3EM = "SHEM-3"; - public static final String SHELLYDT_HT = "SHHT-1"; - public static final String SHELLYDT_DW = "SHDW-1"; - public static final String SHELLYDT_DW2 = "SHDW-2"; - public static final String SHELLYDT_SENSE = "SHSEN-1"; - public static final String SHELLYDT_MOTION = "SHMOS-01"; - public static final String SHELLYDT_MOTION2 = "SHMOS-02"; - public static final String SHELLYDT_GAS = "SHGS-1"; - public static final String SHELLYDT_DIMMER = "SHDM-1"; - public static final String SHELLYDT_DIMMER2 = "SHDM-2"; - public static final String SHELLYDT_IX3 = "SHIX3-1"; - public static final String SHELLYDT_BULB = "SHBLB-1"; - public static final String SHELLYDT_DUO = "SHBDUO-1"; - public static final String SHELLYDT_DUORGBW = "SHCB-1"; - public static final String SHELLYDT_VINTAGE = "SHVIN-1"; - public static final String SHELLYDT_RGBW2 = "SHRGBW2"; - public static final String SHELLYDT_BUTTON1 = "SHBTN-1"; - public static final String SHELLYDT_BUTTON2 = "SHBTN-2"; - 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); - public static final ThingTypeUID THING_TYPE_SHELLY1PM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1PM_STR); - public static final ThingTypeUID THING_TYPE_SHELLYEM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEM_STR); - public static final ThingTypeUID THING_TYPE_SHELLY3EM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY3EM_STR); - public static final ThingTypeUID THING_TYPE_SHELLY2_RELAY = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLY2_RELAY_STR); - public static final ThingTypeUID THING_TYPE_SHELLY2_ROLLER = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLY2_ROLLER_STR); - public static final ThingTypeUID THING_TYPE_SHELLY25_RELAY = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLY25_RELAY_STR); - public static final ThingTypeUID THING_TYPE_SHELLY25_ROLLER = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLY25_ROLLER_STR); - public static final ThingTypeUID THING_TYPE_SHELLY4PRO = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY4PRO_STR); - public static final ThingTypeUID THING_TYPE_SHELLYPLUG = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUG_STR); - public static final ThingTypeUID THING_TYPE_SHELLYPLUGS = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUGS_STR); - public static final ThingTypeUID THING_TYPE_SHELLYPLUGU1 = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYPLUGU1_STR); - public static final ThingTypeUID THING_TYPE_SHELLYUNI = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYUNI_STR); - public static final ThingTypeUID THING_TYPE_SHELLYDIMMER = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYDIMMER_STR); - public static final ThingTypeUID THING_TYPE_SHELLYDIMMER2 = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYDIMMER2_STR); - public static final ThingTypeUID THING_TYPE_SHELLYIX3 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYIX3_STR); - public static final ThingTypeUID THING_TYPE_SHELLYBULB = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYBULB_STR); - public static final ThingTypeUID THING_TYPE_SHELLYDUO = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYDUO_STR); - public static final ThingTypeUID THING_TYPE_SHELLYVINTAGE = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYVINTAGE_STR); - public static final ThingTypeUID THING_TYPE_SHELLYDUORGBW = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYDUORGBW_STR); - public static final ThingTypeUID THING_TYPE_SHELLYHT = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYHT_STR); - public static final ThingTypeUID THING_TYPE_SHELLYSENSE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSENSE_STR); - public static final ThingTypeUID THING_TYPE_SHELLYSMOKE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSMOKE_STR); - public static final ThingTypeUID THING_TYPE_SHELLYGAS = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYGAS_STR); - public static final ThingTypeUID THING_TYPE_SHELLYFLOOD = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYFLOOD_STR); - public static final ThingTypeUID THING_TYPE_SHELLYDOORWIN = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYDOORWIN_STR); - public static final ThingTypeUID THING_TYPE_SHELLYDOORWIN2 = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYDOORWIN2_STR); - public static final ThingTypeUID THING_TYPE_SHELLYTRV = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYTRV_STR); - public static final ThingTypeUID THING_TYPE_SHELLYBUTTON1 = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYBUTTON1_STR); - public static final ThingTypeUID THING_TYPE_SHELLYBUTTON2 = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYBUTTON2_STR); - public static final ThingTypeUID THING_TYPE_SHELLYEYE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEYE_STR); - public static final ThingTypeUID THING_TYPE_SHELLMOTION = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYMOTION_STR); - public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_COLOR = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYRGBW2_COLOR_STR); - public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_WHITE = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYRGBW2_WHITE_STR); - public static final ThingTypeUID THING_TYPE_SHELLYPROTECTED = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYPROTECTED_STR); - public static final ThingTypeUID THING_TYPE_SHELLYUNKNOWN = new ThingTypeUID(BINDING_ID, - THING_TYPE_SHELLYUNKNOWN_STR); - public static final Set SUPPORTED_THING_TYPES_UIDS = Collections .unmodifiableSet(Stream.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1L, THING_TYPE_SHELLY1PM, THING_TYPE_SHELLYEM, THING_TYPE_SHELLY3EM, THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY2_ROLLER, diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java index 4f5bb53a78..a2cab8c4fc 100755 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/ShellyHandlerFactory.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.shelly.internal; -import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; +import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*; import java.util.HashMap; import java.util.Map; @@ -21,7 +21,7 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.shelly.internal.coap.ShellyCoapServer; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer; import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration; import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler; import org.openhab.binding.shelly.internal.handler.ShellyLightHandler; @@ -62,7 +62,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(ShellyHandlerFactory.class); private final HttpClient httpClient; private final ShellyTranslationProvider messages; - private final ShellyCoapServer coapServer; + private final Shelly1CoapServer coapServer; private final ShellyThingTable thingTable; private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration(); @@ -81,7 +81,6 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory { @Reference ShellyTranslationProvider translationProvider, @Reference ShellyThingTable thingTable, @Reference HttpClientFactory httpClientFactory, ComponentContext componentContext, Map configProperties) { - logger.debug("Activate Shelly HandlerFactory"); super.activate(componentContext); messages = translationProvider; // Save bindingConfig & pass it to all registered listeners @@ -103,10 +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 - logger.info("{}", messages.get("status.managerstarted", localIP, httpPort)); + this.coapServer = new Shelly1CoapServer(); } @Override diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java index a7238a8b73..e267de7e82 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiInterface.java @@ -15,15 +15,15 @@ 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.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyRollerStatus; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsLogin; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyShortLightStatus; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor; import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; /** @@ -47,11 +47,11 @@ public interface ShellyApiInterface { public void setSleepTime(int value) throws ShellyApiException; - public ShellyStatusRelay getRelayStatus(Integer relayIndex) throws ShellyApiException; + public ShellyStatusRelay getRelayStatus(int relayIndex) throws ShellyApiException; public void setRelayTurn(int id, String turnMode) throws ShellyApiException; - public ShellyControlRoller getRollerStatus(int rollerIndex) throws ShellyApiException; + public ShellyRollerStatus getRollerStatus(int rollerIndex) throws ShellyApiException; public void setRollerTurn(int relayIndex, String turnMode) throws ShellyApiException; diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java deleted file mode 100644 index 39158923a0..0000000000 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiJsonDTO.java +++ /dev/null @@ -1,1192 +0,0 @@ -/** - * 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.ArrayList; -import java.util.List; - -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor.ShellyMotionSettings; -import org.openhab.core.thing.CommonTriggerEvents; - -import com.google.gson.annotations.SerializedName; - -/** - * The {@link ShellyApiJsonDTO} is used for the JSon/GSon mapping - * - * @author Markus Michels - Initial contribution - */ -public class ShellyApiJsonDTO { - public static final String SHELLY_NULL_URL = "null"; - public static final String SHELLY_URL_DEVINFO = "/shelly"; - public static final String SHELLY_URL_STATUS = "/status"; - public static final String SHELLY_URL_SETTINGS = "/settings"; - public static final String SHELLY_URL_SETTINGS_AP = "/settings/ap"; - public static final String SHELLY_URL_SETTINGS_STA = "/settings/sta"; - public static final String SHELLY_URL_SETTINGS_LOGIN = "/settings/sta"; - public static final String SHELLY_URL_SETTINGS_CLOUD = "/settings/cloud"; - public static final String SHELLY_URL_LIST_IR = "/ir/list"; - public static final String SHELLY_URL_SEND_IR = "/ir/emit"; - public static final String SHELLY_URL_RESTART = "/reboot"; - - public static final String SHELLY_URL_SETTINGS_RELAY = "/settings/relay"; - public static final String SHELLY_URL_STATUS_RELEAY = "/status/relay"; - public static final String SHELLY_URL_CONTROL_RELEAY = "/relay"; - - public static final String SHELLY_URL_SETTINGS_EMETER = "/settings/emeter"; - public static final String SHELLY_URL_STATUS_EMETER = "/emeter"; - public static final String SHELLY_URL_DATA_EMETER = "/emeter/{0}/em_data.csv"; - - public static final String SHELLY_URL_CONTROL_ROLLER = "/roller"; - public static final String SHELLY_URL_SETTINGS_ROLLER = "/settings/roller"; - - public static final String SHELLY_URL_SETTINGS_LIGHT = "/settings/light"; - public static final String SHELLY_URL_STATUS_LIGHT = "/light"; - public static final String SHELLY_URL_CONTROL_LIGHT = "/light"; - - public static final String SHELLY_URL_SETTINGS_DIMMER = "/settings/light"; - - // Wakeup reasons - public static final String SHELLY_WAKEUPT_SENSOR = "SENSOR"; // new sensordata - 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 - - // - // Action URLs according to the device type - // - public static final String SHELLY_EVENTURL_SUFFIX = "_url"; - - // Relay - public static final String SHELLY_EVENT_BTN_ON = "btn_on"; - public static final String SHELLY_EVENT_BTN_OFF = "btn_off"; - public static final String SHELLY_EVENT_OUT_ON = "out_on"; - public static final String SHELLY_EVENT_OUT_OFF = "out_off"; - public static final String SHELLY_EVENT_SHORTPUSH = "shortpush"; - public static final String SHELLY_EVENT_LONGPUSH = "longpush"; - // Button - public static final String SHELLY_EVENT_DOUBLE_SHORTPUSH = "double_shortpush"; - public static final String SHELLY_EVENT_TRIPLE_SHORTPUSH = "triple_shortpush"; - public static final String SHELLY_EVENT_SHORT_LONGTPUSH = "shortpush_longpush"; - public static final String SHELLY_EVENT_LONG_SHORTPUSH = "longpush_shortpush"; - - // Dimmer - public static final String SHELLY_EVENT_BTN1_ON = "btn1_on"; - public static final String SHELLY_EVENT_BTN1_OFF = "btn1_off"; - public static final String SHELLY_EVENT_BTN2_ON = "btn2_on"; - public static final String SHELLY_EVENT_BTN2_OFF = "btn2_off"; - public static final String SHELLY_EVENT_SHORTPUSH1 = "btn1_shortpush"; - public static final String SHELLY_EVENT_LONGPUSH1 = "btn1_longpush"; - public static final String SHELLY_EVENT_SHORTPUSH2 = "btn2_shortpush"; - public static final String SHELLY_EVENT_LONGPUSH2 = "btn2_longpush"; - - // Roller - 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"; - public static final String SHELLY_EVENT_DARK = "dark"; - public static final String SHELLY_EVENT_TWILIGHT = "twilight"; - public static final String SHELLY_EVENT_BRIGHT = "bright"; - public static final String SHELLY_EVENT_FLOOD_DETECTED = "flood_detected"; - public static final String SHELLY_EVENT_FLOOD_GONE = "flood_gone"; - public static final String SHELLY_EVENT_VIBRATION = "vibration"; // DW 1.6.5+ - public static final String SHELLY_EVENT_OPEN = "open"; // DW 1.6.5+ - public static final String SHELLY_EVENT_CLOSE = "close"; // DW 1.6.5+ - public static final String SHELLY_EVENT_TEMP_OVER = "temp_over"; // FW 1.7 - public static final String SHELLY_EVENT_TEMP_UNDER = "temp_under"; // FW 1.7 - - // Gas - public static final String SHELLY_EVENT_ALARM_MILD = "alarm_mild"; // DW 1.7+ - public static final String SHELLY_EVENT_ALARM_HEAVY = "alarm_heavy"; // DW 1.7+ - public static final String SHELLY_EVENT_ALARM_OFF = "alarm_off"; // DW 1.7+ - - // - // 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"; - public static final String SHELLY_BTNT_TWO_BUTTON = "dual_button"; - public static final String SHELLY_BTNT_TOGGLE = "toggle"; - public static final String SHELLY_BTNT_EDGE = "edge"; - public static final String SHELLY_BTNT_DETACHED = "detached"; - - public static final String SHELLY_STATE_LAST = "last"; - 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"; - - public static final String SHELLY_ALWD_TRIGGER_NONE = "none"; - public static final String SHELLY_ALWD_ROLLER_TURN_OPEN = "open"; - public static final String SHELLY_ALWD_ROLLER_TURN_CLOSE = "close"; - public static final String SHELLY_ALWD_ROLLER_TURN_STOP = "stop"; - - // API Error Codes - public static final String SHELLY_APIERR_UNAUTHORIZED = "Unauthorized"; - public static final String SHELLY_APIERR_TIMEOUT = "Timeout"; - public static final String SHELLY_APIERR_NOT_CALIBRATED = "Not calibrated!"; - - // API device types / properties - public static final String SHELLY_CLASS_RELAY = "relay"; // Relay: relay mode - public static final String SHELLY_CLASS_ROLLER = "roller"; // Relay: roller mode - public static final String SHELLY_CLASS_LIGHT = "light"; // Bulb: color mode - public static final String SHELLY_CLASS_EMETER = "emeter"; // EM/EM3: emeter - - public static final String SHELLY_API_ON = "on"; - public static final String SHELLY_API_OFF = "off"; - public static final String SHELLY_API_TRUE = "true"; - public static final String SHELLY_API_FALSE = "false"; - - public static final String SHELLY_API_MODE = "mode"; - public static final String SHELLY_MODE_RELAY = "relay"; // Relay: relay mode - public static final String SHELLY_MODE_ROLLER = "roller"; // Relay: roller mode - public static final String SHELLY_MODE_COLOR = "color"; // Bulb/RGBW2: color mode - public static final String SHELLY_MODE_WHITE = "white"; // Bulb/RGBW2: white mode - - public static final String SHELLY_LED_STATUS_DISABLE = "led_status_disable"; - public static final String SHELLY_LED_POWER_DISABLE = "led_power_disable"; - - public static final String SHELLY_API_STOPR_NORMAL = "normal"; - public static final String SHELLY_API_STOPR_SAFETYSW = "safety_switch"; - public static final String SHELLY_API_STOPR_OBSTACLE = "obstacle"; - public static final String SHELLY_API_STOPR_OVERPOWER = "overpower"; - - public static final String SHELLY_TIMER_AUTOON = "auto_on"; - public static final String SHELLY_TIMER_AUTOOFF = "auto_off"; - public static final String SHELLY_TIMER_ACTIVE = "has_timer"; - - public static final String SHELLY_LIGHT_TURN = "turn"; - public static final String SHELLY_LIGHT_DEFSTATE = "def_state"; - public static final String SHELLY_LIGHTTIMER = "timer"; - - public static final String SHELLY_COLOR_RED = "red"; - public static final String SHELLY_COLOR_BLUE = "blue"; - public static final String SHELLY_COLOR_GREEN = "green"; - public static final String SHELLY_COLOR_YELLOW = "yellow"; - public static final String SHELLY_COLOR_WHITE = "white"; - public static final String SHELLY_COLOR_GAIN = "gain"; - public static final String SHELLY_COLOR_BRIGHTNESS = "brightness"; - public static final String SHELLY_COLOR_TEMP = "temp"; - public static final String SHELLY_COLOR_EFFECT = "effect"; - - public static final int SHELLY_MIN_ROLLER_POS = 0; - public static final int SHELLY_MAX_ROLLER_POS = 100; - public static final int SHELLY_MIN_BRIGHTNESS = 0; - public static final int SHELLY_MAX_BRIGHTNESS = 100; - public static final int SHELLY_MIN_GAIN = 0; - public static final int SHELLY_MAX_GAIN = 100; - public static final int SHELLY_MIN_COLOR = 0; - public static final int SHELLY_MAX_COLOR = 255; - public static final int SHELLY_DIM_STEPSIZE = 10; - - // color temperature: 3000 = warm, 4750 = white, 6565 = cold; gain: 0..100 - public static final int MIN_COLOR_TEMP_BULB = 3000; - public static final int MAX_COLOR_TEMP_BULB = 6500; - public static final int MIN_COLOR_TEMP_DUO = 2700; - public static final int MAX_COLOR_TEMP_DUO = 6500; - public static final int COLOR_TEMP_RANGE_BULB = MAX_COLOR_TEMP_DUO - MIN_COLOR_TEMP_DUO; - public static final int COLOR_TEMP_RANGE_DUO = MAX_COLOR_TEMP_DUO - MIN_COLOR_TEMP_DUO; - public static final double MIN_BRIGHTNESS = 0.0; - public static final double MAX_BRIGHTNESS = 100.0; - public static final double SATURATION_FACTOR = 2.55; - public static final double GAIN_FACTOR = SHELLY_MAX_GAIN / 100; - public static final double BRIGHTNESS_FACTOR = SHELLY_MAX_BRIGHTNESS / 100; - - // Door/Window - public static final String SHELLY_API_ILLUM_DARK = "dark"; - public static final String SHELLY_API_ILLUM_TWILIGHT = "twilight"; - public static final String SHELLY_API_ILLUM_BRIGHT = "bright"; - public static final String SHELLY_API_DWSTATE_OPEN = "open"; - public static final String SHELLY_API_DWSTATE_CLOSE = "close"; - - // Shelly Sense - public static final String SHELLY_IR_CODET_STORED = "stored"; - public static final String SHELLY_IR_CODET_PRONTO = "pronto"; - public static final String SHELLY_IR_CODET_PRONTO_HEX = "pronto_hex"; - - // Bulb/Duo/RGBW2 - public static final int SHELLY_MIN_EFFECT = 0; - public static final int SHELLY_MAX_EFFECT = 6; - - // Button - public static final String SHELLY_BTNEVENT_1SHORTPUSH = "S"; - public static final String SHELLY_BTNEVENT_2SHORTPUSH = "SS"; - public static final String SHELLY_BTNEVENT_3SHORTPUSH = "SSS"; - public static final String SHELLY_BTNEVENT_LONGPUSH = "L"; - public static final String SHELLY_BTNEVENT_SHORTLONGPUSH = "SL"; - public static final String SHELLY_BTNEVENT_LONGSHORTPUSH = "LS"; - - public static final String SHELLY_TEMP_CELSIUS = "C"; - public static final String SHELLY_TEMP_FAHRENHEIT = "F"; - - // Motion - public static final int SHELLY_MOTION_SLEEPTIME_OFFSET = 3; // we need to substract and offset - - // TRV - public static final int SHELLY_TRV_MIN_TEMP = 5; // < 5: means: lowest (valve fully closed) - public static final int SHELLY_TRV_MAX_TEMP = 30; // > 30: means: highest (valve fully open) - - public static final String SHELLY_TRV_MODE_MANUAL = "manual"; - public static final String SHELLY_TRV_MODE_AUTO = "automatic"; - - // CoIoT Multicast setting - public static final String SHELLY_COIOT_MCAST = "mcast"; - - public static class ShellySettingsDevice { - public String type; - public String mac; - public String hostname; - public String fw; - public Boolean auth; - - @SerializedName("coiot") // Shelly Motion Multicast Endpoint - public String coiot; - public Integer longid; - - @SerializedName("num_outputs") - public Integer numOutputs; - @SerializedName("num_meters") - public Integer numMeters; - @SerializedName("num_emeters") - public Integer numEMeters; - @SerializedName("num_rollers") - public Integer numRollers; - } - - public static class ShellySettingsWiFiAp { - public Boolean enabled; - public String ssid; - public String key; - } - - public static class ShellySettingsWiFiNetwork { - public Boolean enabled; - public String ssid; - public Integer rssi; - - @SerializedName("ipv4_method") - public String ipv4Method; - public String ip; - public String gw; - public String mask; - public String dns; - } - - public static class ShellySettingsMqtt { - public Boolean enable; - public String server; - public String user; - @SerializedName("reconnect_timeout_max") - public Double reconnectTimeoutMax; - @SerializedName("reconnect_timeout_min") - public Double reconnectTimeoutMin; - @SerializedName("clean_session") - public Boolean cleanSession; - @SerializedName("keep_alive") - public Integer keepAlive; - @SerializedName("will_topic") - public String willTopic; - @SerializedName("will_message") - public String willMessage; - @SerializedName("max_qos") - public Integer maxQOS; - public Boolean retain; - @SerializedName("update_period") - public Integer updatePeriod; - } - - public static class ShellySettingsCoiot { // FW 1.6+ - @SerializedName("update_period") - public Integer updatePeriod; - public Boolean enabled; // Motion 1.0.7: Coap can be disabled - public String peer; // if set the device uses singlecast CoAP, mcast=set back to Multicast - } - - public static class ShellyStatusMqtt { - public Boolean connected; - } - - public static class ShellySettingsSntp { - public String server; - public Boolean enabled; - } - - public static class ShellySettingsLogin { - public Boolean enabled; - public Boolean unprotected; - public String username; - public String password; - } - - public static class ShellySettingsBuildInfo { - @SerializedName("build_id") - public String buildId; - @SerializedName("build_timestamp") - public String buildTimestamp; - @SerializedName("build_version") - public String buildVersion; - } - - public static class ShellyStatusCloud { - public Boolean enabled; - public Boolean connected; - } - - public static class ShellySettingsHwInfo { - @SerializedName("hw_revision") - public String hwRevision; - @SerializedName("batch_id") - public Integer batchId; - } - - public static class ShellySettingsScheduleRules { - } - - public static class ShellySettingsRelay { - public String name; - @SerializedName("default_state") - public String defaultState; // Accepted values: off, on, last, switch - @SerializedName("btn_type") - public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx - @SerializedName("btn1_type") // Shelly 1L - public String btnType1; - @SerializedName("btn2_type") // Shelly 1L - public String btnType2; - @SerializedName("has_timer") - public Boolean hasTimer; // Whether a timer is currently armed for this channel - @SerializedName("auto_on") - public Double autoOn; // Automatic flip back timer, seconds. Will engage after turning Shelly1 OFF. - @SerializedName("auto_off") - public Double autoOff; // Automatic flip back timer, seconds. Will engage after turning Shelly1 ON. - @SerializedName("btn_on_url") - public String btnOnUrl; // input is activated - @SerializedName("btnOffUrl") - public String btnOffUrl; // input is deactivated - @SerializedName("out_on_url") - public String outOnUrl; // output is activated - @SerializedName("out_off_url") - public String outOffUrl; // output is deactivated - @SerializedName("roller_open_url") - public String rollerOpenUrl; // to access when roller reaches open position - @SerializedName("roller_close_url") - public String rollerCloseUrl; // to access when roller reaches close position - @SerializedName("roller_stop_url") - public String rollerStopUrl; // to access when roller stopped - @SerializedName("longpush_url") - 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 { - public String name; // unique name of the device - public Boolean ison; // true: output is ON - @SerializedName("default_state") - public String defaultState; // Accepted values: off, on, last, switch - @SerializedName("auto_on") - public Double autoOn; // Automatic flip back timer, seconds. Will engage after turning Shelly1 OFF. - @SerializedName("auto_off") - public Double autoOff; // Automatic flip back timer, seconds. Will engage after turning Shelly1 ON. - @SerializedName("btn1_on_url") - public String btn1OnUrl; // URL to access when SW input is activated - @SerializedName("btn1_off_url") - public String btn1OffUrl; // URL to access when SW input is deactivated - @SerializedName("btn2_on_url") - public String btn2OnUrl; // URL to access when SW input is activated - @SerializedName("btn2_off_url") - public String btn2OoffUrl; // URL to access when SW input is deactivated - @SerializedName("out_on_url") - public String outOnUrl; // URL to access when output is activated - @SerializedName("out_off_url") - public String outOffUrl; // URL to access when output is deactivated - @SerializedName("longpush_url") - public String pushLongUrl; // long push button event - @SerializedName("shortpush_url") - public String pushShortUrl; // short push button event - @SerializedName("btn_type") - public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx - @SerializedName("btn1_type") - public String btnType1; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx - @SerializedName("btn2_type") - public String btnType2; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx - @SerializedName("swap_inputs") - public Integer swapInputs; // 0=no - } - - public static class ShellySettingsRoller { - public Double maxtime; - @SerializedName("maxtime_open") - public Double maxtimeOpen; - @SerializedName("maxtime_close") - public Double maxtimeClose; - @SerializedName("default_state") - public String defaultState; // see SHELLY_STATE_xxx - public Boolean swap; - @SerializedName("swap_inputs") - public Boolean swapInputs; - @SerializedName("input_mode") - public String inputMode; // see SHELLY_INP_MODE_OPENCLOSE - @SerializedName("button_type") - public String buttonType; // // see SHELLY_BTNT_xxx - @SerializedName("btn_Reverse") - public Integer btnReverse; - public String state; - public Double power; - @SerializedName("is_valid") - public Boolean isValid; - @SerializedName("safety_switch") - public Boolean safetySwitch; - @SerializedName("obstacle_mode") - public String obstaclMode; // SHELLY_OBSTMODE_ - @SerializedName("obstacle_action") - public String obstacleAction; // see SHELLY_STATE_xxx - @SerializedName("obstacle_power") - public Integer obstaclePower; - @SerializedName("obstacle_delay") - public Integer obstacleDelay; - @SerializedName("safety_mode") - public String safetyMode; // see SHELLY_SAFETYM_xxx - @SerializedName("safety_action") - public String safetyAction; // see SHELLY_STATE_xxx - @SerializedName("safety_allowed_on_trigger") - public String safetyAllowedOnTrigger; // see SHELLY_ALWD_TRIGGER_xxx - @SerializedName("off_power") - public Integer offPower; - public Boolean positioning; - } - - public static class ShellySettingsRgbwLight { - public String name; - public Boolean ison; // true: output is ON - public Integer brightness; - public Integer transition; - @SerializedName("default_state") - public String defaultState; - @SerializedName("auto_on") - public Double autoOn; // Automatic flip back timer, seconds. Will engage after turning Shelly1 OFF. - @SerializedName("auto_off") - public Double autoOff; // Automatic flip back timer, seconds. Will engage after turning Shelly1 ON. - public Boolean schedule; - @SerializedName("btn_type") - public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx - @SerializedName("btn_reverse") - public Integer btnReverse; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx - @SerializedName("out_on_url") - public String outOnUrl; // output is activated - @SerializedName("out_off_url") - public String outOffUrl; // output is deactivated - } - - public static class ShellyFavPos { // FW 1.9.2+ in roller mode - public String name; - public Integer pos; - } - - public static class ShellyInputState { - public Integer input; - - // Shelly Button - public String event; - @SerializedName("event_cnt") - public Integer eventCount; - } - - public static class ShellySettingsMeter { - @SerializedName("is_valid") - public Boolean isValid; - public Double power; - public Double[] counters = { 0.0, 0.0, 0.0 }; - public Double total; - public Long timestamp; - } - - public static class ShellySettingsEMeter { // ShellyEM meter - @SerializedName("is_valid") - public Boolean isValid; // Whether the associated meter is functioning properly - public Double power; // Instantaneous power, Watts - public Double reactive; // Instantaneous reactive power, Watts - public Double voltage; // RMS voltage, Volts - public Double total; // Total consumed energy, Wh - @SerializedName("total_returned") - public Double totalReturned; // Total returned energy, Wh - - public Double pf; // 3EM - public Double current; // 3EM - } - - public static class ShellySettingsUpdate { - public String status; - @SerializedName("has_update") - public Boolean hasUpdate; - @SerializedName("new_version") - public String newVersion; - @SerializedName("old_version") - public String oldVersion; - @SerializedName("beta_version") - public String betaVersion; - } - - public static class ShellySettingsGlobal { - // https://shelly-api-docs.shelly.cloud/#shelly1pm-settings - public ShellySettingsDevice device; - @SerializedName("wifi_ap") - public ShellySettingsWiFiAp wifiAp; - @SerializedName("wifi_sta") - public ShellySettingsWiFiNetwork wifiSta; - @SerializedName("wifi_sta1") - public ShellySettingsWiFiNetwork wifiSta1; - @SerializedName("wifirecovery_reboot_enabled") - public Boolean wifiRecoveryReboot; // FW 1.10+ - @SerializedName("ap_roaming") - public ShellyApRoaming apRoaming; // FW 1.10+ - - public ShellySettingsMqtt mqtt; // not used for now - public ShellySettingsSntp sntp; // not used for now - public ShellySettingsCoiot coiot; // Firmware 1.6+ - public ShellySettingsLogin login; - @SerializedName("pin_code") - public String pinCode; - @SerializedName("coiot_execute_enable") - public Boolean coiotExecuteEnable; - public String name; - public Boolean discoverable; // FW 1.6+ - public String fw; - @SerializedName("build_info") - public ShellySettingsBuildInfo buildInfo; - public ShellyStatusCloud cloud; - @SerializedName("sleep_mode") - public ShellySensorSleepMode sleepMode; // FW 1.6 - @SerializedName("external_power") - public Integer externalPower; // H&T FW 1.6, seems to be the same like charger for the Sense - @SerializedName("debug_enable") // FW 1.10+ - public Boolean debugEnable; - - public String timezone; - public Double lat; - public Double lng; - public Boolean tzautodetect; - public String time; - - public ShellySettingsHwInfo hwinfo; - public String mode; - @SerializedName("max_power") - public Double maxPower; - public Boolean calibrated; - - public ArrayList relays; - public ArrayList rollers; - public ArrayList dimmers; - public ArrayList lights; - public ArrayList emeters; - public ArrayList inputs; // ix3 - public ArrayList thermostats; // TRV - - public Double voltage; // AC voltage for Shelly 2.5 - @SerializedName("supply_voltage") - public Long supplyVoltage; // Shelly 1PM/1L: 0=110V, 1=220V - - @SerializedName("temperature_units") - public String temperatureUnits = "C"; // Either'C'or'F' - - @SerializedName("led_status_disable") - public Boolean ledStatusDisable; // PlugS only Disable LED indication for network - // status - @SerializedName("led_power_disable") - public Boolean ledPowerDisable; // PlugS only Disable LED indication for network - // status - @SerializedName("light_sensor") - public String lightSensor; // Sense: sensor type - @SerializedName("rain_sensor") - public Boolean rainSensor; // Flood: true=in rain mode - - // FW 1.5.7: Door Window - @SerializedName("dark_treshold") - public Integer darkTreshold; // Illumination definition for "dark" in lux - @SerializedName("twilight_treshold") - public Integer twiLightTreshold; // Illumination definition for "twilight" in lux - @SerializedName("dark_url") - public String darkUrl; // URL to report to when luminance <= dark_threshold - @SerializedName("twilight_url") - public String twiLightUrl; // URL reports when luminance > dark_threshold AND luminance <= - @SerializedName("close_url") - public String closeUrl; // URL reports when DW contact is closed FW 1.6.5+ - @SerializedName("vibration_url") - public String vibrationUrl; // URL reports when DW detects vibration FW 1.6.5+ - - // Gas FW 1.7 - @SerializedName("set_volume") - public Integer volume; // Speaker volume for alarm - @SerializedName("alarm_off_url") - public String alarmOffUrl; // URL reports when alarm went off - @SerializedName("alarm_mild_url") - public String alarmMidUrl; // URL reports middle alarm - @SerializedName("alarm_heavy_url") - public String alarmHeavyfUrl; // URL reports heavy alarm - - // Roller with FW 1.9.2+ - @SerializedName("favorites_enabled") - public Boolean favoritesEnabled = false; - public ArrayList favorites; - - // Motion - public ShellyMotionSettings motion; - @SerializedName("tamper_sensitivity") - public Integer tamperSensitivity; - @SerializedName("dark_threshold") - public Integer darkThreshold; - @SerializedName("twilight_threshold") - public Integer twilightThreshold; - - @SerializedName("sleep_time") // Shelly Motion - public Integer sleepTime; - } - - public static class ShellySettingsAttributes { - @SerializedName("device_type") - public String deviceType; // Device model identifier - @SerializedName("device_mac") - public String deviceMac; // MAC address of the device in hexadecimal - @SerializedName("wifi_ap") - public String wifiAp; // WiFi access poInteger configuration, see /settings/ap for details - @SerializedName("wifi_sta") - public String wifiSta; // WiFi client configuration. See /settings/sta for details - public String login; // credentials used for HTTP Basic authentication for the REST interface. If - // enabled is true clients must include an Authorization: Basic ... HTTP header with valid - // credentials when performing TP requests. - public String name; // unique name of the device. - public String fw; // current FW version - } - - public static class ShellyActionsStats { - public Integer skipped; - } - - public static class ShellySettingsStatus { - public String name; // FW 1.8: Symbolic Device name is configurable - - @SerializedName("wifi_sta") - public ShellySettingsWiFiNetwork wifiSta; // WiFi client configuration. See /settings/sta for details - public ShellyStatusCloud cloud; - public ShellyStatusMqtt mqtt; - - public String time; - public Integer serial = -1; - @SerializedName("has_update") - public Boolean hasUpdate; - public String mac; - public Boolean discoverable; // FW 1.6+ - @SerializedName("cfg_changed_cnt") - public Integer cfgChangedCount; // FW 1.8 - @SerializedName("actions_stats") - public ShellyActionsStats astats; - - public ArrayList relays; - public Double voltage; // Shelly 2.5 - - public ArrayList rollers; - public Integer input; // RGBW2 has no JSON array - public ArrayList inputs; - public ArrayList lights; - public ArrayList dimmers; - public ArrayList meters; - public ArrayList emeters; - - // Internal device temp - public ShellyStatusSensor.ShellySensorTmp tmp; // Shelly 1PM - public Double temperature = SHELLY_API_INVTEMP; // Shelly 2.5 - public Boolean overtemperature; - - // Shelly Dimmer only - public Boolean loaderror; - public Boolean overload; - - // Shelly TRV - public Boolean calibrated; - public ArrayList thermostats; - - public ShellySettingsUpdate update; - @SerializedName("ram_total") - public Long ramTotal; - @SerializedName("ram_free") - public Long ramFree; - @SerializedName("fs_size") - public Long fsSize; - @SerializedName("fs_free") - public Long fsFree; - public Long uptime; - - @SerializedName("sleep_time") // Shelly Motion - public Integer sleepTime; - - public String json; - } - - public static class ShellySettingsInput { - @SerializedName("btn_type") - public String btnType; - } - - public static class ShellyControlRelay { - // https://shelly-api-docs.shelly.cloud/#shelly1-1pm-settings-relay-0 - @SerializedName("is_valid") - public Boolean isValid; - @SerializedName("has_timer") - public Boolean hasTimer; // Whether a timer is currently armed for this channel - @SerializedName("timer_remaining") - public Integer timerRemaining; // FW 1.6+ - public Boolean overpower; // Shelly1PM only if maximum allowed power was exceeded - - public String turn; // Accepted values are on and off. This will turn ON/OFF the respective output - // channel when request is sent . - public Integer timer; // A one-shot flip-back timer in seconds. - } - - public static class ShellyShortStatusRelay { - public String name; // FW 1.8+: Channel could now have a logical name - @SerializedName("is_valid") - public Boolean isValid; - public Boolean ison; // Whether output channel is on or off - @SerializedName("has_timer") - public Boolean hasTimer; // Whether a timer is currently armed for this channel - @SerializedName("timer_remaining") - public Integer timerRemaining; - public Boolean overpower; // Shelly1PM only if maximum allowed power was exceeded - public Double temperature; // Internal device temperature - public Boolean overtemperature; // Device over heated - } - - public static class ShellyShortLightStatus { - public Boolean ison; // Whether output channel is on or off - public String mode; // color or white - valid only for Bulb and RGBW2 even Dimmer returns it also - public Integer brightness; // brightness: 0.100% - - @SerializedName("has_timer") - public Boolean hasTimer; - } - - public static class ShellyStatusRelay { - public String name; // FW 1.8: Symbolic channel name is configurable - - @SerializedName("wifi_sta") - public ShellySettingsWiFiNetwork wifiSta; // WiFi status - public ShellySettingsCoiot coiot; // Firmware 1.6+ - public Integer serial; - public String mac; // MAC - public ArrayList relays; // relay status - public ArrayList meters; // current meter value - public ArrayList inputs; // Firmware 1.5.6+ - - @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 Double temperature; // device temp acc. on the selected temp unit - public ShellyStatusSensor.ShellySensorTmp tmp; - } - - public static class ShellyStatusDimmer { - @SerializedName("wifi_sta") - public ShellySettingsWiFiNetwork wifiSta; // WiFi status - public ArrayList lights; // relay status - public ArrayList meters; // current meter value - - public ShellyStatusSensor.ShellySensorTmp tmp; - public Boolean overtemperature; - - public Boolean loaderror; - public Boolean overload; - } - - public static class ShellyControlRoller { - public String name; // FW 1.8: Symbolic name is configurable - - @SerializedName("roller_pos") - public Integer rollerPos; // number Desired position in percent - public Integer duration; // If specified, the motor will move for this period in seconds. If missing, the - // value of maxtime in /settings/roller/N will be used. - public String state; // One of stop, open, close - public Double power; // Current power consumption in Watts - @SerializedName("is_valid") - public Boolean isValid; // If the power meter functions properly - @SerializedName("safety_switch") - public Boolean safetySwitch; // Whether the safety input is currently triggered - public Boolean overtemperature; - @SerializedName("stop_reason") - public String stopReason; // Last cause for stopping: normal, safety_switch, obstacle - @SerializedName("last_direction") - public String lastDirection; // Last direction of motion, open or close - public Boolean calibrating; - public Boolean positioning; // true when calibration was performed - @SerializedName("current_pos") - public Integer currentPos; // current position 0..100, 100=open - } - - public static class ShellyOtaCheckResult { - public String status; - } - - public static class ShellyApRoaming { - public Boolean enabled; - public Integer threshold; - } - - public static class ShellySensorSleepMode { - public Integer period; - public String unit; - } - - // Shelly TRV - public class ShellyThermnostat { - public class ShellyThermTargetTemp { - public Boolean enabled; - public Double value; - public String unit; - } - - public class ShellyThermTemp { - public Double value; - public String units; - @SerializedName("is_valid") - public Boolean isValid; - } - - public Double pos; - @SerializedName("target_t") - public ShellyThermTargetTemp targetTemp; - public Boolean schedule; - @SerializedName("schedule_profile") - public Integer profile; - @SerializedName("schedule_profile_names") - public String[] profileNames; - public ShellyThermTemp tmp; - @SerializedName("boost_minutes") - public Integer boostMinutes; - } - - public static class ShellyStatusSensor { - // https://shelly-api-docs.shelly.cloud/#h-amp-t-settings - public static class ShellySensorTmp { - public Double value; // Temperature in configured unites - public String units; // 'C' or 'F' - public Double tC; // temperature in deg C - public Double tF; // temperature in deg F - @SerializedName("is_valid") - public Boolean isValid; // whether the internal sensor is operating properly - } - - public static class ShellySensorHum { - public Double value; // relative humidity in % - } - - public static class ShellySensorBat { - public Double value; // estimated remaining battery capacity in % - public Double voltage; // battery voltage - }; - - // Door/Window sensor - public static class ShellySensorState { - @SerializedName("is_valid") - public Boolean isValid; // whether the internal sensor is operating properly - public String state; // Shelly Door/Window - - // Shelly Motion - public Boolean motion; - public Boolean vibration; - @SerializedName("timestamp") - public Long motionTimestamp; - @SerializedName("active") - public Boolean motionActive; - } - - public static class ShellySensorLux { - @SerializedName("is_valid") - public Boolean isValid; // whether the internal sensor is operating properly - public Double value; - - public String illumination; - } - - public static class ShellySensorAccel { - public Integer tilt; // Tilt in ° - public Integer vibration; // Whether vibration is detected - } - - public static class ShellyMotionSettings { - public Integer sensitivity; - @SerializedName("blind_time_minutes") - public Integer blindTimeMinutes; - @SerializedName("pulse_count") - public Integer pulseCount; - @SerializedName("operating_mode") - public Integer operatingMode; - public Boolean enabled; - } - - public static class ShellyExtTemperature { - public static class ShellyShortTemp { - public Double tC; // temperature in deg C - public Double tF; // temperature in deg F - } - - // Shelly 1/1PM have up to 3 sensors - // for whatever reasons it's not an array, but 3 independent elements - @SerializedName("0") - public ShellyShortTemp sensor1; - @SerializedName("1") - public ShellyShortTemp sensor2; - @SerializedName("2") - public ShellyShortTemp sensor3; - } - - public static class ShellyExtHumidity { - public static class ShellyShortHum { - public Double hum; // Humidity reading of sensor 0, percent - } - - // Shelly 1/1PM have up to 3 sensors - // for whatever reasons it's not an array, but 3 independent elements - @SerializedName("0") - public ShellyShortHum sensor1; - } - - public static class ShellyADC { - public Double voltage; - } - - public ShellySensorTmp tmp; - public ShellySensorHum hum; - public ShellySensorLux lux; - public ShellySensorAccel accel; - public ShellySensorBat bat; - @SerializedName("sensor") - public ShellySensorState sensor; - public Boolean smoke; // SHelly Smoke - public Boolean flood; // Shelly Flood: true = flood condition detected - @SerializedName("rain_sensor") - public Boolean rainSensor; // Shelly Flood: true=in rain mode - - public Boolean motion; // Shelly Sense: true=motion detected - public Boolean charger; // Shelly Sense, TRV: true=charger connected - - @SerializedName("act_reasons") - public List actReasons; // HT/Smoke/Flood: list of reasons which woke up the device - - @SerializedName("sensor_error") - public String sensorError; // 1.5.7: Only displayed in case of error - - // FW 1.7: Shelly Gas - @SerializedName("gas_sensor") - public ShellyStatusGasSensor gasSensor; - @SerializedName("concentration") - public ShellyStatusGasConcentration concentration; - public ArrayList valves; - - // FW 1.7 Button - @SerializedName("connect_retries") - public Integer connectRetries; - public ArrayList inputs; // Firmware 1.5.6+ - - // Shelly UNI FW 1.9+ - public ArrayList adcs; - - // Shelly TRV - public Boolean calibrated; - public ArrayList thermostats; - } - - public static class ShellySettingsSmoke { - @SerializedName("temperature_units") - public String temperatureUnits; // Either 'C' or 'F' - @SerializedName("temperature_threshold") - public Integer temperatureThreshold; // Temperature delta (in configured degree units) which triggers an update - @SerializedName("sleep_mode_period") - public Integer sleepModePeriod; // Periodic update period in hours, between 1 and 24 - } - - // Shelly Gas - // "gas_sensor":{"sensor_state":"normal","self_test_state":"not_completed","alarm_state":"none"}, - // "concentration":{"ppm":0,"is_valid":true}, - public static class ShellyStatusGasSensor { - @SerializedName("sensor_state") - public String sensorState; - @SerializedName("self_test_state") - public String selfTestState; - @SerializedName("alarm_state") - public String alarmState; - } - - public static class ShellyStatusGasConcentration { - public Integer ppm; - @SerializedName("is_valid") - public Boolean isValid; - } - - public static class ShellyStatusValve { - public String state; // closed/opened/not_connected/failure/closing/opening/checking - } - - public static class ShellySettingsLight { - public Integer red; // red brightness, 0..255, applies in mode="color" - public Integer green; // green brightness, 0..255, applies in mode="color" - public Integer blue; // blue brightness, 0..255, applies in mode="color" - public Integer white; // white brightness, 0..255, applies in mode="color" - public Integer gain; // gain for all channels, 0..100, applies in mode="color" - public Integer temp; // color temperature in K, 3000..6500, applies in mode="white" - public Integer brightness; // brightness, 0..100, applies in mode="white" - public Integer effect; // Currently applied effect, description: 0: Off, 1: Meteor Shower, 2: Gradual - // Change, 3: Breath, - // 4: Flash, 5: On/Off Gradual, 6: Red/Green Change - @SerializedName("default_state") - public String defaultState; // one of on, off or last - @SerializedName("auto_on") - public Double autoOn; // see above - @SerializedName("auto_off") - public Double autoOff; // see above - - public Integer dcpower; // RGW2:Set to true for 24 V power supply, false for 12 V - - // Shelly Dimmer - public String mode; - public Boolean ison; - } - - public static class ShellySettingsNightMode { // FW1.5.7+ - public Integer enabled; - @SerializedName("start_time") - public String startTime; - @SerializedName("end_time") - public String endTime; - public Integer brightness; - } - - public static class ShellyStatusLightChannel { - public Boolean ison; - public Double power; - public Boolean overpower; - @SerializedName("has_timer") - public Boolean hasTimer; - @SerializedName("timer_started") - public Integer timerStarted; - @SerializedName("timer_duration") - public Integer timerDuration; - @SerializedName("timer_remaining") - public Integer timerRemaining; - - public Integer red; // red brightness, 0..255, applies in mode="color" - public Integer green; // green brightness, 0..255, applies in mode="color" - public Integer blue; // blue brightness, 0..255, applies in mode="color" - public Integer white; // white brightness, 0..255, applies in mode="color" - public Integer gain; // gain for all channels, 0..100, applies in mode="color" - public Integer temp; // color temperature in K, 3000..6500, applies in mode="white" - public Integer brightness; // brightness, 0..100, applies in mode="white" - public Integer effect; // Currently applied effect, description: 0: Off, 1: Meteor Shower, 2: Gradual - // Change, 3: Breath, - } - - public static class ShellyStatusLight { - public Boolean ison; // Whether output channel is on or off - public Integer input; - - public ArrayList lights; - public ArrayList meters; - } - - public static class ShellySenseKeyCode { - String id; // ID of the stored IR code into Shelly Sense. - String name; // Short description or name of the stored IR code. - } - - public static class ShellySendKeyList { - @SerializedName("key_codes") - public ArrayList keyCodes; - } - - /** - * Shelly Dimmer returns light[]. However, the structure doesn't match the lights[] of a Bulb/RGBW2. - * The tag lights[] will be replaced with dimmers[] so this could be mapped to a different Gson structure. - * The function requires that it's only called when the device is a dimmer - on get settings and get status - * - * @param json Input Json as received by the API - * @return Modified Json - */ - public static String fixDimmerJson(String json) { - return !json.contains("\"lights\":[") ? json - : json.replaceFirst(java.util.regex.Pattern.quote("\"lights\":["), "\"dimmers\":["); - } - - /** - * Convert Shelly Button events into OH button states - * - * @param eventType S/SS/SSS or L - * @return OH button states - */ - public static String mapButtonEvent(String eventType) { - // decode different codings - // 0..2: CoAP - // S/SS/SSS/L: CoAP for Button and xi3 - // shortpush/double_shortpush/triple_shortpush/longpush: REST - switch (eventType) { - case "0": - return CommonTriggerEvents.RELEASED; - case "1": - case SHELLY_BTNEVENT_1SHORTPUSH: - case SHELLY_EVENT_SHORTPUSH: - return CommonTriggerEvents.SHORT_PRESSED; - case SHELLY_BTNEVENT_2SHORTPUSH: - case SHELLY_EVENT_DOUBLE_SHORTPUSH: - return CommonTriggerEvents.DOUBLE_PRESSED; - case SHELLY_BTNEVENT_3SHORTPUSH: - case SHELLY_EVENT_TRIPLE_SHORTPUSH: - return "TRIPLE_PRESSED"; - case "2": - case SHELLY_BTNEVENT_LONGPUSH: - case SHELLY_EVENT_LONGPUSH: - return CommonTriggerEvents.LONG_PRESSED; - case SHELLY_BTNEVENT_SHORTLONGPUSH: - case SHELLY_EVENT_SHORT_LONGTPUSH: - return "SHORT_LONG_PRESSED"; - case SHELLY_BTNEVENT_LONGSHORTPUSH: - case SHELLY_EVENT_LONG_SHORTPUSH: - return "LONG_SHORT_PRESSED"; - default: - return ""; - } - } -} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiResult.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiResult.java index d900ba557f..1f26e50655 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiResult.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyApiResult.java @@ -13,7 +13,7 @@ package org.openhab.binding.shelly.internal.api; import static org.eclipse.jetty.http.HttpStatus.*; -import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*; +import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -33,6 +33,7 @@ public class ShellyApiResult { public String response = ""; public int httpCode = -1; public String httpReason = ""; + public String authResponse = ""; public ShellyApiResult() { } diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java index 774fc88ed9..2d576a27ec 100644 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyDeviceProfile.java @@ -13,7 +13,8 @@ package org.openhab.binding.shelly.internal.api; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; -import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*; +import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*; +import static org.openhab.binding.shelly.internal.discovery.ShellyThingCreator.*; import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import java.util.HashMap; @@ -23,12 +24,12 @@ import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer; -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsGlobal; -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsInput; -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay; -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRgbwLight; -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDimmer; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsGlobal; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsInput; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRelay; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsRgbwLight; +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus; import org.openhab.binding.shelly.internal.util.ShellyVersionDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,6 +64,7 @@ public class ShellyDeviceProfile { public boolean discoverable = true; public boolean auth = false; public boolean alwaysOn = true; + public boolean isGen2 = false; public String hwRev = ""; public String hwBatchId = ""; @@ -93,7 +95,7 @@ public class ShellyDeviceProfile { public boolean isHT = false; // true for H&T public boolean isDW = false; // true for Door Window sensor public boolean isButton = false; // true for a Shelly Button 1 - public boolean isIX3 = false; // true for a Shelly IX + public boolean isIX = false; // true for a Shelly IX public boolean isTRV = false; // true for a Shelly TRV public int minTemp = 0; // Bulb/Duo: Min Light Temp @@ -202,11 +204,12 @@ public class ShellyDeviceProfile { boolean isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR); boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_STR); boolean isUNI = thingType.equals(THING_TYPE_SHELLYUNI_STR); - isHT = thingType.equals(THING_TYPE_SHELLYHT_STR); + isHT = thingType.equals(THING_TYPE_SHELLYHT_STR) || thingType.equals(THING_TYPE_SHELLYPLUSHT_STR); isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_STR); isMotion = thingType.startsWith(THING_TYPE_SHELLYMOTION_STR); isSense = thingType.equals(THING_TYPE_SHELLYSENSE_STR); - isIX3 = thingType.equals(THING_TYPE_SHELLYIX3_STR); + isIX = thingType.equals(THING_TYPE_SHELLYIX3_STR) || thingType.equals(THING_TYPE_SHELLYPLUSI4_STR) + || thingType.equals(THING_TYPE_SHELLYPLUSI4DC_STR); isButton = thingType.equals(THING_TYPE_SHELLYBUTTON1_STR) || thingType.equals(THING_TYPE_SHELLYBUTTON2_STR); isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isUNI || isMotion || isSense || isTRV; hasBattery = isHT || isFlood || isDW || isSmoke || isButton || isMotion || isTRV; @@ -261,7 +264,7 @@ public class ShellyDeviceProfile { int idx = i + 1; // group names are 1-based if (isRGBW2) { return CHANNEL_GROUP_LIGHT_CONTROL; - } else if (isIX3) { + } else if (isIX) { return CHANNEL_GROUP_STATUS + idx; } else if (isButton) { return CHANNEL_GROUP_STATUS; @@ -275,7 +278,7 @@ public class ShellyDeviceProfile { public String getInputSuffix(int i) { int idx = i + 1; // channel names are 1-based - if (isRGBW2 || isIX3) { + if (isRGBW2 || isIX) { return ""; // RGBW2 has only 1 channel } else if (isRoller || isDimmer) { // Roller has 2 relays, but it will be mapped to 1 roller with 2 inputs @@ -294,7 +297,7 @@ public class ShellyDeviceProfile { String btnType = ""; if (isButton) { return true; - } else if (isIX3 && settings.inputs != null && idx < settings.inputs.size()) { + } else if (isIX && settings.inputs != null && idx < settings.inputs.size()) { ShellySettingsInput input = settings.inputs.get(idx); btnType = getString(input.btnType); } else if (isDimmer) { diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java deleted file mode 100644 index 38cb916d06..0000000000 --- a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpApi.java +++ /dev/null @@ -1,717 +0,0 @@ -/** - * 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 static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; -import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*; -import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; - -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.HttpStatus; -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.ShellySendKeyList; -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySenseKeyCode; -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDevice; -import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsLight; -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.ShellySettingsUpdate; -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; -import org.openhab.core.library.unit.ImperialUnits; -import org.openhab.core.library.unit.SIUnits; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; - -/** - * {@link ShellyHttpApi} wraps the Shelly REST API and provides various low level function to access the device api (not - * cloud api). - * - * @author Markus Michels - Initial contribution - */ -@NonNullByDefault -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"; - - private final Logger logger = LoggerFactory.getLogger(ShellyHttpApi.class); - private final HttpClient httpClient; - private ShellyThingConfiguration config = new ShellyThingConfiguration(); - private String thingName; - private final Gson gson = new Gson(); - private int timeoutErrors = 0; - private int timeoutsRecovered = 0; - - private ShellyDeviceProfile profile = new ShellyDeviceProfile(); - - public ShellyHttpApi(String thingName, ShellyThingConfiguration config, HttpClient httpClient) { - this.httpClient = httpClient; - this.thingName = thingName; - setConfig(thingName, config); - profile.initFromThingType(thingName); - } - - @Override - public void setConfig(String thingName, ShellyThingConfiguration config) { - this.thingName = thingName; - this.config = config; - } - - @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); - } - - /** - * Initialize the device profile - * - * @param thingType Type of DEVICE as returned from the thing properties (based on discovery) - * @return Initialized ShellyDeviceProfile - * @throws ShellyApiException - */ - @Override - public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException { - String json = request(SHELLY_URL_SETTINGS); - if (json.contains("\"type\":\"SHDM-")) { - logger.trace("{}: Detected a Shelly Dimmer: fix Json (replace lights[] tag with dimmers[]", thingName); - json = fixDimmerJson(json); - } - - // Map settings to device profile for Light and Sense - profile.initialize(thingType, json); - - // 2nd level initialization - profile.thingName = profile.hostname; - if (profile.isLight && (profile.numMeters == 0)) { - logger.debug("{}: Get number of meters from light status", thingName); - ShellyStatusLight status = getLightStatus(); - profile.numMeters = status.meters != null ? status.meters.size() : 0; - } - if (profile.isSense) { - profile.irCodes = getIRCodeList(); - logger.debug("{}: Sense stored key list loaded, {} entries.", thingName, profile.irCodes.size()); - } - - return profile; - } - - @Override - public boolean isInitialized() { - return profile.initialized; - } - - /** - * Get generic device settings/status. Json returned from API will be mapped to a Gson object - * - * @return Device settings/status as ShellySettingsStatus object - * @throws ShellyApiException - */ - @Override - public ShellySettingsStatus getStatus() throws ShellyApiException { - String json = ""; - try { - json = request(SHELLY_URL_STATUS); - // Dimmer2 returns invalid json type for loaderror :-( - json = getString(json.replace("\"loaderror\":0,", "\"loaderror\":false,")); - json = getString(json.replace("\"loaderror\":1,", "\"loaderror\":true,")); - ShellySettingsStatus status = fromJson(gson, json, ShellySettingsStatus.class); - status.json = json; - return status; - } catch (JsonSyntaxException e) { - throw new ShellyApiException("Unable to parse JSON: " + json, e); - } - } - - @Override - public ShellyStatusRelay getRelayStatus(Integer relayIndex) throws ShellyApiException { - return callApi(SHELLY_URL_STATUS_RELEAY + "/" + relayIndex.toString(), ShellyStatusRelay.class); - } - - @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); - } - - @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); - } - - @Override - public ShellyControlRoller getRollerStatus(int idx) throws ShellyApiException { - String uri = SHELLY_URL_CONTROL_ROLLER + "/" + idx + "/pos"; - return callApi(uri, ShellyControlRoller.class); - } - - @Override - public void setRollerTurn(int idx, String turnMode) throws ShellyApiException { - request(SHELLY_URL_CONTROL_ROLLER + "/" + idx + "?go=" + turnMode); - } - - @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(int idx, int timer) throws ShellyApiException { - request(SHELLY_URL_CONTROL_ROLLER + "/" + idx + "?timer=" + timer); - } - - @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) { - // complete reported data, map C to F or vice versa: C=(F - 32) * 0.5556; - status.tmp.tC = status.tmp.units.equals(SHELLY_TEMP_CELSIUS) ? status.tmp.value - : ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(getDouble(status.tmp.value)) - .doubleValue(); - double f = (double) SIUnits.CELSIUS.getConverterTo(ImperialUnits.FAHRENHEIT) - .convert(getDouble(status.tmp.value)); - status.tmp.tF = status.tmp.units.equals(SHELLY_TEMP_FAHRENHEIT) ? status.tmp.value : f; - } - if ((status.charger == null) && (profile.settings.externalPower != null)) { - // SHelly H&T uses external_power, Sense uses charger - status.charger = profile.settings.externalPower != 0; - } - return status; - } - - @Override - public void setTimer(int index, String timerName, int value) throws ShellyApiException { - String type = SHELLY_CLASS_RELAY; - if (profile.isRoller) { - type = SHELLY_CLASS_ROLLER; - } else if (profile.isLight) { - type = SHELLY_CLASS_LIGHT; - } - String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "=" + value; - request(uri); - } - - @Override - public void setSleepTime(int value) throws ShellyApiException { - request(SHELLY_URL_SETTINGS + "?sleep_time=" + value); - } - - @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) { - uri = uri + "&target_t=" + getDouble(profile.settings.thermostats.get(0).targetTemp.value); - } - request(uri); // percentage to open the valve - } - - @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 - } - - @Override - public void setValveBoostTime(int valveId, int value) throws ShellyApiException { - request("/settings/thermostat/" + valveId + "?boost_minutes=" + value); - } - - @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)); - } - - public ShellySettingsLight getLightSettings() throws ShellyApiException { - return callApi(SHELLY_URL_SETTINGS_LIGHT, ShellySettingsLight.class); - } - - @Override - public ShellyStatusLight getLightStatus() throws ShellyApiException { - return callApi(SHELLY_URL_STATUS, ShellyStatusLight.class); - } - - public void setLightSetting(String parm, String value) throws ShellyApiException { - 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); - } catch (ShellyApiException e) { - if (e.getApiResult().isNotFound()) { - return ""; // only supported by FW 1.10+ - } - throw e; - } - } - - @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); - } - - public ShellySettingsUpdate firmwareUpdate(String uri) throws ShellyApiException { - return callApi("/ota?" + uri, ShellySettingsUpdate.class); - } - - @Override - public String setCloud(boolean enabled) throws ShellyApiException { - return callApi("/settings/cloud/?enabled=" + (enabled ? "1" : "0"), String.class); - } - - /** - * Change between White and Color Mode - * - * @param mode - * @throws ShellyApiException - */ - @Override - public void setLightMode(String mode) throws ShellyApiException { - if (!mode.isEmpty() && !profile.mode.equals(mode)) { - setLightSetting(SHELLY_API_MODE, mode); - profile.mode = mode; - profile.inColor = profile.isLight && profile.mode.equalsIgnoreCase(SHELLY_MODE_COLOR); - } - } - - /** - * Set a single light parameter - * - * @param lightIndex Index of the light, usually 0 for Bulb and 0..3 for RGBW2. - * @param parm Name of the parameter (see API spec) - * @param value The value - * @throws ShellyApiException - */ - @Override - public void setLightParm(int lightIndex, String parm, String value) throws ShellyApiException { - // Bulb, RGW2: //?parm?value - // Dimmer: /light/?parm=value - request(getControlUriPrefix(lightIndex) + "?" + parm + "=" + value); - } - - @Override - public void setLightParms(int lightIndex, Map parameters) throws ShellyApiException { - String url = getControlUriPrefix(lightIndex) + "?"; - int i = 0; - for (String key : parameters.keySet()) { - if (i > 0) { - url = url + "&"; - } - url = url + key + "=" + parameters.get(key); - i++; - } - request(url); - } - - /** - * Retrieve the IR Code list from the Shelly Sense device. The list could be customized by the user. It defines the - * symbolic key code, which gets - * map into a PRONTO code - * - * @return Map of key codes - * @throws ShellyApiException - */ - public Map getIRCodeList() throws ShellyApiException { - String result = request(SHELLY_URL_LIST_IR); - // take pragmatic approach to make the returned JSon into named arrays for Gson parsing - String keyList = substringAfter(result, "["); - keyList = substringBeforeLast(keyList, "]"); - keyList = keyList.replaceAll(java.util.regex.Pattern.quote("\",\""), "\", \"name\": \""); - keyList = keyList.replaceAll(java.util.regex.Pattern.quote("["), "{ \"id\":"); - keyList = keyList.replaceAll(java.util.regex.Pattern.quote("]"), "} "); - String json = "{\"key_codes\" : [" + keyList + "] }"; - ShellySendKeyList codes = fromJson(gson, json, ShellySendKeyList.class); - Map list = new HashMap<>(); - for (ShellySenseKeyCode key : codes.keyCodes) { - if (key != null) { - list.put(key.id, key.name); - } - } - return list; - } - - /** - * Sends a IR key code to the Shelly Sense. - * - * @param keyCode A keyCoud could be a symbolic name (as defined in the key map on the device) or a PRONTO Code in - * plain or hex64 format - * - * @throws ShellyApiException - * @throws IllegalArgumentException - */ - @Override - public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException { - String type = ""; - if (profile.irCodes.containsKey(keyCode)) { - type = SHELLY_IR_CODET_STORED; - } else if ((keyCode.length() > 4) && keyCode.contains(" ")) { - type = SHELLY_IR_CODET_PRONTO; - } else { - type = SHELLY_IR_CODET_PRONTO_HEX; - } - String url = SHELLY_URL_SEND_IR + "?type=" + type; - if (type.equals(SHELLY_IR_CODET_STORED)) { - url = url + "&" + "id=" + keyCode; - } else if (type.equals(SHELLY_IR_CODET_PRONTO)) { - String code = Base64.getEncoder().encodeToString(keyCode.getBytes(StandardCharsets.UTF_8)); - url = url + "&" + SHELLY_IR_CODET_PRONTO + "=" + code; - } else if (type.equals(SHELLY_IR_CODET_PRONTO_HEX)) { - url = url + "&" + SHELLY_IR_CODET_PRONTO_HEX + "=" + keyCode; - } - request(url); - } - - public void setSenseSetting(String setting, String value) throws ShellyApiException { - request(SHELLY_URL_SETTINGS + "?" + setting + "=" + value); - } - - /** - * Set event callback URLs. Depending on the device different event types are supported. In fact all of them will be - * redirected to the binding's servlet and act as a trigger to schedule a status update - * - * @param ShellyApiException - * @throws ShellyApiException - */ - @Override - public void setActionURLs() throws ShellyApiException { - setRelayEvents(); - setDimmerEvents(); - setSensorEventUrls(); - } - - private void setRelayEvents() throws ShellyApiException { - if (profile.settings.relays != null) { - int num = profile.isRoller ? profile.numRollers : profile.numRelays; - for (int i = 0; i < num; i++) { - setEventUrls(i); - } - } - } - - private void setDimmerEvents() throws ShellyApiException { - if (profile.settings.dimmers != null) { - for (int i = 0; i < profile.settings.dimmers.size(); i++) { - setEventUrls(i); - } - } else if (profile.isLight) { - setEventUrls(0); - } - } - - /** - * Set sensor Action URLs - * - * @throws ShellyApiException - */ - private void setSensorEventUrls() throws ShellyApiException, ShellyApiException { - if (profile.isSensor) { - logger.debug("{}: Set Sensor Reporting URL", thingName); - setEventUrl(config.eventsSensorReport, SHELLY_EVENT_SENSORREPORT, SHELLY_EVENT_DARK, SHELLY_EVENT_TWILIGHT, - SHELLY_EVENT_FLOOD_DETECTED, SHELLY_EVENT_FLOOD_GONE, SHELLY_EVENT_OPEN, SHELLY_EVENT_CLOSE, - SHELLY_EVENT_VIBRATION, SHELLY_EVENT_ALARM_MILD, SHELLY_EVENT_ALARM_HEAVY, SHELLY_EVENT_ALARM_OFF, - SHELLY_EVENT_TEMP_OVER, SHELLY_EVENT_TEMP_UNDER); - } - } - - /** - * Set/delete Relay/Roller/Dimmer Action URLs - * - * @param index Device Index (0-based) - * @throws ShellyApiException - */ - private void setEventUrls(Integer index) throws ShellyApiException { - if (profile.isRoller) { - setEventUrl(EVENT_TYPE_ROLLER, 0, config.eventsRoller, SHELLY_EVENT_ROLLER_OPEN, SHELLY_EVENT_ROLLER_CLOSE, - SHELLY_EVENT_ROLLER_STOP); - } else if (profile.isDimmer) { - // 2 set of URLs - setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsButton, SHELLY_EVENT_BTN1_ON, SHELLY_EVENT_BTN1_OFF, - SHELLY_EVENT_BTN2_ON, SHELLY_EVENT_BTN2_OFF); - setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsPush, SHELLY_EVENT_SHORTPUSH1, SHELLY_EVENT_LONGPUSH1, - SHELLY_EVENT_SHORTPUSH2, SHELLY_EVENT_LONGPUSH2); - - // Relay output - setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF); - } else if (profile.hasRelays) { - // Standard relays: btn_xxx, out_xxx, short/longpush URLs - setEventUrl(EVENT_TYPE_RELAY, index, config.eventsButton, SHELLY_EVENT_BTN_ON, SHELLY_EVENT_BTN_OFF); - setEventUrl(EVENT_TYPE_RELAY, index, config.eventsPush, SHELLY_EVENT_SHORTPUSH, SHELLY_EVENT_LONGPUSH); - setEventUrl(EVENT_TYPE_RELAY, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF); - } else if (profile.isLight) { - // Duo, Bulb - setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF); - } - } - - private void setEventUrl(boolean enabled, String... eventTypes) throws ShellyApiException { - if (config.localIp.isEmpty()) { - throw new ShellyApiException(thingName + ": Local IP address was not detected, can't build Callback URL"); - } - for (String eventType : eventTypes) { - if (profile.containsEventUrl(eventType)) { - // H&T adds the type=xx to report_url itself, so we need to ommit here - String eclass = profile.isSensor ? EVENT_TYPE_SENSORDATA : eventType; - String urlParm = eventType.contains("temp") || profile.isHT ? "" : "?type=" + eventType; - String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/" - + profile.thingName + "/" + eclass + urlParm; - String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL; - String testUrl = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\""; - if (!enabled && !profile.settingsJson.contains(testUrl)) { - // Don't set URL to null when the current one doesn't point to this OH - // Don't interfere with a 3rd party App - continue; - } - if (!profile.settingsJson.contains(testUrl)) { - // Current Action URL is != new URL - logger.debug("{}: Set new url for event type {}: {}", thingName, eventType, newUrl); - request(SHELLY_URL_SETTINGS + "?" + mkEventUrl(eventType) + "=" + urlEncode(newUrl)); - } - } - } - } - - private void setEventUrl(String deviceClass, Integer index, boolean enabled, String... eventTypes) - throws ShellyApiException { - for (String eventType : eventTypes) { - if (profile.containsEventUrl(eventType)) { - String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/" - + profile.thingName + "/" + deviceClass + "/" + index + "?type=" + eventType; - String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL; - String test = "\"" + mkEventUrl(eventType) + "\":\"" + callBackUrl + "\""; - if (!enabled && !profile.settingsJson.contains(test)) { - // Don't set URL to null when the current one doesn't point to this OH - // Don't interfere with a 3rd party App - continue; - } - test = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\""; - if (!profile.settingsJson.contains(test)) { - // Current Action URL is != new URL - logger.debug("{}: Set URL for type {} to {}", thingName, eventType, newUrl); - request(SHELLY_URL_SETTINGS + "/" + deviceClass + "/" + index + "?" + mkEventUrl(eventType) + "=" - + urlEncode(newUrl)); - } - } - } - } - - private static String mkEventUrl(String eventType) { - return eventType + SHELLY_EVENTURL_SUFFIX; - } - - /** - * Submit GET request and return response, check for invalid responses - * - * @param uri: URI (e.g. "/settings") - */ - public T callApi(String uri, Class classOfT) throws ShellyApiException { - String json = request(uri); - return fromJson(gson, json, classOfT); - } - - private String request(String uri) throws ShellyApiException { - ShellyApiResult apiResult = new ShellyApiResult(); - int retries = 3; - boolean timeout = false; - while (retries > 0) { - try { - apiResult = innerRequest(HttpMethod.GET, uri); - if (timeout) { - logger.debug("{}: API timeout #{}/{} recovered ({})", thingName, timeoutErrors, timeoutsRecovered, - apiResult.getUrl()); - timeoutsRecovered++; - } - return apiResult.response; // successful - } catch (ShellyApiException e) { - if ((!e.isTimeout() && !apiResult.isHttpServerError()) || profile.hasBattery || (retries == 0)) { - // Sensor in sleep mode or API exception for non-battery device or retry counter expired - throw e; // non-timeout exception - } - - timeout = true; - retries--; - timeoutErrors++; // count the retries - logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString()); - } - } - throw new ShellyApiException("API Timeout or inconsistent result"); // successful - } - - private ShellyApiResult innerRequest(HttpMethod method, String uri) throws ShellyApiException { - Request request = null; - String url = "http://" + config.deviceIp + uri; - ShellyApiResult apiResult = new ShellyApiResult(method.toString(), url); - - try { - request = httpClient.newRequest(url).method(method.toString()).timeout(SHELLY_API_TIMEOUT_MS, - TimeUnit.MILLISECONDS); - - if (!config.userId.isEmpty()) { - String value = config.userId + ":" + config.password; - request.header(HTTP_HEADER_AUTH, - HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(value.getBytes())); - } - request.header(HttpHeader.ACCEPT, CONTENT_TYPE_JSON); - logger.trace("{}: HTTP {} for {}", thingName, method, url); - - // Do request and get response - ContentResponse contentResponse = request.send(); - apiResult = new ShellyApiResult(contentResponse); - String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim(); - logger.trace("{}: HTTP Response {}: {}", thingName, contentResponse.getStatus(), response); - - // validate response, API errors are reported as Json - if (contentResponse.getStatus() != HttpStatus.OK_200) { - throw new ShellyApiException(apiResult); - } - if (response.isEmpty() || !response.startsWith("{") && !response.startsWith("[") && !url.contains("/debug/") - && !url.contains("/sta_cache_reset")) { - throw new ShellyApiException("Unexpected response: " + response); - } - } catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) { - ShellyApiException ex = new ShellyApiException(apiResult, e); - if (!ex.isTimeout()) { // will be handled by the caller - logger.trace("{}: API call returned exception", thingName, ex); - } - throw ex; - } - return apiResult; - } - - public String getControlUriPrefix(Integer id) { - String uri = ""; - if (profile.isLight || profile.isDimmer) { - if (profile.isDuo || profile.isDimmer) { - // Duo + Dimmer - uri = SHELLY_URL_CONTROL_LIGHT; - } else { - // Bulb + RGBW2 - uri = "/" + (profile.inColor ? SHELLY_MODE_COLOR : SHELLY_MODE_WHITE); - } - } else { - // Roller, Relay - uri = SHELLY_URL_CONTROL_RELEAY; - } - uri = uri + "/" + id; - return uri; - } - - @Override - public int getTimeoutErrors() { - return timeoutErrors; - } - - @Override - public int getTimeoutsRecovered() { - return timeoutsRecovered; - } -} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java new file mode 100644 index 0000000000..1bc9a8fb7f --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api/ShellyHttpClient.java @@ -0,0 +1,245 @@ +/** + * 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 static org.openhab.binding.shelly.internal.ShellyBindingConstants.SHELLY_API_TIMEOUT_MS; +import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*; +import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; +import org.openhab.binding.shelly.internal.handler.ShellyThingInterface; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +/** + * {@link ShellyHttpClient} implements basic HTTP access + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +public class ShellyHttpClient { + private final Logger logger = LoggerFactory.getLogger(ShellyHttpClient.class); + + 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"; + public static final String CONTENT_TYPE_FORM_URLENC = "application/x-www-form-urlencoded"; + + protected final HttpClient httpClient; + protected ShellyThingConfiguration config = new ShellyThingConfiguration(); + protected String thingName; + protected final Gson gson = new Gson(); + protected int timeoutErrors = 0; + protected int timeoutsRecovered = 0; + private ShellyDeviceProfile profile; + + public ShellyHttpClient(String thingName, ShellyThingInterface thing) { + this(thingName, thing.getThingConfig(), thing.getHttpClient()); + this.profile = thing.getProfile(); + profile.initFromThingType(thingName); + } + + public ShellyHttpClient(String thingName, ShellyThingConfiguration config, HttpClient httpClient) { + profile = new ShellyDeviceProfile(); + this.thingName = thingName; + setConfig(thingName, config); + this.httpClient = httpClient; + } + + public void initialize() throws ShellyApiException { + } + + public void setConfig(String thingName, ShellyThingConfiguration config) { + this.thingName = thingName; + this.config = config; + } + + /** + * Submit GET request and return response, check for invalid responses + * + * @param uri: URI (e.g. "/settings") + */ + public T callApi(String uri, Class classOfT) throws ShellyApiException { + String json = httpRequest(uri); + return fromJson(gson, json, classOfT); + } + + public T postApi(String uri, String data, Class classOfT) throws ShellyApiException { + String json = httpPost(uri, data); + return fromJson(gson, json, classOfT); + } + + protected String httpRequest(String uri) throws ShellyApiException { + ShellyApiResult apiResult = new ShellyApiResult(); + int retries = 3; + boolean timeout = false; + while (retries > 0) { + try { + apiResult = innerRequest(HttpMethod.GET, uri, ""); + if (timeout) { + logger.debug("{}: API timeout #{}/{} recovered ({})", thingName, timeoutErrors, timeoutsRecovered, + apiResult.getUrl()); + timeoutsRecovered++; + } + return apiResult.response; // successful + } catch (ShellyApiException e) { + if ((!e.isTimeout() && !apiResult.isHttpServerError()) && !apiResult.isNotFound() || profile.hasBattery + || (retries == 0)) { + // Sensor in sleep mode or API exception for non-battery device or retry counter expired + throw e; // non-timeout exception + } + + timeout = true; + retries--; + timeoutErrors++; // count the retries + logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString()); + } + } + throw new ShellyApiException("API Timeout or inconsistent result"); // successful + } + + public String httpPost(String uri, String data) throws ShellyApiException { + return innerRequest(HttpMethod.POST, uri, data).response; + } + + private ShellyApiResult innerRequest(HttpMethod method, String uri, String data) throws ShellyApiException { + Request request = null; + String url = "http://" + config.deviceIp + uri; + ShellyApiResult apiResult = new ShellyApiResult(method.toString(), url); + + try { + request = httpClient.newRequest(url).method(method.toString()).timeout(SHELLY_API_TIMEOUT_MS, + TimeUnit.MILLISECONDS); + + if (!config.password.isEmpty() && !getString(data).contains("\"auth\":{")) { + String value = config.userId + ":" + config.password; + request.header(HTTP_HEADER_AUTH, + HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(value.getBytes())); + } + fillPostData(request, data); + logger.trace("{}: HTTP {} for {} {}", thingName, method, url, data); + + // Do request and get response + ContentResponse contentResponse = request.send(); + apiResult = new ShellyApiResult(contentResponse); + apiResult.httpCode = contentResponse.getStatus(); + String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim(); + logger.trace("{}: HTTP Response {}: {}", thingName, contentResponse.getStatus(), response); + + HttpFields headers = contentResponse.getHeaders(); + String auth = headers.get(HttpHeader.WWW_AUTHENTICATE); + if (!getString(auth).isEmpty()) { + apiResult.authResponse = auth; + } + + // validate response, API errors are reported as Json + if (apiResult.httpCode != HttpStatus.OK_200) { + throw new ShellyApiException(apiResult); + } + + if (response.isEmpty() || !response.startsWith("{") && !response.startsWith("[") && !url.contains("/debug/") + && !url.contains("/sta_cache_reset")) { + throw new ShellyApiException("Unexpected response: " + response); + } + } catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) { + ShellyApiException ex = new ShellyApiException(apiResult, e); + if (!ex.isTimeout()) { // will be handled by the caller + logger.trace("{}: API call returned exception", thingName, ex); + } + throw ex; + } + return apiResult; + } + + /** + * Fill in POST data, set http headers + * + * @param request HTTP request structure + * @param data POST data, might be empty + */ + private void fillPostData(Request request, String data) { + boolean json = data.startsWith("{") || data.contains("\": {"); + String type = json ? CONTENT_TYPE_JSON : CONTENT_TYPE_FORM_URLENC; + request.header(HttpHeader.CONTENT_TYPE, type); + if (!data.isEmpty()) { + StringContentProvider postData; + postData = new StringContentProvider(type, data, StandardCharsets.UTF_8); + request.content(postData); + request.header(HttpHeader.CONTENT_LENGTH, Long.toString(postData.getLength())); + } + } + + /** + * Format POST body depending on content type (JSON or form encoded) + * + * @param dataMap Field list + * @param json true=JSON format, false=form encoded + * @return formatted body + */ + public static String buildPostData(Map dataMap, boolean json) { + String data = ""; + for (Map.Entry e : dataMap.entrySet()) { + data = data + (data.isEmpty() ? "" : json ? ", " : "&"); + if (!json) { + data = data + e.getKey() + "=" + e.getValue(); + } else { + data = data + "\"" + e.getKey() + "\" : \"" + e.getValue() + "\""; + } + } + return json ? "{ " + data + " }" : data; + } + + public String getControlUriPrefix(Integer id) { + String uri = ""; + if (profile.isLight || profile.isDimmer) { + if (profile.isDuo || profile.isDimmer) { + // Duo + Dimmer + uri = SHELLY_URL_CONTROL_LIGHT; + } else { + // Bulb + RGBW2 + uri = "/" + (profile.inColor ? SHELLY_MODE_COLOR : SHELLY_MODE_WHITE); + } + } else { + // Roller, Relay + uri = SHELLY_URL_CONTROL_RELEAY; + } + uri = uri + "/" + id; + return uri; + } + + public int getTimeoutErrors() { + return timeoutErrors; + } + + public int getTimeoutsRecovered() { + return timeoutsRecovered; + } +} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java new file mode 100644 index 0000000000..4cf98d3962 --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1ApiJsonDTO.java @@ -0,0 +1,1194 @@ +/** + * 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.api1; + +import java.util.ArrayList; +import java.util.List; + +import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyMotionSettings; +import org.openhab.core.thing.CommonTriggerEvents; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link Shelly1ApiJsonDTO} is used for the JSon/GSon mapping + * + * @author Markus Michels - Initial contribution + */ +public class Shelly1ApiJsonDTO { + public static final String SHELLY_NULL_URL = "null"; + public static final String SHELLY_URL_DEVINFO = "/shelly"; + public static final String SHELLY_URL_STATUS = "/status"; + public static final String SHELLY_URL_SETTINGS = "/settings"; + public static final String SHELLY_URL_SETTINGS_AP = "/settings/ap"; + public static final String SHELLY_URL_SETTINGS_STA = "/settings/sta"; + public static final String SHELLY_URL_SETTINGS_LOGIN = "/settings/sta"; + public static final String SHELLY_URL_SETTINGS_CLOUD = "/settings/cloud"; + public static final String SHELLY_URL_LIST_IR = "/ir/list"; + public static final String SHELLY_URL_SEND_IR = "/ir/emit"; + public static final String SHELLY_URL_RESTART = "/reboot"; + + public static final String SHELLY_URL_SETTINGS_RELAY = "/settings/relay"; + public static final String SHELLY_URL_STATUS_RELEAY = "/status/relay"; + public static final String SHELLY_URL_CONTROL_RELEAY = "/relay"; + + public static final String SHELLY_URL_SETTINGS_EMETER = "/settings/emeter"; + public static final String SHELLY_URL_STATUS_EMETER = "/emeter"; + public static final String SHELLY_URL_DATA_EMETER = "/emeter/{0}/em_data.csv"; + + public static final String SHELLY_URL_CONTROL_ROLLER = "/roller"; + public static final String SHELLY_URL_SETTINGS_ROLLER = "/settings/roller"; + + public static final String SHELLY_URL_SETTINGS_LIGHT = "/settings/light"; + public static final String SHELLY_URL_STATUS_LIGHT = "/light"; + public static final String SHELLY_URL_CONTROL_LIGHT = "/light"; + + public static final String SHELLY_URL_SETTINGS_DIMMER = "/settings/light"; + + // Wakeup reasons + public static final String SHELLY_WAKEUPT_SENSOR = "SENSOR"; // new sensordata + 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 + + // + // Action URLs according to the device type + // + public static final String SHELLY_EVENTURL_SUFFIX = "_url"; + + // Relay + public static final String SHELLY_EVENT_BTN_ON = "btn_on"; + public static final String SHELLY_EVENT_BTN_OFF = "btn_off"; + public static final String SHELLY_EVENT_OUT_ON = "out_on"; + public static final String SHELLY_EVENT_OUT_OFF = "out_off"; + public static final String SHELLY_EVENT_SHORTPUSH = "shortpush"; + public static final String SHELLY_EVENT_LONGPUSH = "longpush"; + // Button + public static final String SHELLY_EVENT_DOUBLE_SHORTPUSH = "double_shortpush"; + public static final String SHELLY_EVENT_TRIPLE_SHORTPUSH = "triple_shortpush"; + public static final String SHELLY_EVENT_SHORT_LONGTPUSH = "shortpush_longpush"; + public static final String SHELLY_EVENT_LONG_SHORTPUSH = "longpush_shortpush"; + + // Dimmer + public static final String SHELLY_EVENT_BTN1_ON = "btn1_on"; + public static final String SHELLY_EVENT_BTN1_OFF = "btn1_off"; + public static final String SHELLY_EVENT_BTN2_ON = "btn2_on"; + public static final String SHELLY_EVENT_BTN2_OFF = "btn2_off"; + public static final String SHELLY_EVENT_SHORTPUSH1 = "btn1_shortpush"; + public static final String SHELLY_EVENT_LONGPUSH1 = "btn1_longpush"; + public static final String SHELLY_EVENT_SHORTPUSH2 = "btn2_shortpush"; + public static final String SHELLY_EVENT_LONGPUSH2 = "btn2_longpush"; + + // Roller + 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"; + public static final String SHELLY_EVENT_DARK = "dark"; + public static final String SHELLY_EVENT_TWILIGHT = "twilight"; + public static final String SHELLY_EVENT_BRIGHT = "bright"; + public static final String SHELLY_EVENT_FLOOD_DETECTED = "flood_detected"; + public static final String SHELLY_EVENT_FLOOD_GONE = "flood_gone"; + public static final String SHELLY_EVENT_VIBRATION = "vibration"; // DW 1.6.5+ + public static final String SHELLY_EVENT_OPEN = "open"; // DW 1.6.5+ + public static final String SHELLY_EVENT_CLOSE = "close"; // DW 1.6.5+ + public static final String SHELLY_EVENT_TEMP_OVER = "temp_over"; // FW 1.7 + public static final String SHELLY_EVENT_TEMP_UNDER = "temp_under"; // FW 1.7 + + // Gas + public static final String SHELLY_EVENT_ALARM_MILD = "alarm_mild"; // DW 1.7+ + public static final String SHELLY_EVENT_ALARM_HEAVY = "alarm_heavy"; // DW 1.7+ + public static final String SHELLY_EVENT_ALARM_OFF = "alarm_off"; // DW 1.7+ + + // + // 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"; + public static final String SHELLY_BTNT_TWO_BUTTON = "dual_button"; + public static final String SHELLY_BTNT_TOGGLE = "toggle"; + public static final String SHELLY_BTNT_EDGE = "edge"; + public static final String SHELLY_BTNT_DETACHED = "detached"; + + public static final String SHELLY_STATE_LAST = "last"; + 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"; + + public static final String SHELLY_ALWD_TRIGGER_NONE = "none"; + public static final String SHELLY_ALWD_ROLLER_TURN_OPEN = "open"; + public static final String SHELLY_ALWD_ROLLER_TURN_CLOSE = "close"; + public static final String SHELLY_ALWD_ROLLER_TURN_STOP = "stop"; + + // API Error Codes + public static final String SHELLY_APIERR_UNAUTHORIZED = "Unauthorized"; + public static final String SHELLY_APIERR_TIMEOUT = "Timeout"; + public static final String SHELLY_APIERR_NOT_CALIBRATED = "Not calibrated!"; + + // API device types / properties + public static final String SHELLY_CLASS_RELAY = "relay"; // Relay: relay mode + public static final String SHELLY_CLASS_ROLLER = "roller"; // Relay: roller mode + public static final String SHELLY_CLASS_LIGHT = "light"; // Bulb: color mode + public static final String SHELLY_CLASS_EMETER = "emeter"; // EM/EM3: emeter + + public static final String SHELLY_API_ON = "on"; + public static final String SHELLY_API_OFF = "off"; + public static final String SHELLY_API_TRUE = "true"; + public static final String SHELLY_API_FALSE = "false"; + + public static final String SHELLY_API_MODE = "mode"; + public static final String SHELLY_MODE_RELAY = "relay"; // Relay: relay mode + public static final String SHELLY_MODE_ROLLER = "roller"; // Relay: roller mode + public static final String SHELLY_MODE_COLOR = "color"; // Bulb/RGBW2: color mode + public static final String SHELLY_MODE_WHITE = "white"; // Bulb/RGBW2: white mode + + public static final String SHELLY_LED_STATUS_DISABLE = "led_status_disable"; + public static final String SHELLY_LED_POWER_DISABLE = "led_power_disable"; + + public static final String SHELLY_API_STOPR_NORMAL = "normal"; + public static final String SHELLY_API_STOPR_SAFETYSW = "safety_switch"; + public static final String SHELLY_API_STOPR_OBSTACLE = "obstacle"; + public static final String SHELLY_API_STOPR_OVERPOWER = "overpower"; + + public static final String SHELLY_TIMER_AUTOON = "auto_on"; + public static final String SHELLY_TIMER_AUTOOFF = "auto_off"; + public static final String SHELLY_TIMER_ACTIVE = "has_timer"; + + public static final String SHELLY_LIGHT_TURN = "turn"; + public static final String SHELLY_LIGHT_DEFSTATE = "def_state"; + public static final String SHELLY_LIGHTTIMER = "timer"; + + public static final String SHELLY_COLOR_RED = "red"; + public static final String SHELLY_COLOR_BLUE = "blue"; + public static final String SHELLY_COLOR_GREEN = "green"; + public static final String SHELLY_COLOR_YELLOW = "yellow"; + public static final String SHELLY_COLOR_WHITE = "white"; + public static final String SHELLY_COLOR_GAIN = "gain"; + public static final String SHELLY_COLOR_BRIGHTNESS = "brightness"; + public static final String SHELLY_COLOR_TEMP = "temp"; + public static final String SHELLY_COLOR_EFFECT = "effect"; + + public static final int SHELLY_MIN_ROLLER_POS = 0; + public static final int SHELLY_MAX_ROLLER_POS = 100; + public static final int SHELLY_MIN_BRIGHTNESS = 0; + public static final int SHELLY_MAX_BRIGHTNESS = 100; + public static final int SHELLY_MIN_GAIN = 0; + public static final int SHELLY_MAX_GAIN = 100; + public static final int SHELLY_MIN_COLOR = 0; + public static final int SHELLY_MAX_COLOR = 255; + public static final int SHELLY_DIM_STEPSIZE = 10; + + // color temperature: 3000 = warm, 4750 = white, 6565 = cold; gain: 0..100 + public static final int MIN_COLOR_TEMP_BULB = 3000; + public static final int MAX_COLOR_TEMP_BULB = 6500; + public static final int MIN_COLOR_TEMP_DUO = 2700; + public static final int MAX_COLOR_TEMP_DUO = 6500; + public static final int COLOR_TEMP_RANGE_BULB = MAX_COLOR_TEMP_DUO - MIN_COLOR_TEMP_DUO; + public static final int COLOR_TEMP_RANGE_DUO = MAX_COLOR_TEMP_DUO - MIN_COLOR_TEMP_DUO; + public static final double MIN_BRIGHTNESS = 0.0; + public static final double MAX_BRIGHTNESS = 100.0; + public static final double SATURATION_FACTOR = 2.55; + public static final double GAIN_FACTOR = SHELLY_MAX_GAIN / 100; + public static final double BRIGHTNESS_FACTOR = SHELLY_MAX_BRIGHTNESS / 100; + + // Door/Window + public static final String SHELLY_API_ILLUM_DARK = "dark"; + public static final String SHELLY_API_ILLUM_TWILIGHT = "twilight"; + public static final String SHELLY_API_ILLUM_BRIGHT = "bright"; + public static final String SHELLY_API_DWSTATE_OPEN = "open"; + public static final String SHELLY_API_DWSTATE_CLOSE = "close"; + + // Shelly Sense + public static final String SHELLY_IR_CODET_STORED = "stored"; + public static final String SHELLY_IR_CODET_PRONTO = "pronto"; + public static final String SHELLY_IR_CODET_PRONTO_HEX = "pronto_hex"; + + // Bulb/Duo/RGBW2 + public static final int SHELLY_MIN_EFFECT = 0; + public static final int SHELLY_MAX_EFFECT = 6; + + // Button + public static final String SHELLY_BTNEVENT_1SHORTPUSH = "S"; + public static final String SHELLY_BTNEVENT_2SHORTPUSH = "SS"; + public static final String SHELLY_BTNEVENT_3SHORTPUSH = "SSS"; + public static final String SHELLY_BTNEVENT_LONGPUSH = "L"; + public static final String SHELLY_BTNEVENT_SHORTLONGPUSH = "SL"; + public static final String SHELLY_BTNEVENT_LONGSHORTPUSH = "LS"; + + public static final String SHELLY_TEMP_CELSIUS = "C"; + public static final String SHELLY_TEMP_FAHRENHEIT = "F"; + + // Motion + public static final int SHELLY_MOTION_SLEEPTIME_OFFSET = 3; // we need to substract and offset + + // TRV + public static final int SHELLY_TRV_MIN_TEMP = 5; // < 5: means: lowest (valve fully closed) + public static final int SHELLY_TRV_MAX_TEMP = 30; // > 30: means: highest (valve fully open) + + public static final String SHELLY_TRV_MODE_MANUAL = "manual"; + public static final String SHELLY_TRV_MODE_AUTO = "automatic"; + + // CoIoT Multicast setting + public static final String SHELLY_COIOT_MCAST = "mcast"; + + public static class ShellySettingsDevice { + public String type; + public String mac; + public String hostname; + public String fw; + public Boolean auth; + public Integer gen; + + @SerializedName("coiot") // Shelly Motion Multicast Endpoint + public String coiot; + public Integer longid; + + @SerializedName("num_outputs") + public Integer numOutputs; + @SerializedName("num_meters") + public Integer numMeters; + @SerializedName("num_emeters") + public Integer numEMeters; + @SerializedName("num_rollers") + public Integer numRollers; + } + + public static class ShellySettingsWiFiAp { + public Boolean enabled; + public String ssid; + public String key; + } + + public static class ShellySettingsWiFiNetwork { + public Boolean enabled; + public String ssid; + public Integer rssi; + + @SerializedName("ipv4_method") + public String ipv4Method; + public String ip; + public String gw; + public String mask; + public String dns; + } + + public static class ShellySettingsMqtt { + public Boolean enable; + public String server; + public String user; + @SerializedName("reconnect_timeout_max") + public Double reconnectTimeoutMax; + @SerializedName("reconnect_timeout_min") + public Double reconnectTimeoutMin; + @SerializedName("clean_session") + public Boolean cleanSession; + @SerializedName("keep_alive") + public Integer keepAlive; + @SerializedName("will_topic") + public String willTopic; + @SerializedName("will_message") + public String willMessage; + @SerializedName("max_qos") + public Integer maxQOS; + public Boolean retain; + @SerializedName("update_period") + public Integer updatePeriod; + } + + public static class ShellySettingsCoiot { // FW 1.6+ + @SerializedName("update_period") + public Integer updatePeriod; + public Boolean enabled; // Motion 1.0.7: Coap can be disabled + public String peer; // if set the device uses singlecast CoAP, mcast=set back to Multicast + } + + public static class ShellyStatusMqtt { + public Boolean connected; + } + + public static class ShellySettingsSntp { + public String server; + public Boolean enabled; + } + + public static class ShellySettingsLogin { + public Boolean enabled; + public Boolean unprotected; + public String username; + public String password; + } + + public static class ShellySettingsBuildInfo { + @SerializedName("build_id") + public String buildId; + @SerializedName("build_timestamp") + public String buildTimestamp; + @SerializedName("build_version") + public String buildVersion; + } + + public static class ShellyStatusCloud { + public Boolean enabled; + public Boolean connected; + } + + public static class ShellySettingsHwInfo { + @SerializedName("hw_revision") + public String hwRevision; + @SerializedName("batch_id") + public Integer batchId; + } + + public static class ShellySettingsScheduleRules { + } + + public static class ShellySettingsRelay { + public String name; + @SerializedName("default_state") + public String defaultState; // Accepted values: off, on, last, switch + @SerializedName("btn_type") + public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx + @SerializedName("btn1_type") // Shelly 1L + public String btnType1; + @SerializedName("btn2_type") // Shelly 1L + public String btnType2; + @SerializedName("has_timer") + public Boolean hasTimer; // Whether a timer is currently armed for this channel + @SerializedName("auto_on") + public Double autoOn; // Automatic flip back timer, seconds. Will engage after turning Shelly1 OFF. + @SerializedName("auto_off") + public Double autoOff; // Automatic flip back timer, seconds. Will engage after turning Shelly1 ON. + @SerializedName("btn_on_url") + public String btnOnUrl; // input is activated + @SerializedName("btnOffUrl") + public String btnOffUrl; // input is deactivated + @SerializedName("out_on_url") + public String outOnUrl; // output is activated + @SerializedName("out_off_url") + public String outOffUrl; // output is deactivated + @SerializedName("roller_open_url") + public String rollerOpenUrl; // to access when roller reaches open position + @SerializedName("roller_close_url") + public String rollerCloseUrl; // to access when roller reaches close position + @SerializedName("roller_stop_url") + public String rollerStopUrl; // to access when roller stopped + @SerializedName("longpush_url") + 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 { + public String name; // unique name of the device + public Boolean ison; // true: output is ON + @SerializedName("default_state") + public String defaultState; // Accepted values: off, on, last, switch + @SerializedName("auto_on") + public Double autoOn; // Automatic flip back timer, seconds. Will engage after turning Shelly1 OFF. + @SerializedName("auto_off") + public Double autoOff; // Automatic flip back timer, seconds. Will engage after turning Shelly1 ON. + @SerializedName("btn1_on_url") + public String btn1OnUrl; // URL to access when SW input is activated + @SerializedName("btn1_off_url") + public String btn1OffUrl; // URL to access when SW input is deactivated + @SerializedName("btn2_on_url") + public String btn2OnUrl; // URL to access when SW input is activated + @SerializedName("btn2_off_url") + public String btn2OoffUrl; // URL to access when SW input is deactivated + @SerializedName("out_on_url") + public String outOnUrl; // URL to access when output is activated + @SerializedName("out_off_url") + public String outOffUrl; // URL to access when output is deactivated + @SerializedName("longpush_url") + public String pushLongUrl; // long push button event + @SerializedName("shortpush_url") + public String pushShortUrl; // short push button event + @SerializedName("btn_type") + public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx + @SerializedName("btn1_type") + public String btnType1; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx + @SerializedName("btn2_type") + public String btnType2; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx + @SerializedName("swap_inputs") + public Integer swapInputs; // 0=no + } + + public static class ShellySettingsRoller { + public Double maxtime; + @SerializedName("maxtime_open") + public Double maxtimeOpen; + @SerializedName("maxtime_close") + public Double maxtimeClose; + @SerializedName("default_state") + public String defaultState; // see SHELLY_STATE_xxx + public Boolean swap; + @SerializedName("swap_inputs") + public Boolean swapInputs; + @SerializedName("input_mode") + public String inputMode; // see SHELLY_INP_MODE_OPENCLOSE + @SerializedName("button_type") + public String buttonType; // // see SHELLY_BTNT_xxx + @SerializedName("btn_Reverse") + public Integer btnReverse; + public String state; + public Double power; + @SerializedName("is_valid") + public Boolean isValid; + @SerializedName("safety_switch") + public Boolean safetySwitch; + @SerializedName("obstacle_mode") + public String obstaclMode; // SHELLY_OBSTMODE_ + @SerializedName("obstacle_action") + public String obstacleAction; // see SHELLY_STATE_xxx + @SerializedName("obstacle_power") + public Integer obstaclePower; + @SerializedName("obstacle_delay") + public Integer obstacleDelay; + @SerializedName("safety_mode") + public String safetyMode; // see SHELLY_SAFETYM_xxx + @SerializedName("safety_action") + public String safetyAction; // see SHELLY_STATE_xxx + @SerializedName("safety_allowed_on_trigger") + public String safetyAllowedOnTrigger; // see SHELLY_ALWD_TRIGGER_xxx + @SerializedName("off_power") + public Integer offPower; + public Boolean positioning; + } + + public static class ShellySettingsRgbwLight { + public String name; + public Boolean ison; // true: output is ON + public Integer brightness; + public Integer temp; + public Integer transition; + @SerializedName("default_state") + public String defaultState; + @SerializedName("auto_on") + public Double autoOn; // Automatic flip back timer, seconds. Will engage after turning Shelly1 OFF. + @SerializedName("auto_off") + public Double autoOff; // Automatic flip back timer, seconds. Will engage after turning Shelly1 ON. + public Boolean schedule; + @SerializedName("btn_type") + public String btnType; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx + @SerializedName("btn_reverse") + public Integer btnReverse; // Accepted values: momentary, toggle, edge, detached - // see SHELLY_BTNT_xxx + @SerializedName("out_on_url") + public String outOnUrl; // output is activated + @SerializedName("out_off_url") + public String outOffUrl; // output is deactivated + } + + public static class ShellyFavPos { // FW 1.9.2+ in roller mode + public String name; + public Integer pos; + } + + public static class ShellyInputState { + public Integer input; + + // Shelly Button + public String event; + @SerializedName("event_cnt") + public Integer eventCount; + } + + public static class ShellySettingsMeter { + @SerializedName("is_valid") + public Boolean isValid; + public Double power; + public Double[] counters = { 0.0, 0.0, 0.0 }; + public Double total; + public Long timestamp; + } + + public static class ShellySettingsEMeter { // ShellyEM meter + @SerializedName("is_valid") + public Boolean isValid; // Whether the associated meter is functioning properly + public Double power; // Instantaneous power, Watts + public Double reactive; // Instantaneous reactive power, Watts + public Double voltage; // RMS voltage, Volts + public Double total; // Total consumed energy, Wh + @SerializedName("total_returned") + public Double totalReturned; // Total returned energy, Wh + + public Double pf; // 3EM + public Double current; // 3EM + } + + public static class ShellySettingsUpdate { + public String status; + @SerializedName("has_update") + public Boolean hasUpdate; + @SerializedName("new_version") + public String newVersion; + @SerializedName("old_version") + public String oldVersion; + @SerializedName("beta_version") + public String betaVersion; + } + + public static class ShellySettingsGlobal { + // https://shelly-api-docs.shelly.cloud/#shelly1pm-settings + public ShellySettingsDevice device; + @SerializedName("wifi_ap") + public ShellySettingsWiFiAp wifiAp; + @SerializedName("wifi_sta") + public ShellySettingsWiFiNetwork wifiSta; + @SerializedName("wifi_sta1") + public ShellySettingsWiFiNetwork wifiSta1; + @SerializedName("wifirecovery_reboot_enabled") + public Boolean wifiRecoveryReboot; // FW 1.10+ + @SerializedName("ap_roaming") + public ShellyApRoaming apRoaming; // FW 1.10+ + + public ShellySettingsMqtt mqtt; // not used for now + public ShellySettingsSntp sntp; // not used for now + public ShellySettingsCoiot coiot; // Firmware 1.6+ + public ShellySettingsLogin login; + @SerializedName("pin_code") + public String pinCode; + @SerializedName("coiot_execute_enable") + public Boolean coiotExecuteEnable; + public String name; + public Boolean discoverable; // FW 1.6+ + public String fw; + @SerializedName("build_info") + public ShellySettingsBuildInfo buildInfo; + public ShellyStatusCloud cloud; + @SerializedName("sleep_mode") + public ShellySensorSleepMode sleepMode; // FW 1.6 + @SerializedName("external_power") + public Integer externalPower; // H&T FW 1.6, seems to be the same like charger for the Sense + @SerializedName("debug_enable") // FW 1.10+ + public Boolean debugEnable; + + public String timezone; + public Double lat; + public Double lng; + public Boolean tzautodetect; + public String time; + + public ShellySettingsHwInfo hwinfo; + public String mode; + @SerializedName("max_power") + public Double maxPower; + public Boolean calibrated; + + public ArrayList relays; + public ArrayList rollers; + public ArrayList dimmers; + public ArrayList lights; + public ArrayList emeters; + public ArrayList inputs; // ix3 + public ArrayList thermostats; // TRV + + public Double voltage; // AC voltage for Shelly 2.5 + @SerializedName("supply_voltage") + public Long supplyVoltage; // Shelly 1PM/1L: 0=110V, 1=220V + + @SerializedName("temperature_units") + public String temperatureUnits = "C"; // Either'C'or'F' + + @SerializedName("led_status_disable") + public Boolean ledStatusDisable; // PlugS only Disable LED indication for network + // status + @SerializedName("led_power_disable") + public Boolean ledPowerDisable; // PlugS only Disable LED indication for network + // status + @SerializedName("light_sensor") + public String lightSensor; // Sense: sensor type + @SerializedName("rain_sensor") + public Boolean rainSensor; // Flood: true=in rain mode + + // FW 1.5.7: Door Window + @SerializedName("dark_treshold") + public Integer darkTreshold; // Illumination definition for "dark" in lux + @SerializedName("twilight_treshold") + public Integer twiLightTreshold; // Illumination definition for "twilight" in lux + @SerializedName("dark_url") + public String darkUrl; // URL to report to when luminance <= dark_threshold + @SerializedName("twilight_url") + public String twiLightUrl; // URL reports when luminance > dark_threshold AND luminance <= + @SerializedName("close_url") + public String closeUrl; // URL reports when DW contact is closed FW 1.6.5+ + @SerializedName("vibration_url") + public String vibrationUrl; // URL reports when DW detects vibration FW 1.6.5+ + + // Gas FW 1.7 + @SerializedName("set_volume") + public Integer volume; // Speaker volume for alarm + @SerializedName("alarm_off_url") + public String alarmOffUrl; // URL reports when alarm went off + @SerializedName("alarm_mild_url") + public String alarmMidUrl; // URL reports middle alarm + @SerializedName("alarm_heavy_url") + public String alarmHeavyfUrl; // URL reports heavy alarm + + // Roller with FW 1.9.2+ + @SerializedName("favorites_enabled") + public Boolean favoritesEnabled = false; + public ArrayList favorites; + + // Motion + public ShellyMotionSettings motion; + @SerializedName("tamper_sensitivity") + public Integer tamperSensitivity; + @SerializedName("dark_threshold") + public Integer darkThreshold; + @SerializedName("twilight_threshold") + public Integer twilightThreshold; + + @SerializedName("sleep_time") // Shelly Motion + public Integer sleepTime; + } + + public static class ShellySettingsAttributes { + @SerializedName("device_type") + public String deviceType; // Device model identifier + @SerializedName("device_mac") + public String deviceMac; // MAC address of the device in hexadecimal + @SerializedName("wifi_ap") + public String wifiAp; // WiFi access poInteger configuration, see /settings/ap for details + @SerializedName("wifi_sta") + public String wifiSta; // WiFi client configuration. See /settings/sta for details + public String login; // credentials used for HTTP Basic authentication for the REST interface. If + // enabled is true clients must include an Authorization: Basic ... HTTP header with valid + // credentials when performing TP requests. + public String name; // unique name of the device. + public String fw; // current FW version + } + + public static class ShellyActionsStats { + public Integer skipped; + } + + public static class ShellySettingsStatus { + public String name; // FW 1.8: Symbolic Device name is configurable + + @SerializedName("wifi_sta") + public ShellySettingsWiFiNetwork wifiSta; // WiFi client configuration. See /settings/sta for details + public ShellyStatusCloud cloud; + public ShellyStatusMqtt mqtt; + + public String time; + public Integer serial = -1; + @SerializedName("has_update") + public Boolean hasUpdate; + public String mac; + public Boolean discoverable; // FW 1.6+ + @SerializedName("cfg_changed_cnt") + public Integer cfgChangedCount; // FW 1.8 + @SerializedName("actions_stats") + public ShellyActionsStats astats; + + public ArrayList relays; + public Double voltage; // Shelly 2.5 + + public ArrayList rollers; + public Integer input; // RGBW2 has no JSON array + public ArrayList inputs; + public ArrayList lights; + public ArrayList dimmers; + public ArrayList meters; + public ArrayList emeters; + + // Internal device temp + public ShellySensorTmp tmp; // Shelly 1PM + public Double temperature = SHELLY_API_INVTEMP; // Shelly 2.5 + public Boolean overtemperature; + + // Shelly Dimmer only + public Boolean loaderror; + public Boolean overload; + + // Shelly TRV + public Boolean calibrated; + public ArrayList thermostats; + + public ShellySettingsUpdate update; + @SerializedName("ram_total") + public Long ramTotal; + @SerializedName("ram_free") + public Long ramFree; + @SerializedName("fs_size") + public Long fsSize; + @SerializedName("fs_free") + public Long fsFree; + public Long uptime; + + @SerializedName("sleep_time") // Shelly Motion + public Integer sleepTime; + + public String json; + } + + public static class ShellySettingsInput { + @SerializedName("btn_type") + public String btnType; + } + + public static class ShellyControlRelay { + // https://shelly-api-docs.shelly.cloud/#shelly1-1pm-settings-relay-0 + @SerializedName("is_valid") + public Boolean isValid; + @SerializedName("has_timer") + public Boolean hasTimer; // Whether a timer is currently armed for this channel + @SerializedName("timer_remaining") + public Integer timerRemaining; // FW 1.6+ + public Boolean overpower; // Shelly1PM only if maximum allowed power was exceeded + + public String turn; // Accepted values are on and off. This will turn ON/OFF the respective output + // channel when request is sent . + public Integer timer; // A one-shot flip-back timer in seconds. + } + + public static class ShellyShortStatusRelay { + public String name; // FW 1.8+: Channel could now have a logical name + @SerializedName("is_valid") + public Boolean isValid; + public Boolean ison; // Whether output channel is on or off + @SerializedName("has_timer") + public Boolean hasTimer; // Whether a timer is currently armed for this channel + @SerializedName("timer_remaining") + public Integer timerRemaining; + public Boolean overpower; // Shelly1PM only if maximum allowed power was exceeded + public Double temperature; // Internal device temperature + public Boolean overtemperature; // Device over heated + } + + public static class ShellyShortLightStatus { + public Boolean ison; // Whether output channel is on or off + public String mode; // color or white - valid only for Bulb and RGBW2 even Dimmer returns it also + public Integer brightness; // brightness: 0.100% + + @SerializedName("has_timer") + public Boolean hasTimer; + } + + public static class ShellyStatusRelay { + public String name; // FW 1.8: Symbolic channel name is configurable + + @SerializedName("wifi_sta") + public ShellySettingsWiFiNetwork wifiSta; // WiFi status + public ShellySettingsCoiot coiot; // Firmware 1.6+ + public Integer serial; + public String mac; // MAC + public ArrayList relays; // relay status + public ArrayList meters; // current meter value + public ArrayList inputs; // Firmware 1.5.6+ + + @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 Double temperature; // device temp acc. on the selected temp unit + public ShellySensorTmp tmp; + } + + public static class ShellyStatusDimmer { + @SerializedName("wifi_sta") + public ShellySettingsWiFiNetwork wifiSta; // WiFi status + public ArrayList lights; // relay status + public ArrayList meters; // current meter value + + public ShellySensorTmp tmp; + public Boolean overtemperature; + + public Boolean loaderror; + public Boolean overload; + } + + public static class ShellyRollerStatus { + public String name; // FW 1.8: Symbolic name is configurable + + @SerializedName("roller_pos") + public Integer rollerPos; // number Desired position in percent + public Integer duration; // If specified, the motor will move for this period in seconds. If missing, the + // value of maxtime in /settings/roller/N will be used. + public String state; // One of stop, open, close + public Double power; // Current power consumption in Watts + @SerializedName("is_valid") + public Boolean isValid; // If the power meter functions properly + @SerializedName("safety_switch") + public Boolean safetySwitch; // Whether the safety input is currently triggered + public Boolean overtemperature; + @SerializedName("stop_reason") + public String stopReason; // Last cause for stopping: normal, safety_switch, obstacle + @SerializedName("last_direction") + public String lastDirection; // Last direction of motion, open or close + public Boolean calibrating; + public Boolean positioning; // true when calibration was performed + @SerializedName("current_pos") + public Integer currentPos; // current position 0..100, 100=open + } + + public static class ShellyOtaCheckResult { + public String status; + } + + public static class ShellyApRoaming { + public Boolean enabled; + public Integer threshold; + } + + public static class ShellySensorSleepMode { + public Integer period; + public String unit; + } + + // Shelly TRV + public class ShellyThermnostat { + public class ShellyThermTargetTemp { + public Boolean enabled; + public Double value; + public String unit; + } + + public class ShellyThermTemp { + public Double value; + public String units; + @SerializedName("is_valid") + public Boolean isValid; + } + + public Double pos; + @SerializedName("target_t") + public ShellyThermTargetTemp targetTemp; + public Boolean schedule; + @SerializedName("schedule_profile") + public Integer profile; + @SerializedName("schedule_profile_names") + public String[] profileNames; + public ShellyThermTemp tmp; + @SerializedName("boost_minutes") + public Integer boostMinutes; + } + + public static class ShellySensorTmp { + public Double value; // Temperature in configured unites + public String units; // 'C' or 'F' + public Double tC; // temperature in deg C + public Double tF; // temperature in deg F + @SerializedName("is_valid") + public Boolean isValid; // whether the internal sensor is operating properly + } + + public static class ShellyStatusSensor { + // https://shelly-api-docs.shelly.cloud/#h-amp-t-settings + public static class ShellySensorHum { + public Double value; // relative humidity in % + } + + public static class ShellySensorBat { + public Double value; // estimated remaining battery capacity in % + public Double voltage; // battery voltage + }; + + // Door/Window sensor + public static class ShellySensorState { + @SerializedName("is_valid") + public Boolean isValid; // whether the internal sensor is operating properly + public String state; // Shelly Door/Window + + // Shelly Motion + public Boolean motion; + public Boolean vibration; + @SerializedName("timestamp") + public Long motionTimestamp; + @SerializedName("active") + public Boolean motionActive; + } + + public static class ShellySensorLux { + @SerializedName("is_valid") + public Boolean isValid; // whether the internal sensor is operating properly + public Double value; + + public String illumination; + } + + public static class ShellySensorAccel { + public Integer tilt; // Tilt in ° + public Integer vibration; // Whether vibration is detected + } + + public static class ShellyMotionSettings { + public Integer sensitivity; + @SerializedName("blind_time_minutes") + public Integer blindTimeMinutes; + @SerializedName("pulse_count") + public Integer pulseCount; + @SerializedName("operating_mode") + public Integer operatingMode; + public Boolean enabled; + } + + public static class ShellyExtTemperature { + public static class ShellyShortTemp { + public Double tC; // temperature in deg C + public Double tF; // temperature in deg F + } + + // Shelly 1/1PM have up to 3 sensors + // for whatever reasons it's not an array, but 3 independent elements + @SerializedName("0") + public ShellyShortTemp sensor1; + @SerializedName("1") + public ShellyShortTemp sensor2; + @SerializedName("2") + public ShellyShortTemp sensor3; + } + + public static class ShellyExtHumidity { + public static class ShellyShortHum { + public Double hum; // Humidity reading of sensor 0, percent + } + + // Shelly 1/1PM have up to 3 sensors + // for whatever reasons it's not an array, but 3 independent elements + @SerializedName("0") + public ShellyShortHum sensor1; + } + + public static class ShellyADC { + public Double voltage; + } + + public ShellySensorTmp tmp; + public ShellySensorHum hum; + public ShellySensorLux lux; + public ShellySensorAccel accel; + public ShellySensorBat bat; + @SerializedName("sensor") + public ShellySensorState sensor; + public Boolean smoke; // SHelly Smoke + public Boolean flood; // Shelly Flood: true = flood condition detected + @SerializedName("rain_sensor") + public Boolean rainSensor; // Shelly Flood: true=in rain mode + + public Boolean motion; // Shelly Sense: true=motion detected + public Boolean charger; // Shelly Sense, TRV: true=charger connected + + @SerializedName("act_reasons") + public List actReasons; // HT/Smoke/Flood: list of reasons which woke up the device + + @SerializedName("sensor_error") + public String sensorError; // 1.5.7: Only displayed in case of error + + // FW 1.7: Shelly Gas + @SerializedName("gas_sensor") + public ShellyStatusGasSensor gasSensor; + @SerializedName("concentration") + public ShellyStatusGasConcentration concentration; + public ArrayList valves; + + // FW 1.7 Button + @SerializedName("connect_retries") + public Integer connectRetries; + public ArrayList inputs; // Firmware 1.5.6+ + + // Shelly UNI FW 1.9+ + public ArrayList adcs; + + // Shelly TRV + public Boolean calibrated; + public ArrayList thermostats; + } + + public static class ShellySettingsSmoke { + @SerializedName("temperature_units") + public String temperatureUnits; // Either 'C' or 'F' + @SerializedName("temperature_threshold") + public Integer temperatureThreshold; // Temperature delta (in configured degree units) which triggers an update + @SerializedName("sleep_mode_period") + public Integer sleepModePeriod; // Periodic update period in hours, between 1 and 24 + } + + // Shelly Gas + // "gas_sensor":{"sensor_state":"normal","self_test_state":"not_completed","alarm_state":"none"}, + // "concentration":{"ppm":0,"is_valid":true}, + public static class ShellyStatusGasSensor { + @SerializedName("sensor_state") + public String sensorState; + @SerializedName("self_test_state") + public String selfTestState; + @SerializedName("alarm_state") + public String alarmState; + } + + public static class ShellyStatusGasConcentration { + public Integer ppm; + @SerializedName("is_valid") + public Boolean isValid; + } + + public static class ShellyStatusValve { + public String state; // closed/opened/not_connected/failure/closing/opening/checking + } + + public static class ShellySettingsLight { + public Integer red; // red brightness, 0..255, applies in mode="color" + public Integer green; // green brightness, 0..255, applies in mode="color" + public Integer blue; // blue brightness, 0..255, applies in mode="color" + public Integer white; // white brightness, 0..255, applies in mode="color" + public Integer gain; // gain for all channels, 0..100, applies in mode="color" + public Integer temp; // color temperature in K, 3000..6500, applies in mode="white" + public Integer brightness; // brightness, 0..100, applies in mode="white" + public Integer effect; // Currently applied effect, description: 0: Off, 1: Meteor Shower, 2: Gradual + // Change, 3: Breath, + // 4: Flash, 5: On/Off Gradual, 6: Red/Green Change + @SerializedName("default_state") + public String defaultState; // one of on, off or last + @SerializedName("auto_on") + public Double autoOn; // see above + @SerializedName("auto_off") + public Double autoOff; // see above + + public Integer dcpower; // RGW2:Set to true for 24 V power supply, false for 12 V + + // Shelly Dimmer + public String mode; + public Boolean ison; + } + + public static class ShellySettingsNightMode { // FW1.5.7+ + public Integer enabled; + @SerializedName("start_time") + public String startTime; + @SerializedName("end_time") + public String endTime; + public Integer brightness; + } + + public static class ShellyStatusLightChannel { + public Boolean ison; + public Double power; + public Boolean overpower; + @SerializedName("has_timer") + public Boolean hasTimer; + @SerializedName("timer_started") + public Integer timerStarted; + @SerializedName("timer_duration") + public Integer timerDuration; + @SerializedName("timer_remaining") + public Integer timerRemaining; + + public Integer red; // red brightness, 0..255, applies in mode="color" + public Integer green; // green brightness, 0..255, applies in mode="color" + public Integer blue; // blue brightness, 0..255, applies in mode="color" + public Integer white; // white brightness, 0..255, applies in mode="color" + public Integer gain; // gain for all channels, 0..100, applies in mode="color" + public Integer temp; // color temperature in K, 3000..6500, applies in mode="white" + public Integer brightness; // brightness, 0..100, applies in mode="white" + public Integer effect; // Currently applied effect, description: 0: Off, 1: Meteor Shower, 2: Gradual + // Change, 3: Breath, + } + + public static class ShellyStatusLight { + public Boolean ison; // Whether output channel is on or off + public Integer input; + + public ArrayList lights; + public ArrayList meters; + } + + public static class ShellySenseKeyCode { + String id; // ID of the stored IR code into Shelly Sense. + String name; // Short description or name of the stored IR code. + } + + public static class ShellySendKeyList { + @SerializedName("key_codes") + public ArrayList keyCodes; + } + + /** + * Shelly Dimmer returns light[]. However, the structure doesn't match the lights[] of a Bulb/RGBW2. + * The tag lights[] will be replaced with dimmers[] so this could be mapped to a different Gson structure. + * The function requires that it's only called when the device is a dimmer - on get settings and get status + * + * @param json Input Json as received by the API + * @return Modified Json + */ + public static String fixDimmerJson(String json) { + return !json.contains("\"lights\":[") ? json + : json.replaceFirst(java.util.regex.Pattern.quote("\"lights\":["), "\"dimmers\":["); + } + + /** + * Convert Shelly Button events into OH button states + * + * @param eventType S/SS/SSS or L + * @return OH button states + */ + public static String mapButtonEvent(String eventType) { + // decode different codings + // 0..2: CoAP + // S/SS/SSS/L: CoAP for Button and xi3 + // shortpush/double_shortpush/triple_shortpush/longpush: REST + switch (eventType) { + case "0": + return CommonTriggerEvents.RELEASED; + case "1": + case SHELLY_BTNEVENT_1SHORTPUSH: + case SHELLY_EVENT_SHORTPUSH: + return CommonTriggerEvents.SHORT_PRESSED; + case SHELLY_BTNEVENT_2SHORTPUSH: + case SHELLY_EVENT_DOUBLE_SHORTPUSH: + return CommonTriggerEvents.DOUBLE_PRESSED; + case SHELLY_BTNEVENT_3SHORTPUSH: + case SHELLY_EVENT_TRIPLE_SHORTPUSH: + return "TRIPLE_PRESSED"; + case "2": + case SHELLY_BTNEVENT_LONGPUSH: + case SHELLY_EVENT_LONGPUSH: + return CommonTriggerEvents.LONG_PRESSED; + case SHELLY_BTNEVENT_SHORTLONGPUSH: + case SHELLY_EVENT_SHORT_LONGTPUSH: + return "SHORT_LONG_PRESSED"; + case SHELLY_BTNEVENT_LONGSHORTPUSH: + case SHELLY_EVENT_LONG_SHORTPUSH: + return "LONG_SHORT_PRESSED"; + default: + return ""; + } + } +} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTInterface.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTInterface.java new file mode 100644 index 0000000000..0be1585e6b --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTInterface.java @@ -0,0 +1,43 @@ +/** + * 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.api1; + +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.api1.Shelly1CoapJSonDTO.CoIotDescrBlk; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotDescrSen; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotSensor; +import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; +import org.openhab.core.types.State; + +/** + * The {@link Shelly1CoapListener} describes the listening interface to process Coap responses + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +public interface Shelly1CoIoTInterface { + public int getVersion(); + + public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map blkMap); + + public void completeMissingSensorDefinition(Map sensorMap); + + public boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s, + Map updates, ShellyColorUtils col); + + public String getLastWakeup(); +} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTProtocol.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTProtocol.java new file mode 100644 index 0000000000..f544616756 --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTProtocol.java @@ -0,0 +1,400 @@ +/** + * 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.api1; + +import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; +import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*; +import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; + +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.ShellyApiInterface; +import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotDescrBlk; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotDescrSen; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotSensor; +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; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link Shelly1CoIoTProtocol} implements common functions for the CoIoT implementations + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +public class Shelly1CoIoTProtocol { + private final Logger logger = LoggerFactory.getLogger(Shelly1CoIoTProtocol.class); + protected final String thingName; + protected final ShellyThingInterface thingHandler; + protected final ShellyDeviceProfile profile; + protected final ShellyApiInterface api; + protected final Map blkMap; + protected final Map sensorMap; + private final Gson gson = new GsonBuilder().create(); + + // Due to the fact that the device reports only the current/last status, but no real events, we need to distinguish + // between a real update or just a repeated status on periodic updates + protected int lastCfgCount = -1; + protected int[] lastEventCount = { -1, -1, -1, -1, -1, -1, -1, -1 }; // 4Pro has 4 relays, so 8 should be fine + protected String[] inputEvent = { "", "", "", "", "", "", "", "" }; + protected String lastWakeup = ""; + + public Shelly1CoIoTProtocol(String thingName, ShellyThingInterface thingHandler, Map blkMap, + Map sensorMap) { + this.thingName = thingName; + this.thingHandler = thingHandler; + this.blkMap = blkMap; + this.sensorMap = sensorMap; + this.profile = thingHandler.getProfile(); + this.api = thingHandler.getApi(); + } + + protected boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen sen, CoIotSensor s, + Map updates, ShellyColorUtils col) { + // Process status information and convert into channel updates + int rIndex = getIdFromBlk(sen); + String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL + : CHANNEL_GROUP_RELAY_CONTROL + rIndex; + + switch (sen.type.toLowerCase()) { + case "b": // BatteryLevel + + updateChannel(updates, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL, + toQuantityType(s.value, 0, Units.PERCENT)); + break; + case "h" /* Humidity */: + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM, + toQuantityType(s.value, DIGITS_PERCENT, Units.PERCENT)); + break; + case "m" /* Motion */: + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION, + s.value == 1 ? OnOffType.ON : OnOffType.OFF); + break; + case "l": // Luminosity + + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX, + toQuantityType(s.value, DIGITS_LUX, Units.LUX)); + break; + case "s": // CatchAll + switch (sen.desc.toLowerCase()) { + case "state": // Relay status + + case "output": + updatePower(profile, updates, rIndex, sen, s, sensorUpdates); + break; + case "input": + handleInput(sen, s, rGroup, updates); + break; + case "brightness": + // already handled by state/output + break; + case "overtemp": // ++ + if (s.value == 1) { + thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true); + } + break; + case "position": + // work around: Roller reports 101% instead max 100 + double pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(s.value, SHELLY_MAX_ROLLER_POS)); + updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL, + toQuantityType(SHELLY_MAX_ROLLER_POS - pos, Units.PERCENT)); + updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_POS, + toQuantityType(pos, Units.PERCENT)); + break; + case "flood": + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD, + s.value == 1 ? OnOffType.ON : OnOffType.OFF); + break; + case "vibration": // DW with FW1.6.5+ + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION, + s.value == 1 ? OnOffType.ON : OnOffType.OFF); + if (s.value == 1) { + thingHandler.triggerChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE, + EVENT_TYPE_VIBRATION); + } + break; + case "luminositylevel": // + + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ILLUM, getStringType(s.valueStr)); + break; + case "charger": // Sense + updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER, + s.value == 1 ? OnOffType.ON : OnOffType.OFF); + break; + // RGBW2/Bulb + case "red": + col.setRed((int) s.value); + updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_RED, + ShellyColorUtils.toPercent((int) s.value)); + break; + case "green": + col.setGreen((int) s.value); + updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GREEN, + ShellyColorUtils.toPercent((int) s.value)); + break; + case "blue": + col.setBlue((int) s.value); + updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_BLUE, + ShellyColorUtils.toPercent((int) s.value)); + break; + case "white": + col.setWhite((int) s.value); + updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_WHITE, + ShellyColorUtils.toPercent((int) s.value)); + break; + case "gain": + col.setGain((int) s.value); + updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GAIN, + ShellyColorUtils.toPercent((int) s.value, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN)); + break; + case "sensorerror": + String sensorError = s.valueStr != null ? getString(s.valueStr) : "" + s.value; + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, getStringType(sensorError)); + break; + default: + // Unknown + return false; + } + break; + + default: + // Unknown type + return false; + } + + return true; + } + + public static boolean updateChannel(Map updates, String group, String channel, State value) { + updates.put(mkChannelId(group, channel), value); + return true; + } + + protected void handleInput(CoIotDescrSen sen, CoIotSensor s, String rGroup, Map updates) { + int idx = getSensorNumber(sen.desc, sen.id) - 1; + String iGroup = profile.getInputGroup(idx); + String iChannel = CHANNEL_INPUT + profile.getInputSuffix(idx); + updateChannel(updates, iGroup, iChannel, s.value == 0 ? OnOffType.OFF : OnOffType.ON); + } + + protected void handleInputEvent(CoIotDescrSen sen, String type, int count, int serial, Map updates) { + int idx = getSensorNumber(sen.desc, sen.id) - 1; + String group = profile.getInputGroup(idx); + if (count == -1) { + // event type + updateChannel(updates, group, CHANNEL_STATUS_EVENTTYPE + profile.getInputSuffix(idx), new StringType(type)); + inputEvent[idx] = type; + } else { + // event count + updateChannel(updates, group, CHANNEL_STATUS_EVENTCOUNT + profile.getInputSuffix(idx), getDecimal(count)); + logger.trace( + "{}: Check button[{}] for event trigger (isButtonMode={}, isButton={}, hasBattery={}, serial={}, count={}, lastEventCount[{}]={}", + thingName, idx, profile.inButtonMode(idx), profile.isButton, profile.hasBattery, serial, count, idx, + lastEventCount[idx]); + if (profile.inButtonMode(idx) && ((profile.hasBattery && (count == 1)) + || ((lastEventCount[idx] != -1) && (count != lastEventCount[idx])))) { + if (!profile.isButton || (profile.isButton && (serial != 0x200))) { // skip duplicate on wake-up + logger.debug("{}: Trigger event {}", thingName, inputEvent[idx]); + thingHandler.triggerButton(group, idx, inputEvent[idx]); + } + } + lastEventCount[idx] = count; + } + } + + /** + * + * Handles the combined updated of the brightness channel: + * brightness$Switch is the OnOffType (power state) + * brightness&Value is the brightness value + * + * @param profile Device profile, required to select the channel group and name + * @param updates List of updates. updatePower will add brightness$Switch and brightness&Value if changed + * @param id Sensor id from the update + * @param sen Sensor description from the update + * @param s New sensor value + * @param allUpdatesList of updates. This is required, because we need to update both values at the same time + */ + protected void updatePower(ShellyDeviceProfile profile, Map updates, int id, CoIotDescrSen sen, + CoIotSensor s, List allUpdates) { + String group = ""; + String channel = CHANNEL_BRIGHTNESS; + String checkL = ""; // RGBW-white uses 4 different Power, Brightness, VSwitch values + if (profile.isLight || profile.isDimmer) { + if (profile.isBulb || profile.inColor) { + group = CHANNEL_GROUP_LIGHT_CONTROL; + channel = CHANNEL_LIGHT_POWER; + } else if (profile.isDuo) { + group = CHANNEL_GROUP_WHITE_CONTROL; + } else if (profile.isDimmer) { + group = CHANNEL_GROUP_RELAY_CONTROL; + } else if (profile.isRGBW2) { + checkL = String.valueOf(id); // String.valueOf(id - 1); // id is 1-based, L is 0-based + group = CHANNEL_GROUP_LIGHT_CHANNEL + id; + logger.trace("{}: updatePower() for L={}", thingName, checkL); + } + + // We need to update brightness and on/off state at the same time to avoid "flipping brightness slider" in + // the UI + double brightness = -1.0; + double power = -1.0; + for (CoIotSensor update : allUpdates) { + CoIotDescrSen d = fixDescription(sensorMap.get(update.id), blkMap); + if (!checkL.isEmpty() && !d.links.equals(checkL)) { + // continue until we find the correct one + continue; + } + if (d.desc.equalsIgnoreCase("brightness")) { + brightness = update.value; + } else if (d.desc.equalsIgnoreCase("output") || d.desc.equalsIgnoreCase("state")) { + power = update.value; + } + } + if (power != -1) { + updateChannel(updates, group, channel + "$Switch", power == 1 ? OnOffType.ON : OnOffType.OFF); + } + if (brightness != -1) { + updateChannel(updates, group, channel + "$Value", + toQuantityType(power == 1 ? brightness : 0, DIGITS_NONE, Units.PERCENT)); + } + } else if (profile.hasRelays) { + group = profile.numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + id; + updateChannel(updates, group, CHANNEL_OUTPUT, s.value == 1 ? OnOffType.ON : OnOffType.OFF); + } else if (profile.isSensor) { + // Sensor state + if (profile.isDW) { // Door Window has item type Contact + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE, + s.value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + } else { + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE, + s.value == 1 ? OnOffType.ON : OnOffType.OFF); + } + } + } + + /** + * Find index of Input id, which is required to map to channel name + * + * @parm sensorDesc D field from sensor update + * @param sensorId The id from the sensor update + * @return Index of found entry (+1 will be the suffix for the channel name) or null if sensorId is not found + */ + protected int getSensorNumber(String sensorDesc, String sensorId) { + int idx = 0; + for (Map.Entry se : sensorMap.entrySet()) { + CoIotDescrSen sen = se.getValue(); + if (sen.desc.equalsIgnoreCase(sensorDesc)) { + idx++; // iterate from input1..2..n + } + if (sen.id.equalsIgnoreCase(sensorId) && blkMap.containsKey(sen.links)) { + int id = getIdFromBlk(sen); + if (id != -1) { + return id; + } + } + if (sen.id.equalsIgnoreCase(sensorId)) { + return idx; + } + } + logger.debug("{}: sensorId {} not found in sensorMap!", thingName, sensorId); + return -1; + } + + protected int getIdFromBlk(CoIotDescrSen sen) { + int idx = -1; + CoIotDescrBlk blk = blkMap.get(sen.links); + if (blk != null) { + String desc = blk.desc.toLowerCase(); + if (desc.startsWith(SHELLY_CLASS_RELAY) || desc.startsWith(SHELLY_CLASS_ROLLER) + || desc.startsWith(SHELLY_CLASS_LIGHT) || desc.startsWith(SHELLY_CLASS_EMETER)) { + if (desc.contains("_")) { // CoAP v2 + idx = Integer.parseInt(substringAfter(desc, "_")); + } else { // CoAP v1 + if (desc.substring(0, 5).equalsIgnoreCase(SHELLY_CLASS_RELAY)) { + idx = Integer.parseInt(substringAfter(desc, SHELLY_CLASS_RELAY)); + } + if (desc.substring(0, 6).equalsIgnoreCase(SHELLY_CLASS_ROLLER)) { + idx = Integer.parseInt(substringAfter(desc, SHELLY_CLASS_ROLLER)); + } + if (desc.substring(0, SHELLY_CLASS_EMETER.length()).equalsIgnoreCase(SHELLY_CLASS_EMETER)) { + idx = Integer.parseInt(substringAfter(desc, SHELLY_CLASS_EMETER)); + } + } + idx = idx + 1; // make it 1-based (sen.L is 0-based) + } + } + return idx; + } + + /** + * + * Get matching sensorId for updates on "External Temperature" - there might be more than 1 sensor. + * + * @param sensorId sensorId to map into a channel index + * @return Index of the corresponding channel (e.g. 0 build temperature1, 1->temperagture2...) + */ + protected int getExtTempId(String sensorId) { + int idx = 0; + for (Map.Entry se : sensorMap.entrySet()) { + CoIotDescrSen sen = se.getValue(); + if (sen.desc.equalsIgnoreCase("external_temperature") || sen.desc.equalsIgnoreCase("external temperature c") + || (sen.desc.equalsIgnoreCase("extTemp") && !sen.unit.equalsIgnoreCase(SHELLY_TEMP_FAHRENHEIT))) { + idx++; // iterate from temperature1..2..n + } + if (sen.id.equalsIgnoreCase(sensorId)) { + return idx; + } + } + logger.debug("{}: sensorId {} not found in sensorMap!", thingName, sensorId); + return -1; + } + + protected ShellyDeviceProfile getProfile() { + return profile; + } + + public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map blkMap) { + return sen != null ? sen : new CoIotDescrSen(); + } + + public void completeMissingSensorDefinition(Map sensorMap) { + } + + protected void addSensor(Map sensorMap, String key, String json) { + try { + if (!sensorMap.containsKey(key)) { + CoIotDescrSen sen = gson.fromJson(json, CoIotDescrSen.class); + if (sen != null) { + sensorMap.put(key, sen); + } + } + } catch (JsonSyntaxException e) { + // should never happen + logger.trace("Unable to parse sensor definition: {}", json, e); + } + } + + public String getLastWakeup() { + return lastWakeup; + } +} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion1.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion1.java new file mode 100644 index 0000000000..9eb440a88a --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion1.java @@ -0,0 +1,375 @@ +/** + * 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.api1; + +import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; +import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*; +import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; + +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.api1.Shelly1CoapJSonDTO.CoIotDescrBlk; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotDescrSen; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotSensor; +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; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link Shelly1CoIoTVersion1} implements the parsing for CoIoT version 1 + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +public class Shelly1CoIoTVersion1 extends Shelly1CoIoTProtocol implements Shelly1CoIoTInterface { + private final Logger logger = LoggerFactory.getLogger(Shelly1CoIoTVersion1.class); + + public Shelly1CoIoTVersion1(String thingName, ShellyThingInterface thingHandler, Map blkMap, + Map sensorMap) { + super(thingName, thingHandler, blkMap, sensorMap); + } + + @Override + public int getVersion() { + return Shelly1CoapJSonDTO.COIOT_VERSION_1; + } + + /** + * Process CoIoT status update message. If a status update is received, but the device description has not been + * received yet a GET is send to query device description. + * + * @param devId device id included in the status packet + * @param payload CoAP payload (Json format), example: {"G":[[0,112,0]]} + * @param serial Serial for this request. If this the the same as last serial + * the update was already sent and processed so this one gets + * ignored. + */ + @Override + public boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s, + Map updates, ShellyColorUtils col) { + // first check the base implementation + if (super.handleStatusUpdate(sensorUpdates, sen, s, updates, col)) { + // process by the base class + return true; + } + + // Process status information and convert into channel updates + Integer rIndex = Integer.parseInt(sen.links) + 1; + String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL + : CHANNEL_GROUP_RELAY_CONTROL + rIndex; + switch (sen.type.toLowerCase()) { + case "t": // Temperature + + Double value = getDouble(s.value); + switch (sen.desc.toLowerCase()) { + case "temperature": // Sensor Temp + if (getString(getProfile().settings.temperatureUnits) + .equalsIgnoreCase(SHELLY_TEMP_FAHRENHEIT)) { + value = ImperialUnits.FAHRENHEIT.getConverterTo(SIUnits.CELSIUS).convert(getDouble(s.value)) + .doubleValue(); + } + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP, + toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS)); + break; + case "temperature f": // Device Temp -> ignore (we use C only) + break; + case "temperature c": // Device Temp in C + // Device temperature + updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, + toQuantityType(value, DIGITS_NONE, SIUnits.CELSIUS)); + break; + case "external temperature f": // Shelly 1/1PM external temp sensors + // ignore F, we use C only + break; + case "external temperature c": // Shelly 1/1PM external temp sensors + case "external_temperature": + int idx = getExtTempId(sen.id); + if (idx > 0) { + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP + idx, + toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS)); + } else { + logger.debug("{}: Unable to get extSensorId {} from {}/{}", thingName, sen.id, sen.type, + sen.desc); + } + break; + default: + logger.debug("{}: Unknown temperatur type: {}", thingName, sen.desc); + } + break; + case "p": // Power/Watt + // 3EM uses 1-based meter IDs, other 0-based + String mGroup = profile.numMeters == 1 ? CHANNEL_GROUP_METER + : CHANNEL_GROUP_METER + (profile.isEMeter ? sen.links : rIndex); + updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS, + toQuantityType(s.value, DIGITS_WATT, Units.WATT)); + updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp()); + break; + case "s" /* CatchAll */: + switch (sen.desc.toLowerCase()) { + case "overtemp": + if (s.value == 1) { + thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true); + } + break; + case "energy counter 0 [w-min]": + updateChannel(updates, rGroup, CHANNEL_METER_LASTMIN1, + toQuantityType(s.value, DIGITS_WATT, Units.WATT)); + break; + case "energy counter 1 [w-min]": + case "energy counter 2 [w-min]": + // we don't use them + break; + case "energy counter total [w-h]": // 3EM reports W/h + case "energy counter total [w-min]": + Double total = profile.isEMeter ? s.value / 1000 : s.value / 60 / 1000; + updateChannel(updates, rGroup, CHANNEL_METER_TOTALKWH, + toQuantityType(total, DIGITS_KWH, Units.KILOWATT_HOUR)); + break; + case "voltage": + updateChannel(updates, rGroup, CHANNEL_EMETER_VOLTAGE, + toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.VOLT)); + break; + case "current": + updateChannel(updates, rGroup, CHANNEL_EMETER_CURRENT, + toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.AMPERE)); + break; + case "pf": + updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, + toQuantityType(getDecimal(s.value), Units.PERCENT)); + break; + case "position": + // work around: Roller reports 101% instead max 100 + double pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(s.value, SHELLY_MAX_ROLLER_POS)); + updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL, + toQuantityType(SHELLY_MAX_ROLLER_POS - pos, Units.PERCENT)); + updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_POS, + toQuantityType(pos, Units.PERCENT)); + break; + case "input event": // Shelly Button 1 + handleInputEvent(sen, getString(s.valueStr), -1, serial, updates); + break; + case "input event counter": // Shelly Button 1/ix3 + handleInputEvent(sen, "", getInteger((int) s.value), serial, updates); + break; + case "flood": + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD, + s.value == 1 ? OnOffType.ON : OnOffType.OFF); + break; + case "tilt": // DW with FW1.6.5+ //+ + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT, + toQuantityType(s.value, DIGITS_NONE, Units.DEGREE_ANGLE)); + break; + case "vibration": // DW with FW1.6.5+ + if (profile.isMotion) { + // handle as status + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION, + s.value == 1 ? OnOffType.ON : OnOffType.OFF); + } else if (s.value == 1) { + // handle as event + thingHandler.triggerChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE, + EVENT_TYPE_VIBRATION); + } + break; + case "temp": // Shelly Bulb + case "colortemperature": // Shelly Duo + updateChannel(updates, + profile.inColor ? CHANNEL_GROUP_COLOR_CONTROL : CHANNEL_GROUP_WHITE_CONTROL, + CHANNEL_COLOR_TEMP, + ShellyColorUtils.toPercent((int) s.value, profile.minTemp, profile.maxTemp)); + break; + case "sensor state": // Shelly Gas + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE, getStringType(s.valueStr)); + break; + case "alarm state": // Shelly Gas + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE, + getStringType(s.valueStr)); + break; + case "self-test state":// Shelly Gas + updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST, + getStringType(s.valueStr)); + break; + case "concentration":// Shelly Gas + 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)); + break; + default: + // Unknown + return false; + } + break; + + default: + // Unknown type + return false; + } + return true; + } + + /** + * + * Depending on the device type and firmware release there are significant bugs or incosistencies in the CoIoT + * Device Description returned by the discovery request. Shelly is even not following it's own speicifcation. All of + * that has been reported to Shelly and acknowledged. Firmware 1.6 brought significant improvements. However, the + * old mapping stays in to support older firmware releases. + * + * @param sen Sensor description received from device + * @return fixed Sensor description (sen) + */ + @Override + public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map blkMap) { + // Shelly1: reports null descr+type "Switch" -> map to S + // Shelly1PM: reports null descr+type "Overtemp" -> map to O + // Shelly1PM: reports null descr+type "W" -> add description + // Shelly1PM: reports temp senmsors without desc -> add description + // Shelly Dimmer: sensors are reported without descriptions -> map to S + // SHelly Sense: multiple issues: Description should not be lower case, invalid type for Motion and Battery + // Shelly Sense: Battery is reported with Desc "battery", but type "H" instead of "B" + // Shelly Sense: Motion is reported with Desc "battery", but type "H" instead of "B" + // Shelly Bulb: Colors are coded with Type="Red" etc. rather than Type="S" and color as Descr + // Shelly RGBW2 is reporting Brightness, Power, VSwitch for each channel, but all with L=0 + if (sen == null) { + throw new IllegalArgumentException("sen should not be null!"); + } + if (sen.desc == null) { + sen.desc = ""; + } + String desc = sen.desc.toLowerCase(); + + // RGBW2 reports Power_0, Power_1, Power_2, Power_3; same for VSwitch and Brightness, all of them linkted to L:0 + // we break it up to Power with L:0, Power with L:1... + if (desc.contains("_") && (desc.contains("power") || desc.contains("vswitch") || desc.contains("brightness"))) { + String newDesc = substringBefore(sen.desc, "_"); + String newLink = substringAfter(sen.desc, "_"); + sen.desc = newDesc; + sen.links = newLink; + if (!blkMap.containsKey(sen.links)) { + // auto-insert a matching blk entry + CoIotDescrBlk blk = new CoIotDescrBlk(); + CoIotDescrBlk blk0 = blkMap.get("0"); // blk 0 is always there + blk.id = sen.links; + if (blk0 != null) { + blk.desc = blk0.desc + "_" + blk.id; + blkMap.put(blk.id, blk); + } + } + } + + switch (sen.type.toLowerCase()) { + case "w": // old devices/firmware releases use "W", new ones "P" + sen.type = "P"; + sen.desc = "Power"; + break; + case "tc": + sen.type = "T"; + sen.desc = "Temperature C"; + break; + case "tf": + sen.type = "T"; + sen.desc = "Temperature F"; + break; + case "overtemp": + sen.type = "S"; + sen.desc = "Overtemp"; + break; + case "relay0": + case "switch": + case "vswitch": + sen.type = "S"; + sen.desc = "State"; + break; + } + + switch (sen.desc.toLowerCase()) { + case "motion": // fix acc to spec it's T=M + sen.type = "M"; + sen.desc = "Motion"; + break; + case "battery": // fix: type is B not H + sen.type = "B"; + sen.desc = "Battery"; + break; + case "overtemp": + sen.type = "S"; + sen.desc = "Overtemp"; + break; + case "relay0": + case "switch": + case "vswitch": + sen.type = "S"; + sen.desc = "State"; + break; + case "e cnt 0 [w-min]": // 4 Pro + case "e cnt 1 [w-min]": + case "e cnt 2 [w-min]": + case "e cnt total [w-min]": // 4 Pro + sen.desc = sen.desc.toLowerCase().replace("e cnt", "energy counter"); + break; + + } + + if (sen.desc.isEmpty()) { + switch (sen.type.toLowerCase()) { + case "p": + sen.desc = "Power"; + break; + case "T": + sen.desc = "Temperature"; + break; + case "input": + sen.type = "S"; + sen.desc = "Input"; + break; + case "output": + sen.type = "S"; + sen.desc = "Output"; + break; + case "brightness": + sen.type = "S"; + sen.desc = "Brightness"; + break; + case "red": + case "green": + case "blue": + case "white": + case "gain": + case "temp": // Bulb: Color temperature + sen.desc = sen.type; + sen.type = "S"; + break; + case "vswitch": + // it seems that Shelly tends to break their own spec: T is the description and D is no longer + // included -> map D to sen.T and set CatchAll for T + sen.desc = sen.type; + sen.type = "S"; + break; + // Default: set no description + // (there are no T values defined in the CoIoT spec) + case "tostate": + default: + sen.desc = ""; + } + } + return sen; + } +} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion2.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion2.java new file mode 100644 index 0000000000..432ff38783 --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoIoTVersion2.java @@ -0,0 +1,416 @@ +/** + * 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 + */ +/** + * The {@link Shelly1CoIoTVersion1} implements the parsing for CoIoT version 1 + * + * @author Markus Michels - Initial contribution + */ +package org.openhab.binding.shelly.internal.api1; + +import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; +import static org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.*; +import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; + +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.api1.Shelly1CoapJSonDTO.CoIotDescrBlk; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotDescrSen; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotSensor; +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; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link Shelly1CoIoTVersion1} implements the parsing for CoIoT version 2 + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +public class Shelly1CoIoTVersion2 extends Shelly1CoIoTProtocol implements Shelly1CoIoTInterface { + private final Logger logger = LoggerFactory.getLogger(Shelly1CoIoTVersion2.class); + + public Shelly1CoIoTVersion2(String thingName, ShellyThingInterface thingHandler, Map blkMap, + Map sensorMap) { + super(thingName, thingHandler, blkMap, sensorMap); + } + + @Override + public int getVersion() { + return Shelly1CoapJSonDTO.COIOT_VERSION_2; + } + + /** + * Process CoIoT status update message. If a status update is received, but the device description has not been + * received yet a GET is send to query device description. + * + * @param sensorUpdates Complete list of sensor updates + * @param sen The specific sensor update to handle + * @param updates Resulting updates (new updates will be added to input list) + */ + @Override + public boolean handleStatusUpdate(List sensorUpdates, CoIotDescrSen sen, int serial, CoIotSensor s, + Map updates, ShellyColorUtils col) { + // first check the base implementation + if (super.handleStatusUpdate(sensorUpdates, sen, s, updates, col)) { + // process by the base class + return true; + } + + // Process status information and convert into channel updates + // Integer rIndex = Integer.parseInt(sen.links) + 1; + int rIndex = getIdFromBlk(sen); + String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL + : CHANNEL_GROUP_RELAY_CONTROL + rIndex; + String mGroup = profile.numMeters <= 1 ? CHANNEL_GROUP_METER + : CHANNEL_GROUP_METER + (profile.isEMeter ? getIdFromBlk(sen) : rIndex); + + boolean processed = true; + double value = getDouble(s.value); + String reason = ""; + + if (profile.isTRV) { + // Special handling for TRV, because it uses duplicate ID values with different meanings + switch (sen.id) { + case "3101": // current temp + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP, + toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS)); + break; + case "3103": // target temp in C. 4/31, 999=unknown + updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SETTEMP, + toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS)); + break; + case "3116": // S, valveError, 0/1 + if (s.value == 1) { + thingHandler.postEvent(ALARM_TYPE_VALVE_ERROR, false); + } + break; + case "3117": // S, mode, 0-5 (0=disabled) + value = getDouble(s.value).intValue(); + updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE, getDecimal(value)); + updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SCHEDULE, getOnOff(value > 0)); + break; + case "3118": // Valve state + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE, + value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + break; + case "3121": // valvePos, Type=S, Range=0/100; + boolean updated = updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_POSITION, + s.value != -1 ? toQuantityType(getDouble(s.value), 0, Units.PERCENT) : 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; + } + } else { + processed = false; + } + + if (processed) { + return true; + } + + processed = true; + switch (sen.id) { + case "3106": // L, luminosity, lux, U32, -1 + case "3110": // S, luminosityLevel, dark/twilight/bright, "unknown"=unknown + case "3111": // B, battery, 0-100%, unknown -1 + case "3112": // S, charger, 0/1 + case "3115": // S, sensorError, 0/1 + // processed by base handler + break; + + case "6109": // P, overpowerValue, W, U32 + case "9101": + // Relay: S, mode, relay/roller or + // Dimmer: S, mode, color/white + // skip, could check against thing mode... + break; + + case "1101": // relay_0: output, 0/1 + case "1201": // relay_1: output, 0/1 + case "1301": // relay_2: output, 0/1 + case "1401": // relay_3: output, 0/1 + updatePower(profile, updates, rIndex, sen, s, sensorUpdates); + break; + case "1102": // roler_0: S, roller, open/close/stop -> roller state + updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_STATE, getStringType(s.valueStr)); + break; + case "1103": // roller_0: S, rollerPos, 0-100, unknown -1 + int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min((int) value, SHELLY_MAX_ROLLER_POS)); + logger.debug("{}: CoAP update roller position: control={}, position={}", thingName, + SHELLY_MAX_ROLLER_POS - pos, pos); + updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL, + toQuantityType((double) (SHELLY_MAX_ROLLER_POS - pos), Units.PERCENT)); + updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_POS, + toQuantityType((double) pos, Units.PERCENT)); + break; + case "1105": // Gas: S, valve, closed/opened/not_connected/failure/closing/opening/checking or unknown + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE, getStringType(s.valueStr)); + break; + + case "2101": // Input_0: S, input, 0/1 + case "2201": // Input_1: S, input, 0/1 + case "2301": // Input_2: S, input, 0/1 + case "2401": // Input_3: S, input, 0/1 + handleInput(sen, s, rGroup, updates); + break; + case "2102": // Input_0: EV, inputEvent, S/SS/SSS/L + case "2202": // Input_1: EV, inputEvent + case "2302": // Input_2: EV, inputEvent + case "2402": // Input_3: EV, inputEvent + handleInputEvent(sen, getString(s.valueStr), -1, serial, updates); + break; + case "2103": // EVC, inputEventCnt, U16 + case "2203": // EVC, inputEventCnt, U16 + case "2303": // EVC, inputEventCnt, U16 + case "2403": // EVC, inputEventCnt, U16 + handleInputEvent(sen, "", getInteger((int) s.value), serial, updates); + break; + case "3101": // sensor_0: T, extTemp, C, -55/125; unknown 999 + case "3201": // sensor_1: T, extTemp, C, -55/125; unknown 999 + case "3301": // sensor_2: T, extTemp, C, -55/125; unknown 999 + int idx = getExtTempId(sen.id); + if (idx >= 0) { + // 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 + updateChannel(updates, CHANNEL_GROUP_SENSOR, channel, + toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS)); + } else { + logger.debug("{}: Unable to get extSensorId {} from {}/{}", thingName, sen.id, sen.type, sen.desc); + } + break; + case "3104": // T, deviceTemp, Celsius -40/300; 999=unknown + if ("targetTemp".equalsIgnoreCase(sen.desc)) { + + break; // target temp in F-> ignore + } + // sensor_0: T, internalTemp, F, 39/88, unknown 999 + updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, + toQuantityType(value, DIGITS_NONE, SIUnits.CELSIUS)); + break; + case "3102": // sensor_0: T, extTemp, F, -67/257, unknown 999 + case "3202": // sensor_1: T, extTemp, F, -67/257, unknown 999 + case "3302": // sensor_2: T, extTemp, F, -67/257, unknown 999 + case "3105": // T, deviceTemp, Fahrenheit -40/572 + // skip, we use only C + break; + + case "3107": // C, Gas concentration, U16 + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, getDecimal(s.value)); + break; + case "3108": // DW: S, dwIsOpened, 0/1, -1=unknown + if (value != -1) { + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_STATE, + value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED); + } else { + logger.debug("{}: Sensor error reported, check device, battery and installation", thingName); + } + break; + case "3109": // S, tilt, 0-180deg, -1 + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT, + toQuantityType(s.value, DIGITS_NONE, Units.DEGREE_ANGLE)); + break; + case "3113": // S, sensorOp, warmup/normal/fault + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE, getStringType(s.valueStr)); + break; + case "3114": // S, selfTest, not_completed/completed/running/pending + updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST, getStringType(s.valueStr)); + break; + case "3117": // S, extInput, 0/1 + handleInput(sen, s, rGroup, updates); + break; + case "3118": + updateChannel(updates, mGroup, CHANNEL_SENSOR_VOLTAGE, + toQuantityType(getDouble(s.value), 2, Units.VOLT)); + break; + + case "4101": // relay_0/light_0: P, power, W + case "4201": // relay_1/light_1: P, power, W + case "4301": // relay_2/light_2: P, power, W + case "4401": // relay_3/light_3: P, power, W + case "4105": // emeter_0: P, power, W + case "4205": // emeter_1: P, power, W + 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 + updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS, + toQuantityType(s.value, DIGITS_WATT, Units.WATT)); + if (!profile.isRGBW2 && !profile.isRoller) { + // only for regular, not-aggregated meters + updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp()); + } + break; + + case "4103": // relay_0: E, energy, Wmin, U32 + case "4203": // relay_1: E, energy, Wmin, U32 + case "4303": // relay_2: E, energy, Wmin, U32 + case "4403": // relay_3: E, energy, Wmin, U32 + case "4104": // roller_0: E, rollerEnergy, Wmin, U32, -1 + case "4204": // roller_0: E, rollerEnergy, Wmin, U32, -1 + case "4106": // emeter_0: E, energy, Wh, U32 + case "4206": // emeter_1: E, energy, Wh, U32 + case "4306": // emeter_2: E, energy, Wh, U32 + double total = profile.isEMeter ? s.value / 1000 : s.value / 60 / 1000; + updateChannel(updates, mGroup, CHANNEL_METER_TOTALKWH, + toQuantityType(total, DIGITS_KWH, Units.KILOWATT_HOUR)); + break; + + case "4107": // emeter_0: E, energyReturned, Wh, U32, -1 + case "4207": // emeter_1: E, energyReturned, Wh, U32, -1 + case "4307": // emeter_2: E, energyReturned, Wh, U32, -1 + updateChannel(updates, mGroup, CHANNEL_EMETER_TOTALRET, + toQuantityType(getDouble(s.value) / 1000, DIGITS_KWH, Units.KILOWATT_HOUR)); + break; + + case "4108": // emeter_0: V, voltage, 0-265V, U32, -1 + case "4208": // emeter_1: V, voltage, 0-265V, U32, -1 + case "4308": // emeter_2: V, voltage, 0-265V, U32, -1 + updateChannel(updates, mGroup, CHANNEL_EMETER_VOLTAGE, + toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.VOLT)); + break; + + case "4109": // emeter_0: A, current, 0/120A, -1 + case "4209": // emeter_1: A, current, 0/120A, -1 + case "4309": // emeter_2: A, current, 0/120A, -1 + updateChannel(updates, rGroup, CHANNEL_EMETER_CURRENT, + toQuantityType(getDouble(s.value), DIGITS_VOLT, Units.AMPERE)); + break; + + case "4110": // emeter_0: S, powerFactor, 0/1, -1 + case "4210": // emeter_1: S, powerFactor, 0/1, -1 + case "4310": // emeter_2: S, powerFactor, 0/1, -1 + updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value)); + break; + + case "5101": // {"I":5101,"T":"S","D":"brightness","R":"0/100","L":1}, + case "5102": // {"I":5102,"T":"S","D":"gain","R":"0/100","L":1}, + case "5103": // {"I":5103,"T":"S","D":"colorTemp","U":"K","R":"3000/6500","L":1}, + case "5105": // {"I":5105,"T":"S","D":"red","R":"0/255","L":1}, + case "5106": // {"I":5106,"T":"S","D":"green","R":"0/255","L":1}, + case "5107": // {"I":5107,"T":"S","D":"blue","R":"0/255","L":1}, + case "5108": // {"I":5108,"T":"S","D":"white","R":"0/255","L":1}, + // already covered by base handler + break; + + case "6101": // A, overtemp, 0/1 + if (s.value == 1) { + thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true); + } + break; + case "6102": // relay_0: A, overpower, 0/1 + case "6202": // relay_1: A, overpower, 0/1 + case "6302": // relay_2: A, overpower, 0/1 + case "6402": // relay_3: A, overpower, 0/1 + if (s.value == 1) { + thingHandler.postEvent(ALARM_TYPE_OVERPOWER, true); + } + break; + case "6104": // relay_0: A, loadError, 0/1 + case "6204": // relay_1: A, loadError, 0/1 + case "6304": // relay_2: A, loadError, 0/1 + case "6404": // relay_3: A, loadError, 0/1 + if (s.value == 1) { + thingHandler.postEvent(ALARM_TYPE_LOADERR, true); + } + break; + case "6103": // roller_0: A, rollerStopReason, normal/safety_switch/obstacle/overpower + reason = getString(s.valueStr); + updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_STOPR, getStringType(reason)); + if (!reason.isEmpty() && !reason.equalsIgnoreCase(SHELLY_API_STOPR_NORMAL)) { + thingHandler.postEvent("ROLLER_" + reason.toUpperCase(), true); + } + case "6106": // A, flood, 0/1, -1 + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD, + value == 1 ? OnOffType.ON : OnOffType.OFF); + break; + + case "6107": // A, motion, 0/1, -1 + // {"I":6107,"T":"A","D":"motion","R":["0/1","-1"],"L":1}, + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION, + value == 1 ? OnOffType.ON : OnOffType.OFF); + break; + case "3119": // Motion timestamp (timestamp os GMT, not adapted to the adapted timezone) + // {"I":3119,"T":"S","D":"timestamp","U":"s","R":["U32","-1"],"L":1}, + if (s.value != 0) { + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS, + getTimestamp(getString("GMT"), (long) s.value)); + } + break; + case "3120": // motionActive (timestamp os GMT, not adapted to the adapted timezone) + // {"I":3120,"T":"S","D":"motionActive","R":["0/1","-1"],"L":1}, + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_ACT, + getTimestamp("GMT", (long) s.value)); + break; + + case "6108": // A, gas, none/mild/heavy/test or unknown + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE, getStringType(s.valueStr)); + break; + case "6110": // A, vibration, 0/1, -1=unknown + updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION, + s.value == 1 ? OnOffType.ON : OnOffType.OFF); + if (s.value == 1) { + // post event + thingHandler.triggerChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ALARM, EVENT_TYPE_VIBRATION); + } + break; + case "9102": // EV, wakeupEvent, battery/button/periodic/poweron/sensor/ext_power, "unknown"=unknown + if (s.valueArray.size() > 0) { + thingHandler.updateWakeupReason(s.valueArray); + lastWakeup = (String) s.valueArray.get(0); + } + break; + case "9103": // EVC, cfgChanged, U16 + if (lastCfgCount == -1 || lastCfgCount != s.value) { + thingHandler.requestUpdates(1, true); // refresh config + } + lastCfgCount = (int) s.value; + break; + + default: + processed = false; + } + return processed; + } + + @Override + public CoIotDescrSen fixDescription(@Nullable CoIotDescrSen sen, Map blkMap) { + return super.fixDescription(sen, blkMap); + } + + private static final String ID_4101_DESCR = "{ \"I\":4101, \"T\":\"P\", \"D\":\"power\", \"U\": \"W\", \"R\":\"0/3500\", \"L\": 1}"; + private static final String ID_4103_DESCR = "{ \"I\":4103, \"T\":\"E\", \"D\":\"energy\", \"U\": \"Wmin\", \"R\":\"U32\", \"L\": 1}"; + + @Override + public void completeMissingSensorDefinition(Map sensorMap) { + if (profile.isDuo && profile.inColor) { + addSensor(sensorMap, "4101", ID_4101_DESCR); + addSensor(sensorMap, "4103", ID_4103_DESCR); + } + super.completeMissingSensorDefinition(sensorMap); + } +} diff --git a/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java new file mode 100644 index 0000000000..bd0889b16e --- /dev/null +++ b/bundles/org.openhab.binding.shelly/src/main/java/org/openhab/binding/shelly/internal/api1/Shelly1CoapHandler.java @@ -0,0 +1,681 @@ +/** + * 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.api1; + +import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; +import static org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.*; +import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; + +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.coap.CoAP.Code; +import org.eclipse.californium.core.coap.CoAP.ResponseCode; +import org.eclipse.californium.core.coap.CoAP.Type; +import org.eclipse.californium.core.coap.MessageObserverAdapter; +import org.eclipse.californium.core.coap.Option; +import org.eclipse.californium.core.coap.OptionNumberRegistry; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +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.api1.Shelly1CoapJSonDTO.CoIotDescrBlk; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotDescrSen; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotDevDescrTypeAdapter; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotDevDescription; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotGenericSensorList; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotSensor; +import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO.CoIotSensorTypeAdapter; +import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; +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; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link Shelly1CoapHandler} handles the CoIoT/CoAP registration and events. + * + * @author Markus Michels - Initial contribution + */ +@NonNullByDefault +public class Shelly1CoapHandler implements Shelly1CoapListener { + private static final byte[] EMPTY_BYTE = new byte[0]; + + private final Logger logger = LoggerFactory.getLogger(Shelly1CoapHandler.class); + private final ShellyThingInterface thingHandler; + private ShellyThingConfiguration config = new ShellyThingConfiguration(); + private final GsonBuilder gsonBuilder = new GsonBuilder(); + private final Gson gson; + private String thingName; + + private boolean coiotBound = false; + private Shelly1CoIoTInterface coiot; + private int coiotVers = -1; + + private final Shelly1CoapServer coapServer; + private @Nullable CoapClient statusClient; + private Request reqDescription = new Request(Code.GET, Type.CON); + private Request reqStatus = new Request(Code.GET, Type.CON); + private boolean updatesRequested = false; + private int coiotPort = COIOT_PORT; + + private long coiotMessages = 0; + private long coiotErrors = 0; + private int lastSerial = -1; + private String lastPayload = ""; + private Map blkMap = new LinkedHashMap<>(); + private Map sensorMap = new LinkedHashMap<>(); + private ShellyDeviceProfile profile; + private ShellyApiInterface api; + + public Shelly1CoapHandler(ShellyThingInterface thingHandler, Shelly1CoapServer coapServer) { + this.thingHandler = thingHandler; + this.thingName = thingHandler.getThingName(); + this.profile = thingHandler.getProfile(); + this.api = thingHandler.getApi(); + this.coapServer = coapServer; + this.coiot = new Shelly1CoIoTVersion2(thingName, thingHandler, blkMap, sensorMap); // Default: V2 + + gsonBuilder.registerTypeAdapter(CoIotDevDescription.class, new CoIotDevDescrTypeAdapter()); + gsonBuilder.registerTypeAdapter(CoIotGenericSensorList.class, new CoIotSensorTypeAdapter()); + gson = gsonBuilder.create(); + } + + /** + * Initialize CoAP access, send discovery packet and start Status server + * + * @parm thingName Thing name derived from Thing Type/hostname + * @parm config ShellyThingConfiguration + * @thows ShellyApiException + */ + public synchronized void start(String thingName, ShellyThingConfiguration config) throws ShellyApiException { + try { + this.thingName = thingName; + this.config = config; + this.profile = thingHandler.getProfile(); + if (isStarted()) { + logger.trace("{}: CoAP Listener was already started", thingName); + stop(); + } + + logger.debug("{}: Starting CoAP Listener", thingName); + if (!profile.coiotEndpoint.isEmpty() && profile.coiotEndpoint.contains(":")) { + String ps = substringAfter(profile.coiotEndpoint, ":"); + coiotPort = Integer.parseInt(ps); + } + coapServer.start(config.localIp, coiotPort, this); + statusClient = new CoapClient(completeUrl(config.deviceIp, coiotPort, COLOIT_URI_DEVSTATUS)) + .setTimeout((long) SHELLY_API_TIMEOUT_MS).useNONs().setEndpoint(coapServer.getEndpoint()); + @Nullable + Endpoint endpoint = null; + CoapClient client = statusClient; + if (client != null) { + endpoint = client.getEndpoint(); + } + if ((endpoint == null) || !endpoint.isStarted()) { + logger.warn("{}: Unable to initialize CoAP access (network error)", thingName); + throw new ShellyApiException("Network initialization failed"); + } + + discover(); + } catch (SocketException e) { + logger.warn("{}: Unable to initialize CoAP access (socket exception) - {}", thingName, e.getMessage()); + throw new ShellyApiException("Network error", e); + } catch (UnknownHostException e) { + logger.info("{}: CoAP Exception (Unknown Host)", thingName, e); + throw new ShellyApiException("Unknown Host: " + config.deviceIp, e); + } + } + + public boolean isStarted() { + return statusClient != null; + } + + /** + * Process an inbound Response (or mapped Request): decode CoAP options. handle discovery result or status updates + * + * @param response The Response packet + */ + @Override + public void processResponse(@Nullable Response response) { + if (response == null) { + coiotErrors++; + return; // other device instance + } + ResponseCode code = response.getCode(); + if (code != ResponseCode.CONTENT) { + // error handling + logger.debug("{}: Unknown Response Code {} received, payload={}", thingName, code, + response.getPayloadString()); + coiotErrors++; + return; + } + + List