[shelly] Auto-numbering for channel labels & bugfixes (#13066)

* - new device types added
- min firmware set to 1.8.2
- unit for gas concentration fixed (ppm)
- Auto numbering on channel labels for groups with multiple instances
(add sequence suffix)
- API and Thing interfaces defined to restrict access to those classes
- fix on TRV boost update via CoAP
- fix for status.temperature and status.uptime, internalTemp channels
- don’t use meter timestamp if not present (RGBW2)
- low battery indicator for sensor devices fixed
- device detection based on model/type improved
- various messages/translations fixed/improved
- README updated (missing thing types added)

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

* missing properties added

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

* minor changes

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

* markdown fixed

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

* review changes applied

Signed-off-by: Markus Michels <markus7017@gmail.com>

* shelly_de.properties restored from main branch

Signed-off-by: Markus Michels <markus7017@gmail.com>
This commit is contained in:
Markus Michels 2022-07-19 19:37:06 +02:00 committed by GitHub
parent d814640727
commit 4f8c1722a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 817 additions and 280 deletions

View File

@ -45,8 +45,10 @@ Also check out the [Shelly Manager](doc/ShellyManager.md), which
| shellyplugs | Shelly Plug-S | SHPLG-S | | shellyplugs | Shelly Plug-S | SHPLG-S |
| shellyem | Shelly EM with integrated Power Meters | SHEM | | shellyem | Shelly EM with integrated Power Meters | SHEM |
| shellyem3 | Shelly 3EM with 3 integrated Power Meter | SHEM-3 | | shellyem3 | Shelly 3EM with 3 integrated Power Meter | SHEM-3 |
| shellyrgbw2 | Shelly RGB Controller | SHRGBW2 | | shellyrgbw2-color | Shelly RGBW2 Controller in Color Mode | SHRGBW2 |
| shellybulb | Shelly Bulb in Color or White Mode | SHBLB-1 | | shellyrgbw2-white | Shelly RGBW2 Controller in White Mode | SHRGBW2 |
| shellybulb-color | Shelly Bulb in Color Mode | SHBLB-1 |
| shellybulb-white | Shelly Bulb in White Mode | SHBLB-1 |
| shellybulbduo | Shelly Duo White | SHBDUO-1 | | shellybulbduo | Shelly Duo White | SHBDUO-1 |
| shellybulbduo | Shelly Duo White G10 | SHBDUO-1 | | shellybulbduo | Shelly Duo White G10 | SHBDUO-1 |
| shellycolorbulb | Shelly Duo Color G10 | SHCB-1 | | shellycolorbulb | Shelly Duo Color G10 | SHCB-1 |
@ -55,6 +57,7 @@ Also check out the [Shelly Manager](doc/ShellyManager.md), which
| shellyflood | Shelly Flood Sensor | SHWT-1 | | shellyflood | Shelly Flood Sensor | SHWT-1 |
| shellysmoke | Shelly Smoke Sensor | SHSM-1 | | shellysmoke | Shelly Smoke Sensor | SHSM-1 |
| shellymotion | Shelly Motion Sensor | SHMOS-01 | | shellymotion | Shelly Motion Sensor | SHMOS-01 |
| shellymotion2 | Shelly Motion Sensor 2 | SHMOS-02 |
| shellygas | Shelly Gas Sensor | SHGS-1 | | shellygas | Shelly Gas Sensor | SHGS-1 |
| shellydw | Shelly Door/Window | SHDW-1 | | shellydw | Shelly Door/Window | SHDW-1 |
| shellydw2 | Shelly Door/Window 2 | SHDW-2 | | shellydw2 | Shelly Door/Window 2 | SHDW-2 |
@ -742,6 +745,23 @@ Using the Thing configuration option `brightnessAutoOn` you could decide if the
`true`: Brightness will be set and device output is powered = light turns on with the new brightness `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. `false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off.
### Shelly RGBW2 in White Mode (thing-type: shellyrgbw2-color)
|Group |Channel |Type |read-only|Description |
|----------|-------------|---------|---------|-----------------------------------------------------------------------|
|control |power |Switch |r/w |Switch light ON/OFF |
| |input |Switch |yes |State of Input |
| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec |
| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec |
| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
|color | | | |Color settings: only valid in COLOR mode |
| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, but not white |
|meter |currentWatts |Number |yes |Current power consumption in Watts (all channels) |
Please note that the settings of channel group color are only valid in color mode and vice versa for white mode.
The current firmware doesn't support the timestamp report for the meters.
The binding emulates this by using the system time on every update.
### Shelly RGBW2 in White Mode (thing-type: shellyrgbw2-white) ### Shelly RGBW2 in White Mode (thing-type: shellyrgbw2-white)
|Group |Channel |Type |read-only|Description | |Group |Channel |Type |read-only|Description |
@ -845,6 +865,23 @@ You have a Motion controlling your light.
You switch off the light and want to leave the room, but the motion sensor immediately switches light back on. You switch off the light and want to leave the room, but the motion sensor immediately switches light back on.
Using 'sensorSleepTime' you could suppress motion events while leaving the room, e.g. for 5sec and the light doesn's switch on. Using 'sensorSleepTime' you could suppress motion events while leaving the room, e.g. for 5sec and the light doesn's switch on.
### Shelly Motion 2 (thing-type: shellymotion2)
|Group |Channel |Type |read-only|Description |
|----------|---------------|---------|---------|---------------------------------------------------------------------|
|sensors |motion |Switch |yes |ON: Motion was detected |
| |motionTimestamp|DateTime |yes |Time when motion started/was detected |
| |lux |Number |yes |Brightness in Lux |
| |illumination |String |yes |Current illumination: dark/twilight/bright |
| |temperature |Number |yes |Temperature measured by the sensor |
| |vibration |Switch |yes |ON: Vibration detected |
| |charger |Switch |yes |ON: USB charging cable is connected external power supply activated. |
| |motionActive |Switch |yes |ON: Motion detection is currently active |
| |sensorSleepTime|Number |no |Specifies the number of sec the sensor should not report events ]
| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
|battery |batteryLevel |Number |yes |Battery Level in % |
| |lowBattery |Switch |yes |Low battery alert (< 20%) |
### Shelly TRV (thing-type: shellytrv) ### Shelly TRV (thing-type: shellytrv)
Note: You might need to reboot the device to enable the discovery mode for 3 minutes(use the Web UI). Note: You might need to reboot the device to enable the discovery mode for 3 minutes(use the Web UI).

View File

@ -69,9 +69,29 @@ public class ShellyBindingConstants {
public static final String THING_TYPE_SHELLYSENSE_STR = "shellysense"; public static final String THING_TYPE_SHELLYSENSE_STR = "shellysense";
public static final String THING_TYPE_SHELLYTRV_STR = "shellytrv"; public static final String THING_TYPE_SHELLYTRV_STR = "shellytrv";
public static final String THING_TYPE_SHELLYMOTION_STR = "shellymotion"; 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_SHELLYBUTTON1_STR = "shellybutton1";
public static final String THING_TYPE_SHELLYBUTTON2_STR = "shellybutton2"; public static final String THING_TYPE_SHELLYBUTTON2_STR = "shellybutton2";
public static final String THING_TYPE_SHELLYUNI_STR = "shellyuni"; 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_SHELLYPROTECTED_STR = "shellydevice";
public static final String THING_TYPE_SHELLYUNKNOWN_STR = "shellyunknown"; public static final String THING_TYPE_SHELLYUNKNOWN_STR = "shellyunknown";
@ -107,6 +127,24 @@ public class ShellyBindingConstants {
public static final String SHELLYDT_UNI = "SHUNI-1"; public static final String SHELLYDT_UNI = "SHUNI-1";
public static final String SHELLYDT_TRV = "SHTRV-01"; 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 // 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_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_SHELLY1L = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1L_STR);
@ -186,6 +224,7 @@ public class ShellyBindingConstants {
public static final String PROPERTY_DEV_NAME = "deviceName"; public static final String PROPERTY_DEV_NAME = "deviceName";
public static final String PROPERTY_DEV_TYPE = "deviceType"; public static final String PROPERTY_DEV_TYPE = "deviceType";
public static final String PROPERTY_DEV_MODE = "deviceMode"; public static final String PROPERTY_DEV_MODE = "deviceMode";
public static final String PROPERTY_DEV_GEN = "deviceGeneration";
public static final String PROPERTY_HWREV = "deviceHwRev"; public static final String PROPERTY_HWREV = "deviceHwRev";
public static final String PROPERTY_HWBATCH = "deviceHwBatch"; public static final String PROPERTY_HWBATCH = "deviceHwBatch";
public static final String PROPERTY_UPDATE_PERIOD = "devUpdatePeriod"; public static final String PROPERTY_UPDATE_PERIOD = "devUpdatePeriod";
@ -340,7 +379,7 @@ public class ShellyBindingConstants {
public static final String CHANNEL_BUTTON_TRIGGER2 = CHANNEL_BUTTON_TRIGGER + "2"; public static final String CHANNEL_BUTTON_TRIGGER2 = CHANNEL_BUTTON_TRIGGER + "2";
public static final String SERVICE_TYPE = "_http._tcp.local."; public static final String SERVICE_TYPE = "_http._tcp.local.";
public static final String SHELLY_API_MIN_FWVERSION = "v1.5.7";// v1.5.7+ public static final String SHELLY_API_MIN_FWVERSION = "v1.8.2";
public static final String SHELLY_API_MIN_FWCOIOT = "v1.6";// v1.6.0+ public static final String SHELLY_API_MIN_FWCOIOT = "v1.6";// v1.6.0+
public static final String SHELLY_API_FWCOIOT2 = "v1.8";// CoAP 2 with FW 1.8+ public static final String SHELLY_API_FWCOIOT2 = "v1.8";// CoAP 2 with FW 1.8+
public static final String SHELLY_API_FW_110 = "v1.10"; // FW 1.10 or newer detected, activates some add feature public static final String SHELLY_API_FW_110 = "v1.10"; // FW 1.10 or newer detected, activates some add feature

View File

@ -17,7 +17,6 @@ import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -29,6 +28,8 @@ import org.openhab.binding.shelly.internal.handler.ShellyLightHandler;
import org.openhab.binding.shelly.internal.handler.ShellyManagerInterface; import org.openhab.binding.shelly.internal.handler.ShellyManagerInterface;
import org.openhab.binding.shelly.internal.handler.ShellyProtectedHandler; import org.openhab.binding.shelly.internal.handler.ShellyProtectedHandler;
import org.openhab.binding.shelly.internal.handler.ShellyRelayHandler; import org.openhab.binding.shelly.internal.handler.ShellyRelayHandler;
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider; import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.util.ShellyUtils; import org.openhab.binding.shelly.internal.util.ShellyUtils;
import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.net.http.HttpClientFactory;
@ -63,7 +64,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
private final ShellyTranslationProvider messages; private final ShellyTranslationProvider messages;
private final ShellyCoapServer coapServer; private final ShellyCoapServer coapServer;
private final Map<String, ShellyBaseHandler> deviceListeners = new ConcurrentHashMap<>(); private final ShellyThingTable thingTable;
private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration(); private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
private String localIP = ""; private String localIP = "";
private int httpPort = -1; private int httpPort = -1;
@ -77,8 +78,9 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
*/ */
@Activate @Activate
public ShellyHandlerFactory(@Reference NetworkAddressService networkAddressService, public ShellyHandlerFactory(@Reference NetworkAddressService networkAddressService,
@Reference ShellyTranslationProvider translationProvider, @Reference HttpClientFactory httpClientFactory, @Reference ShellyTranslationProvider translationProvider, @Reference ShellyThingTable thingTable,
ComponentContext componentContext, Map<String, Object> configProperties) { @Reference HttpClientFactory httpClientFactory, ComponentContext componentContext,
Map<String, Object> configProperties) {
logger.debug("Activate Shelly HandlerFactory"); logger.debug("Activate Shelly HandlerFactory");
super.activate(componentContext); super.activate(componentContext);
messages = translationProvider; messages = translationProvider;
@ -100,6 +102,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
} }
logger.debug("Using OH HTTP port {}", httpPort); logger.debug("Using OH HTTP port {}", httpPort);
this.thingTable = thingTable;
this.coapServer = new ShellyCoapServer(); this.coapServer = new ShellyCoapServer();
// Promote Shelly Manager usage // Promote Shelly Manager usage
@ -137,8 +140,8 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
if (handler != null) { if (handler != null) {
String uid = thing.getUID().getAsString(); String uid = thing.getUID().getAsString();
deviceListeners.put(uid, handler); thingTable.addThing(uid, handler);
logger.debug("Thing handler for uid {} added, total things = {}", uid, deviceListeners.size()); logger.debug("Thing handler for uid {} added, total things = {}", uid, thingTable.size());
return handler; return handler;
} }
@ -147,7 +150,11 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
} }
public Map<String, ShellyManagerInterface> getThingHandlers() { public Map<String, ShellyManagerInterface> getThingHandlers() {
return new HashMap<>(deviceListeners); Map<String, ShellyManagerInterface> table = new HashMap<>();
for (Map.Entry<String, ShellyThingInterface> entry : thingTable.getTable().entrySet()) {
table.put(entry.getKey(), (ShellyManagerInterface) entry.getValue());
}
return table;
} }
/** /**
@ -157,7 +164,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
protected synchronized void removeHandler(@NonNull ThingHandler thingHandler) { protected synchronized void removeHandler(@NonNull ThingHandler thingHandler) {
if (thingHandler instanceof ShellyBaseHandler) { if (thingHandler instanceof ShellyBaseHandler) {
String uid = thingHandler.getThing().getUID().getAsString(); String uid = thingHandler.getThing().getUID().getAsString();
deviceListeners.remove(uid); thingTable.removeThing(uid);
} }
} }
@ -172,8 +179,8 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
public void onEvent(String ipAddress, String deviceName, String componentIndex, String eventType, public void onEvent(String ipAddress, String deviceName, String componentIndex, String eventType,
Map<String, String> parameters) { Map<String, String> parameters) {
logger.trace("{}: Dispatch event to thing handler", deviceName); logger.trace("{}: Dispatch event to thing handler", deviceName);
for (Map.Entry<String, ShellyBaseHandler> listener : deviceListeners.entrySet()) { for (Map.Entry<String, ShellyThingInterface> listener : thingTable.getTable().entrySet()) {
ShellyBaseHandler thingHandler = listener.getValue(); ShellyBaseHandler thingHandler = (ShellyBaseHandler) listener.getValue();
if (thingHandler.onEvent(ipAddress, deviceName, componentIndex, eventType, parameters)) { if (thingHandler.onEvent(ipAddress, deviceName, componentIndex, eventType, parameters)) {
// event processed // event processed
return; return;

View File

@ -0,0 +1,124 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.shelly.internal.api;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyOtaCheckResult;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDevice;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsLogin;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLight;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
/**
* The {@link ShellyApiInterface} Defines device API
*
* @author Markus Michels - Initial contribution
*/
@NonNullByDefault
public interface ShellyApiInterface {
public boolean isInitialized();
public void setConfig(String thingName, ShellyThingConfiguration config);
public ShellySettingsDevice getDeviceInfo() throws ShellyApiException;
public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException;
public ShellySettingsStatus getStatus() throws ShellyApiException;
public void setLedStatus(String ledName, Boolean value) throws ShellyApiException;
public void setSleepTime(int value) throws ShellyApiException;
public ShellyStatusRelay getRelayStatus(Integer relayIndex) throws ShellyApiException;
public void setRelayTurn(int id, String turnMode) throws ShellyApiException;
public ShellyControlRoller getRollerStatus(int rollerIndex) throws ShellyApiException;
public void setRollerTurn(int relayIndex, String turnMode) throws ShellyApiException;
public void setRollerPos(int relayIndex, int position) throws ShellyApiException;
public void setTimer(int index, String timerName, int value) throws ShellyApiException;
public ShellyStatusSensor getSensorStatus() throws ShellyApiException;
public ShellyStatusLight getLightStatus() throws ShellyApiException;
public ShellyShortLightStatus getLightStatus(int index) throws ShellyApiException;
public void setLightMode(String mode) throws ShellyApiException;
public void setLightParm(int lightIndex, String parm, String value) throws ShellyApiException;
public void setLightParms(int lightIndex, Map<String, String> parameters) throws ShellyApiException;
public ShellyShortLightStatus setLightTurn(int id, String turnMode) throws ShellyApiException;
public void setBrightness(int id, int brightness, boolean autoOn) throws ShellyApiException;
// Valve
public void setValveMode(int id, boolean auto) throws ShellyApiException;
public void setValveTemperature(int valveId, int value) throws ShellyApiException;
public void setValveProfile(int valveId, int value) throws ShellyApiException;
public void setValvePosition(int valveId, double value) throws ShellyApiException;
public void setValveBoostTime(int valveId, int value) throws ShellyApiException;
public void startValveBoost(int valveId, int value) throws ShellyApiException;
public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException;
public ShellySettingsLogin getLoginSettings() throws ShellyApiException;
public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException;
public String setWiFiRecovery(boolean enable) throws ShellyApiException;
public String deviceReboot() throws ShellyApiException;
public String setDebug(boolean enabled) throws ShellyApiException;
public String getDebugLog(String id) throws ShellyApiException;
public String setCloud(boolean enabled) throws ShellyApiException;
public String setApRoaming(boolean enable) throws ShellyApiException;
public String factoryReset() throws ShellyApiException;
public String resetStaCache() throws ShellyApiException;
public int getTimeoutsRecovered();
public int getTimeoutErrors();
public String getCoIoTDescription() throws ShellyApiException;
public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException;
public void setActionURLs() throws ShellyApiException;
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException;
}

View File

@ -60,6 +60,7 @@ public class ShellyApiJsonDTO {
public static final String SHELLY_WAKEUPT_PERIODIC = "PERIODIC"; // periodic wakeup 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_BUTTON = "BUTTON"; // button pressed
public static final String SHELLY_WAKEUPT_POWERON = "POWERON"; // device powered up 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 public static final String SHELLY_WAKEUPT_UNKNOWN = "UNKNOWN"; // other event
// //
@ -94,6 +95,12 @@ public class ShellyApiJsonDTO {
public static final String SHELLY_EVENT_ROLLER_OPEN = "roller_open"; public static final String SHELLY_EVENT_ROLLER_OPEN = "roller_open";
public static final String SHELLY_EVENT_ROLLER_CLOSE = "roller_close"; 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_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 // Sensors
public static final String SHELLY_EVENT_SENSORREPORT = "report"; public static final String SHELLY_EVENT_SENSORREPORT = "report";
@ -116,6 +123,8 @@ public class ShellyApiJsonDTO {
// //
// API values // 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_MOMENTARY = "momentary";
public static final String SHELLY_BTNT_MOM_ON_RELEASE = "momentary_on_release"; 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_ONE_BUTTON = "one_button";
@ -128,6 +137,8 @@ public class ShellyApiJsonDTO {
public static final String SHELLY_STATE_STOP = "stop"; public static final String SHELLY_STATE_STOP = "stop";
public static final String SHELLY_INP_MODE_OPENCLOSE = "openclose"; 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_OBSTMODE_DISABLED = "disabled";
public static final String SHELLY_SAFETYM_WHILEOPENING = "while_opening"; public static final String SHELLY_SAFETYM_WHILEOPENING = "while_opening";
@ -359,8 +370,6 @@ public class ShellyApiJsonDTO {
public static class ShellySettingsRelay { public static class ShellySettingsRelay {
public String name; public String name;
public Boolean ison;
public Boolean overpower;
@SerializedName("default_state") @SerializedName("default_state")
public String defaultState; // Accepted values: off, on, last, switch public String defaultState; // Accepted values: off, on, last, switch
@SerializedName("btn_type") @SerializedName("btn_type")
@ -393,6 +402,16 @@ public class ShellyApiJsonDTO {
public String pushLongUrl; // to access when roller stopped public String pushLongUrl; // to access when roller stopped
@SerializedName("shortpush_url") @SerializedName("shortpush_url")
public String pushShortUrl; // to access when roller stopped 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 static class ShellySettingsDimmer {
@ -591,18 +610,19 @@ public class ShellyApiJsonDTO {
public Boolean calibrated; public Boolean calibrated;
public ArrayList<ShellySettingsRelay> relays; public ArrayList<ShellySettingsRelay> relays;
public Double voltage; // AC voltage for Shelly 2.5 public ArrayList<ShellySettingsRoller> rollers;
@SerializedName("supply_voltage")
public Long supplyVoltage; // Shelly 1PM/1L: 0=110V, 1=220V
public ArrayList<ShellySettingsDimmer> dimmers; public ArrayList<ShellySettingsDimmer> dimmers;
public ArrayList<ShellySettingsRgbwLight> lights; public ArrayList<ShellySettingsRgbwLight> lights;
public ArrayList<ShellySettingsEMeter> emeters; public ArrayList<ShellySettingsEMeter> emeters;
public ArrayList<ShellySettingsInput> inputs; // ix3 public ArrayList<ShellySettingsInput> inputs; // ix3
public ArrayList<ShellyThermnostat> thermostats; // TRV public ArrayList<ShellyThermnostat> thermostats; // TRV
public Double voltage; // AC voltage for Shelly 2.5
@SerializedName("supply_voltage")
public Long supplyVoltage; // Shelly 1PM/1L: 0=110V, 1=220V
@SerializedName("temperature_units") @SerializedName("temperature_units")
public String temperatureUnits; // Either'C'or'F' public String temperatureUnits = "C"; // Either'C'or'F'
@SerializedName("led_status_disable") @SerializedName("led_status_disable")
public Boolean ledStatusDisable; // PlugS only Disable LED indication for network public Boolean ledStatusDisable; // PlugS only Disable LED indication for network
@ -641,7 +661,7 @@ public class ShellyApiJsonDTO {
// Roller with FW 1.9.2+ // Roller with FW 1.9.2+
@SerializedName("favorites_enabled") @SerializedName("favorites_enabled")
public Boolean favoritesEnabled; public Boolean favoritesEnabled = false;
public ArrayList<ShellyFavPos> favorites; public ArrayList<ShellyFavPos> favorites;
// Motion // Motion
@ -686,7 +706,7 @@ public class ShellyApiJsonDTO {
public ShellyStatusMqtt mqtt; public ShellyStatusMqtt mqtt;
public String time; public String time;
public Integer serial; public Integer serial = -1;
@SerializedName("has_update") @SerializedName("has_update")
public Boolean hasUpdate; public Boolean hasUpdate;
public String mac; public String mac;
@ -709,7 +729,7 @@ public class ShellyApiJsonDTO {
// Internal device temp // Internal device temp
public ShellyStatusSensor.ShellySensorTmp tmp; // Shelly 1PM public ShellyStatusSensor.ShellySensorTmp tmp; // Shelly 1PM
public Double temperature; // Shelly 2.5 public Double temperature = SHELLY_API_INVTEMP; // Shelly 2.5
public Boolean overtemperature; public Boolean overtemperature;
// Shelly Dimmer only // Shelly Dimmer only
@ -838,16 +858,16 @@ public class ShellyApiJsonDTO {
public Integer currentPos; // current position 0..100, 100=open public Integer currentPos; // current position 0..100, 100=open
} }
public class ShellyOtaCheckResult { public static class ShellyOtaCheckResult {
public String status; public String status;
} }
public class ShellyApRoaming { public static class ShellyApRoaming {
public Boolean enabled; public Boolean enabled;
public Integer threshold; public Integer threshold;
} }
public class ShellySensorSleepMode { public static class ShellySensorSleepMode {
public Integer period; public Integer period;
public String unit; public String unit;
} }

View File

@ -58,6 +58,7 @@ public class ShellyDeviceProfile {
public ShellySettingsStatus status = new ShellySettingsStatus(); public ShellySettingsStatus status = new ShellySettingsStatus();
public String hostname = ""; public String hostname = "";
public String name = "";
public String mode = ""; public String mode = "";
public boolean discoverable = true; public boolean discoverable = true;
public boolean auth = false; public boolean auth = false;
@ -118,6 +119,7 @@ public class ShellyDeviceProfile {
settings = gs; // only update when no exception settings = gs; // only update when no exception
// General settings // General settings
name = getString(settings.name);
deviceType = getString(settings.device.type); deviceType = getString(settings.device.type);
mac = getString(settings.device.mac); mac = getString(settings.device.mac);
hostname = settings.device.hostname != null && !settings.device.hostname.isEmpty() hostname = settings.device.hostname != null && !settings.device.hostname.isEmpty()
@ -251,6 +253,10 @@ public class ShellyDeviceProfile {
return numRelays == 1 ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_STATUS + idx; return numRelays == 1 ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_STATUS + idx;
} }
public String getMeterGroup(int idx) {
return numMeters > 1 ? CHANNEL_GROUP_METER + (idx + 1) : CHANNEL_GROUP_METER;
}
public String getInputGroup(int i) { public String getInputGroup(int i) {
int idx = i + 1; // group names are 1-based int idx = i + 1; // group names are 1-based
if (isRGBW2) { if (isRGBW2) {
@ -275,7 +281,7 @@ public class ShellyDeviceProfile {
// Roller has 2 relays, but it will be mapped to 1 roller with 2 inputs // Roller has 2 relays, but it will be mapped to 1 roller with 2 inputs
return String.valueOf(idx); return String.valueOf(idx);
} else if (hasRelays) { } else if (hasRelays) {
return (numRelays) == 1 && (numInputs >= 2) ? String.valueOf(idx) : ""; return numRelays == 1 && numInputs >= 2 ? String.valueOf(idx) : "";
} }
return ""; return "";
} }
@ -288,7 +294,7 @@ public class ShellyDeviceProfile {
String btnType = ""; String btnType = "";
if (isButton) { if (isButton) {
return true; return true;
} else if (isIX3 && (settings.inputs != null) && (idx < settings.inputs.size())) { } else if (isIX3 && settings.inputs != null && idx < settings.inputs.size()) {
ShellySettingsInput input = settings.inputs.get(idx); ShellySettingsInput input = settings.inputs.get(idx);
btnType = getString(input.btnType); btnType = getString(input.btnType);
} else if (isDimmer) { } else if (isDimmer) {
@ -315,7 +321,6 @@ public class ShellyDeviceProfile {
btnType = light.btnType; btnType = light.btnType;
} }
logger.trace("{}: Checking for trigger, button-type[{}] is {}", thingName, idx, btnType);
return btnType.equalsIgnoreCase(SHELLY_BTNT_MOMENTARY) || btnType.equalsIgnoreCase(SHELLY_BTNT_MOM_ON_RELEASE) return btnType.equalsIgnoreCase(SHELLY_BTNT_MOMENTARY) || btnType.equalsIgnoreCase(SHELLY_BTNT_MOM_ON_RELEASE)
|| btnType.equalsIgnoreCase(SHELLY_BTNT_ONE_BUTTON) || btnType.equalsIgnoreCase(SHELLY_BTNT_TWO_BUTTON) || btnType.equalsIgnoreCase(SHELLY_BTNT_ONE_BUTTON) || btnType.equalsIgnoreCase(SHELLY_BTNT_TWO_BUTTON)
|| btnType.equalsIgnoreCase(SHELLY_BTNT_DETACHED); || btnType.equalsIgnoreCase(SHELLY_BTNT_DETACHED);

View File

@ -60,7 +60,7 @@ import com.google.gson.JsonSyntaxException;
* @author Markus Michels - Initial contribution * @author Markus Michels - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class ShellyHttpApi { public class ShellyHttpApi implements ShellyApiInterface {
public static final String HTTP_HEADER_AUTH = "Authorization"; public static final String HTTP_HEADER_AUTH = "Authorization";
public static final String HTTP_AUTH_TYPE_BASIC = "Basic"; 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_JSON = "application/json; charset=UTF-8";
@ -82,19 +82,23 @@ public class ShellyHttpApi {
profile.initFromThingType(thingName); profile.initFromThingType(thingName);
} }
@Override
public void setConfig(String thingName, ShellyThingConfiguration config) { public void setConfig(String thingName, ShellyThingConfiguration config) {
this.thingName = thingName; this.thingName = thingName;
this.config = config; this.config = config;
} }
public ShellySettingsDevice getDevInfo() throws ShellyApiException { @Override
public ShellySettingsDevice getDeviceInfo() throws ShellyApiException {
return callApi(SHELLY_URL_DEVINFO, ShellySettingsDevice.class); return callApi(SHELLY_URL_DEVINFO, ShellySettingsDevice.class);
} }
@Override
public String setDebug(boolean enabled) throws ShellyApiException { public String setDebug(boolean enabled) throws ShellyApiException {
return callApi(SHELLY_URL_SETTINGS + "?debug_enable=" + Boolean.valueOf(enabled), String.class); return callApi(SHELLY_URL_SETTINGS + "?debug_enable=" + Boolean.valueOf(enabled), String.class);
} }
@Override
public String getDebugLog(String id) throws ShellyApiException { public String getDebugLog(String id) throws ShellyApiException {
return callApi("/debug/" + id, String.class); return callApi("/debug/" + id, String.class);
} }
@ -106,6 +110,7 @@ public class ShellyHttpApi {
* @return Initialized ShellyDeviceProfile * @return Initialized ShellyDeviceProfile
* @throws ShellyApiException * @throws ShellyApiException
*/ */
@Override
public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException { public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException {
String json = request(SHELLY_URL_SETTINGS); String json = request(SHELLY_URL_SETTINGS);
if (json.contains("\"type\":\"SHDM-")) { if (json.contains("\"type\":\"SHDM-")) {
@ -131,6 +136,7 @@ public class ShellyHttpApi {
return profile; return profile;
} }
@Override
public boolean isInitialized() { public boolean isInitialized() {
return profile.initialized; return profile.initialized;
} }
@ -141,6 +147,7 @@ public class ShellyHttpApi {
* @return Device settings/status as ShellySettingsStatus object * @return Device settings/status as ShellySettingsStatus object
* @throws ShellyApiException * @throws ShellyApiException
*/ */
@Override
public ShellySettingsStatus getStatus() throws ShellyApiException { public ShellySettingsStatus getStatus() throws ShellyApiException {
String json = ""; String json = "";
try { try {
@ -156,42 +163,55 @@ public class ShellyHttpApi {
} }
} }
@Override
public ShellyStatusRelay getRelayStatus(Integer relayIndex) throws ShellyApiException { public ShellyStatusRelay getRelayStatus(Integer relayIndex) throws ShellyApiException {
return callApi(SHELLY_URL_STATUS_RELEAY + "/" + relayIndex.toString(), ShellyStatusRelay.class); return callApi(SHELLY_URL_STATUS_RELEAY + "/" + relayIndex.toString(), ShellyStatusRelay.class);
} }
public ShellyShortLightStatus setRelayTurn(Integer id, String turnMode) throws ShellyApiException { @Override
public void setRelayTurn(int id, String turnMode) throws ShellyApiException {
callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(),
ShellyShortLightStatus.class);
}
@Override
public ShellyShortLightStatus setLightTurn(int id, String turnMode) throws ShellyApiException {
return callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(), return callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(),
ShellyShortLightStatus.class); ShellyShortLightStatus.class);
} }
public void setBrightness(Integer id, Integer brightness, boolean autoOn) throws ShellyApiException { @Override
public void setBrightness(int id, int brightness, boolean autoOn) throws ShellyApiException {
String turn = autoOn ? SHELLY_LIGHT_TURN + "=" + SHELLY_API_ON + "&" : ""; String turn = autoOn ? SHELLY_LIGHT_TURN + "=" + SHELLY_API_ON + "&" : "";
request(getControlUriPrefix(id) + "?" + turn + "brightness=" + brightness.toString()); request(getControlUriPrefix(id) + "?" + turn + "brightness=" + brightness);
} }
public ShellyControlRoller getRollerStatus(Integer rollerIndex) throws ShellyApiException { @Override
String uri = SHELLY_URL_CONTROL_ROLLER + "/" + rollerIndex.toString() + "/pos"; public ShellyControlRoller getRollerStatus(int idx) throws ShellyApiException {
String uri = SHELLY_URL_CONTROL_ROLLER + "/" + idx + "/pos";
return callApi(uri, ShellyControlRoller.class); return callApi(uri, ShellyControlRoller.class);
} }
public void setRollerTurn(Integer relayIndex, String turnMode) throws ShellyApiException { @Override
request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?go=" + turnMode); public void setRollerTurn(int idx, String turnMode) throws ShellyApiException {
request(SHELLY_URL_CONTROL_ROLLER + "/" + idx + "?go=" + turnMode);
} }
public void setRollerPos(Integer relayIndex, Integer position) throws ShellyApiException { @Override
request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?go=to_pos&roller_pos=" public void setRollerPos(int id, int position) throws ShellyApiException {
+ position.toString()); request(SHELLY_URL_CONTROL_ROLLER + "/" + id + "?go=to_pos&roller_pos=" + position);
} }
public void setRollerTimer(Integer relayIndex, Integer timer) throws ShellyApiException { public void setRollerTimer(int idx, int timer) throws ShellyApiException {
request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?timer=" + timer.toString()); request(SHELLY_URL_CONTROL_ROLLER + "/" + idx + "?timer=" + timer);
} }
public ShellyShortLightStatus getLightStatus(Integer index) throws ShellyApiException { @Override
public ShellyShortLightStatus getLightStatus(int index) throws ShellyApiException {
return callApi(getControlUriPrefix(index), ShellyShortLightStatus.class); return callApi(getControlUriPrefix(index), ShellyShortLightStatus.class);
} }
@Override
public ShellyStatusSensor getSensorStatus() throws ShellyApiException { public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
ShellyStatusSensor status = callApi(SHELLY_URL_STATUS, ShellyStatusSensor.class); ShellyStatusSensor status = callApi(SHELLY_URL_STATUS, ShellyStatusSensor.class);
if (profile.isSense) { if (profile.isSense) {
@ -210,6 +230,7 @@ public class ShellyHttpApi {
return status; return status;
} }
@Override
public void setTimer(int index, String timerName, int value) throws ShellyApiException { public void setTimer(int index, String timerName, int value) throws ShellyApiException {
String type = SHELLY_CLASS_RELAY; String type = SHELLY_CLASS_RELAY;
if (profile.isRoller) { if (profile.isRoller) {
@ -221,14 +242,17 @@ public class ShellyHttpApi {
request(uri); request(uri);
} }
@Override
public void setSleepTime(int value) throws ShellyApiException { public void setSleepTime(int value) throws ShellyApiException {
request(SHELLY_URL_SETTINGS + "?sleep_time=" + value); request(SHELLY_URL_SETTINGS + "?sleep_time=" + value);
} }
public void setTemperature(int valveId, int value) throws ShellyApiException { @Override
public void setValveTemperature(int valveId, int value) throws ShellyApiException {
request("/thermostat/" + valveId + "?target_t_enabled=1&target_t=" + value); request("/thermostat/" + valveId + "?target_t_enabled=1&target_t=" + value);
} }
@Override
public void setValveMode(int valveId, boolean auto) throws ShellyApiException { public void setValveMode(int valveId, boolean auto) throws ShellyApiException {
String uri = "/settings/thermostat/" + valveId + "?target_t_enabled=" + (auto ? "1" : "0"); String uri = "/settings/thermostat/" + valveId + "?target_t_enabled=" + (auto ? "1" : "0");
if (auto) { if (auto) {
@ -237,24 +261,29 @@ public class ShellyHttpApi {
request(uri); // percentage to open the valve request(uri); // percentage to open the valve
} }
public void setProfile(int valveId, int value) throws ShellyApiException { @Override
public void setValveProfile(int valveId, int value) throws ShellyApiException {
String uri = "/settings/thermostat/" + valveId + "?"; String uri = "/settings/thermostat/" + valveId + "?";
request(uri + (value == 0 ? "schedule=0" : "schedule=1&schedule_profile=" + value)); request(uri + (value == 0 ? "schedule=0" : "schedule=1&schedule_profile=" + value));
} }
@Override
public void setValvePosition(int valveId, double value) throws ShellyApiException { public void setValvePosition(int valveId, double value) throws ShellyApiException {
request("/thermostat/" + valveId + "?pos=" + value); // percentage to open the valve request("/thermostat/" + valveId + "?pos=" + value); // percentage to open the valve
} }
public void setBoostTime(int valveId, int value) throws ShellyApiException { @Override
public void setValveBoostTime(int valveId, int value) throws ShellyApiException {
request("/settings/thermostat/" + valveId + "?boost_minutes=" + value); request("/settings/thermostat/" + valveId + "?boost_minutes=" + value);
} }
public void startBoost(int valveId, int value) throws ShellyApiException { @Override
public void startValveBoost(int valveId, int value) throws ShellyApiException {
int minutes = value != -1 ? value : getInteger(profile.settings.thermostats.get(0).boostMinutes); int minutes = value != -1 ? value : getInteger(profile.settings.thermostats.get(0).boostMinutes);
request("/thermostat/" + valveId + "?boost_minutes=" + minutes); request("/thermostat/" + valveId + "?boost_minutes=" + minutes);
} }
@Override
public void setLedStatus(String ledName, Boolean value) throws ShellyApiException { public void setLedStatus(String ledName, Boolean value) throws ShellyApiException {
request(SHELLY_URL_SETTINGS + "?" + ledName + "=" + (value ? SHELLY_API_TRUE : SHELLY_API_FALSE)); request(SHELLY_URL_SETTINGS + "?" + ledName + "=" + (value ? SHELLY_API_TRUE : SHELLY_API_FALSE));
} }
@ -263,6 +292,7 @@ public class ShellyHttpApi {
return callApi(SHELLY_URL_SETTINGS_LIGHT, ShellySettingsLight.class); return callApi(SHELLY_URL_SETTINGS_LIGHT, ShellySettingsLight.class);
} }
@Override
public ShellyStatusLight getLightStatus() throws ShellyApiException { public ShellyStatusLight getLightStatus() throws ShellyApiException {
return callApi(SHELLY_URL_STATUS, ShellyStatusLight.class); return callApi(SHELLY_URL_STATUS, ShellyStatusLight.class);
} }
@ -271,15 +301,18 @@ public class ShellyHttpApi {
request(SHELLY_URL_SETTINGS + "?" + parm + "=" + value); request(SHELLY_URL_SETTINGS + "?" + parm + "=" + value);
} }
@Override
public ShellySettingsLogin getLoginSettings() throws ShellyApiException { public ShellySettingsLogin getLoginSettings() throws ShellyApiException {
return callApi(SHELLY_URL_SETTINGS + "/login", ShellySettingsLogin.class); return callApi(SHELLY_URL_SETTINGS + "/login", ShellySettingsLogin.class);
} }
@Override
public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException { public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException {
return callApi(SHELLY_URL_SETTINGS + "/login?enabled=yes&username=" + urlEncode(user) + "&password=" return callApi(SHELLY_URL_SETTINGS + "/login?enabled=yes&username=" + urlEncode(user) + "&password="
+ urlEncode(password), ShellySettingsLogin.class); + urlEncode(password), ShellySettingsLogin.class);
} }
@Override
public String getCoIoTDescription() throws ShellyApiException { public String getCoIoTDescription() throws ShellyApiException {
try { try {
return callApi("/cit/d", String.class); return callApi("/cit/d", String.class);
@ -291,31 +324,38 @@ public class ShellyHttpApi {
} }
} }
@Override
public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException { public ShellySettingsLogin setCoIoTPeer(String peer) throws ShellyApiException {
return callApi(SHELLY_URL_SETTINGS + "?coiot_enable=true&coiot_peer=" + peer, ShellySettingsLogin.class); return callApi(SHELLY_URL_SETTINGS + "?coiot_enable=true&coiot_peer=" + peer, ShellySettingsLogin.class);
} }
@Override
public String deviceReboot() throws ShellyApiException { public String deviceReboot() throws ShellyApiException {
return callApi(SHELLY_URL_RESTART, String.class); return callApi(SHELLY_URL_RESTART, String.class);
} }
@Override
public String factoryReset() throws ShellyApiException { public String factoryReset() throws ShellyApiException {
return callApi(SHELLY_URL_SETTINGS + "?reset=true", String.class); return callApi(SHELLY_URL_SETTINGS + "?reset=true", String.class);
} }
@Override
public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException { public ShellyOtaCheckResult checkForUpdate() throws ShellyApiException {
return callApi("/ota/check", ShellyOtaCheckResult.class); // nw FW 1.10+: trigger update check return callApi("/ota/check", ShellyOtaCheckResult.class); // nw FW 1.10+: trigger update check
} }
@Override
public String setWiFiRecovery(boolean enable) throws ShellyApiException { public String setWiFiRecovery(boolean enable) throws ShellyApiException {
return callApi(SHELLY_URL_SETTINGS + "?wifirecovery_reboot_enabled=" + (enable ? "true" : "false"), return callApi(SHELLY_URL_SETTINGS + "?wifirecovery_reboot_enabled=" + (enable ? "true" : "false"),
String.class); // FW 1.10+: Enable auto-restart on WiFi problems 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 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); 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 public String resetStaCache() throws ShellyApiException { // FW 1.10+: Reset cached STA/AP list and to a rescan
return callApi("/sta_cache_reset", String.class); return callApi("/sta_cache_reset", String.class);
} }
@ -324,6 +364,7 @@ public class ShellyHttpApi {
return callApi("/ota?" + uri, ShellySettingsUpdate.class); return callApi("/ota?" + uri, ShellySettingsUpdate.class);
} }
@Override
public String setCloud(boolean enabled) throws ShellyApiException { public String setCloud(boolean enabled) throws ShellyApiException {
return callApi("/settings/cloud/?enabled=" + (enabled ? "1" : "0"), String.class); return callApi("/settings/cloud/?enabled=" + (enabled ? "1" : "0"), String.class);
} }
@ -334,6 +375,7 @@ public class ShellyHttpApi {
* @param mode * @param mode
* @throws ShellyApiException * @throws ShellyApiException
*/ */
@Override
public void setLightMode(String mode) throws ShellyApiException { public void setLightMode(String mode) throws ShellyApiException {
if (!mode.isEmpty() && !profile.mode.equals(mode)) { if (!mode.isEmpty() && !profile.mode.equals(mode)) {
setLightSetting(SHELLY_API_MODE, mode); setLightSetting(SHELLY_API_MODE, mode);
@ -350,13 +392,15 @@ public class ShellyHttpApi {
* @param value The value * @param value The value
* @throws ShellyApiException * @throws ShellyApiException
*/ */
public void setLightParm(Integer lightIndex, String parm, String value) throws ShellyApiException { @Override
public void setLightParm(int lightIndex, String parm, String value) throws ShellyApiException {
// Bulb, RGW2: /<color mode>/<light id>?parm?value // Bulb, RGW2: /<color mode>/<light id>?parm?value
// Dimmer: /light/<light id>?parm=value // Dimmer: /light/<light id>?parm=value
request(getControlUriPrefix(lightIndex) + "?" + parm + "=" + value); request(getControlUriPrefix(lightIndex) + "?" + parm + "=" + value);
} }
public void setLightParms(Integer lightIndex, Map<String, String> parameters) throws ShellyApiException { @Override
public void setLightParms(int lightIndex, Map<String, String> parameters) throws ShellyApiException {
String url = getControlUriPrefix(lightIndex) + "?"; String url = getControlUriPrefix(lightIndex) + "?";
int i = 0; int i = 0;
for (String key : parameters.keySet()) { for (String key : parameters.keySet()) {
@ -405,6 +449,7 @@ public class ShellyHttpApi {
* @throws ShellyApiException * @throws ShellyApiException
* @throws IllegalArgumentException * @throws IllegalArgumentException
*/ */
@Override
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException { public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException {
String type = ""; String type = "";
if (profile.irCodes.containsKey(keyCode)) { if (profile.irCodes.containsKey(keyCode)) {
@ -437,6 +482,7 @@ public class ShellyHttpApi {
* @param ShellyApiException * @param ShellyApiException
* @throws ShellyApiException * @throws ShellyApiException
*/ */
@Override
public void setActionURLs() throws ShellyApiException { public void setActionURLs() throws ShellyApiException {
setRelayEvents(); setRelayEvents();
setDimmerEvents(); setDimmerEvents();
@ -659,10 +705,12 @@ public class ShellyHttpApi {
return uri; return uri;
} }
@Override
public int getTimeoutErrors() { public int getTimeoutErrors() {
return timeoutErrors; return timeoutErrors;
} }
@Override
public int getTimeoutsRecovered() { public int getTimeoutsRecovered() {
return timeoutsRecovered; return timeoutsRecovered;
} }

View File

@ -21,13 +21,13 @@ import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
@ -49,9 +49,9 @@ import com.google.gson.JsonSyntaxException;
public class ShellyCoIoTProtocol { public class ShellyCoIoTProtocol {
private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTProtocol.class); private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTProtocol.class);
protected final String thingName; protected final String thingName;
protected final ShellyBaseHandler thingHandler; protected final ShellyThingInterface thingHandler;
protected final ShellyDeviceProfile profile; protected final ShellyDeviceProfile profile;
protected final ShellyHttpApi api; protected final ShellyApiInterface api;
protected final Map<String, CoIotDescrBlk> blkMap; protected final Map<String, CoIotDescrBlk> blkMap;
protected final Map<String, CoIotDescrSen> sensorMap; protected final Map<String, CoIotDescrSen> sensorMap;
private final Gson gson = new GsonBuilder().create(); private final Gson gson = new GsonBuilder().create();
@ -63,7 +63,7 @@ public class ShellyCoIoTProtocol {
protected String[] inputEvent = { "", "", "", "", "", "", "", "" }; protected String[] inputEvent = { "", "", "", "", "", "", "", "" };
protected String lastWakeup = ""; protected String lastWakeup = "";
public ShellyCoIoTProtocol(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap, public ShellyCoIoTProtocol(String thingName, ShellyThingInterface thingHandler, Map<String, CoIotDescrBlk> blkMap,
Map<String, CoIotDescrSen> sensorMap) { Map<String, CoIotDescrSen> sensorMap) {
this.thingName = thingName; this.thingName = thingName;
this.thingHandler = thingHandler; this.thingHandler = thingHandler;

View File

@ -24,8 +24,8 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.unit.ImperialUnits; import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory;
public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface { public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface {
private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion1.class); private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion1.class);
public ShellyCoIoTVersion1(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap, public ShellyCoIoTVersion1(String thingName, ShellyThingInterface thingHandler, Map<String, CoIotDescrBlk> blkMap,
Map<String, CoIotDescrSen> sensorMap) { Map<String, CoIotDescrSen> sensorMap) {
super(thingName, thingHandler, blkMap, sensorMap); super(thingName, thingHandler, blkMap, sensorMap);
} }
@ -207,7 +207,8 @@ public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCo
getStringType(s.valueStr)); getStringType(s.valueStr));
break; break;
case "concentration":// Shelly Gas case "concentration":// Shelly Gas
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, getDecimal(s.value)); updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM,
toQuantityType(getDouble(s.value), DIGITS_NONE, Units.PARTS_PER_MILLION));
break; break;
case "sensorerror": case "sensorerror":
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, getStringType(s.valueStr)); updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, getStringType(s.valueStr));

View File

@ -29,8 +29,8 @@ import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
@ -49,7 +49,7 @@ import org.slf4j.LoggerFactory;
public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface { public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface {
private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion2.class); private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion2.class);
public ShellyCoIoTVersion2(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap, public ShellyCoIoTVersion2(String thingName, ShellyThingInterface thingHandler, Map<String, CoIotDescrBlk> blkMap,
Map<String, CoIotDescrSen> sensorMap) { Map<String, CoIotDescrSen> sensorMap) {
super(thingName, thingHandler, blkMap, sensorMap); super(thingName, thingHandler, blkMap, sensorMap);
} }
@ -114,12 +114,13 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED); value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
break; break;
case "3121": // valvePos, Type=S, Range=0/100; case "3121": // valvePos, Type=S, Range=0/100;
updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_POSITION, boolean updated = updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_POSITION,
s.value != -1 ? toQuantityType(getDouble(s.value), 0, Units.PERCENT) : UnDefType.UNDEF); s.value != -1 ? toQuantityType(getDouble(s.value), 0, Units.PERCENT) : UnDefType.UNDEF);
break; if (updated && s.value >= 0 && s.value != thingHandler.getChannelDouble(CHANNEL_GROUP_CONTROL,
case "3122": // boostMinutes CHANNEL_CONTROL_POSITION)) {
updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_BTIMER, logger.debug("{}: Valve position changed, force update", thingName);
s.value != -1 ? toQuantityType(s.value, DIGITS_NONE, Units.MINUTE) : UnDefType.UNDEF); thingHandler.requestUpdates(1, false);
}
break; break;
default: default:
processed = false; processed = false;
@ -197,9 +198,8 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
// H&T, Fllod, DW only have 1 channel, 1/1PM with Addon have up to to 3 sensors // 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; String channel = profile.isSensor ? CHANNEL_SENSOR_TEMP : CHANNEL_SENSOR_TEMP + idx;
// Some devices report values = -999 or 99 during fw update // Some devices report values = -999 or 99 during fw update
boolean valid = value > -50 && value < 90;
updateChannel(updates, CHANNEL_GROUP_SENSOR, channel, updateChannel(updates, CHANNEL_GROUP_SENSOR, channel,
valid ? toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS) : UnDefType.UNDEF); toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS));
} else { } else {
logger.debug("{}: Unable to get extSensorId {} from {}/{}", thingName, sen.id, sen.type, sen.desc); logger.debug("{}: Unable to get extSensorId {} from {}/{}", thingName, sen.id, sen.type, sen.desc);
} }
@ -258,7 +258,6 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
case "4305": // emeter_2: P, power, W case "4305": // emeter_2: P, power, W
case "4102": // roller_0: P, rollerPower, W, 0-2300, unknown -1 case "4102": // roller_0: P, rollerPower, W, 0-2300, unknown -1
case "4202": // roller_1: P, rollerPower, W, 0-2300, unknown -1 case "4202": // roller_1: P, rollerPower, W, 0-2300, unknown -1
logger.debug("{}: Updating {}:currentWatts with {}", thingName, mGroup, s.value);
updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS, updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS,
toQuantityType(s.value, DIGITS_WATT, Units.WATT)); toQuantityType(s.value, DIGITS_WATT, Units.WATT));
if (!profile.isRGBW2 && !profile.isRoller) { if (!profile.isRGBW2 && !profile.isRoller) {
@ -386,7 +385,7 @@ public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCo
} }
break; break;
case "9103": // EVC, cfgChanged, U16 case "9103": // EVC, cfgChanged, U16
if ((lastCfgCount != -1) && (lastCfgCount != s.value)) { if (lastCfgCount == -1 || lastCfgCount != s.value) {
thingHandler.requestUpdates(1, true); // refresh config thingHandler.requestUpdates(1, true); // refresh config
} }
lastCfgCount = (int) s.value; lastCfgCount = (int) s.value;

View File

@ -36,8 +36,8 @@ import org.eclipse.californium.core.network.Endpoint;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.api.ShellyApiException; import org.openhab.binding.shelly.internal.api.ShellyApiException;
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDevDescrTypeAdapter; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDevDescrTypeAdapter;
@ -46,8 +46,8 @@ import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotGenericSe
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensorTypeAdapter; import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensorTypeAdapter;
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils; import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
import org.openhab.core.library.unit.Units; import org.openhab.core.library.unit.Units;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -67,7 +67,7 @@ public class ShellyCoapHandler implements ShellyCoapListener {
private static final byte[] EMPTY_BYTE = new byte[0]; private static final byte[] EMPTY_BYTE = new byte[0];
private final Logger logger = LoggerFactory.getLogger(ShellyCoapHandler.class); private final Logger logger = LoggerFactory.getLogger(ShellyCoapHandler.class);
private final ShellyBaseHandler thingHandler; private final ShellyThingInterface thingHandler;
private ShellyThingConfiguration config = new ShellyThingConfiguration(); private ShellyThingConfiguration config = new ShellyThingConfiguration();
private final GsonBuilder gsonBuilder = new GsonBuilder(); private final GsonBuilder gsonBuilder = new GsonBuilder();
private final Gson gson; private final Gson gson;
@ -91,11 +91,11 @@ public class ShellyCoapHandler implements ShellyCoapListener {
private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>(); private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
private Map<String, CoIotDescrSen> sensorMap = new LinkedHashMap<>(); private Map<String, CoIotDescrSen> sensorMap = new LinkedHashMap<>();
private ShellyDeviceProfile profile; private ShellyDeviceProfile profile;
private ShellyHttpApi api; private ShellyApiInterface api;
public ShellyCoapHandler(ShellyBaseHandler thingHandler, ShellyCoapServer coapServer) { public ShellyCoapHandler(ShellyThingInterface thingHandler, ShellyCoapServer coapServer) {
this.thingHandler = thingHandler; this.thingHandler = thingHandler;
this.thingName = thingHandler.thingName; this.thingName = thingHandler.getThingName();
this.profile = thingHandler.getProfile(); this.profile = thingHandler.getProfile();
this.api = thingHandler.getApi(); this.api = thingHandler.getApi();
this.coapServer = coapServer; this.coapServer = coapServer;
@ -506,15 +506,11 @@ public class ShellyCoapHandler implements ShellyCoapListener {
String meter = CHANNEL_GROUP_METER + i; String meter = CHANNEL_GROUP_METER + i;
double current = thingHandler.getChannelDouble(meter, CHANNEL_METER_CURRENTWATTS); double current = thingHandler.getChannelDouble(meter, CHANNEL_METER_CURRENTWATTS);
double total = thingHandler.getChannelDouble(meter, CHANNEL_METER_TOTALKWH); double total = thingHandler.getChannelDouble(meter, CHANNEL_METER_TOTALKWH);
logger.debug("{}: {}#{}={}, total={}", thingName, meter, CHANNEL_METER_CURRENTWATTS, current,
totalCurrent);
totalCurrent += current >= 0 ? current : 0; totalCurrent += current >= 0 ? current : 0;
totalKWH += total >= 0 ? total : 0; totalKWH += total >= 0 ? total : 0;
updateMeter |= current >= 0 | total >= 0; updateMeter |= current >= 0 | total >= 0;
i++; i++;
} }
logger.debug("{}: totalCurrent={}, totalKWH={}, update={}", thingName, totalCurrent, totalKWH,
updateMeter);
if (updateMeter) { if (updateMeter) {
thingHandler.updateChannel(CHANNEL_GROUP_METER, CHANNEL_METER_CURRENTWATTS, thingHandler.updateChannel(CHANNEL_GROUP_METER, CHANNEL_METER_CURRENTWATTS,
toQuantityType(totalCurrent, DIGITS_WATT, Units.WATT)); toQuantityType(totalCurrent, DIGITS_WATT, Units.WATT));
@ -525,10 +521,7 @@ public class ShellyCoapHandler implements ShellyCoapListener {
// Old firmware release are lacking various status values, which are not updated using CoIoT. // Old firmware release are lacking various status values, which are not updated using CoIoT.
// In this case we keep a refresh so it gets polled using REST. Beginning with Firmware 1.6 most // In this case we keep a refresh so it gets polled using REST. Beginning with Firmware 1.6 most
// of the values are available // of the values are available
if ((!thingHandler.autoCoIoT && (thingHandler.scheduledUpdates < 1)) thingHandler.triggerUpdateFromCoap();
|| (thingHandler.autoCoIoT && !profile.isLight && !profile.hasBattery)) {
thingHandler.requestUpdates(1, false);
}
} else { } else {
if (failed == sensorUpdates.size()) { if (failed == sensorUpdates.size()) {
logger.debug("{}: Device description problem detected, re-discover", thingName); logger.debug("{}: Device description problem detected, re-discover", thingName);

View File

@ -38,6 +38,7 @@ public class ShellyBindingConfiguration {
public String defaultUserId = "admin"; // default for http basic user id public String defaultUserId = "admin"; // default for http basic user id
public String defaultPassword = "admin"; // default for http basic auth password public String defaultPassword = "admin"; // default for http basic auth password
public String localIP = ""; // default:use OH network config public String localIP = ""; // default:use OH network config
public int httpPort = -1;
public boolean autoCoIoT = true; public boolean autoCoIoT = true;
public void updateFromProperties(Map<String, Object> properties) { public void updateFromProperties(Map<String, Object> properties) {

View File

@ -41,4 +41,5 @@ public class ShellyThingConfiguration {
public String localIp = ""; // local ip addresses used to create callback url public String localIp = ""; // local ip addresses used to create callback url
public String localPort = "8080"; public String localPort = "8080";
public String serviceName = "";
} }

View File

@ -13,7 +13,7 @@
package org.openhab.binding.shelly.internal.discovery; package org.openhab.binding.shelly.internal.discovery;
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import static org.openhab.binding.shelly.internal.util.ShellyUtils.substringBeforeLast;
import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID;
import java.io.IOException; import java.io.IOException;
@ -100,7 +100,7 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
@Nullable @Nullable
@Override @Override
public DiscoveryResult createResult(final ServiceInfo service) { public DiscoveryResult createResult(final ServiceInfo service) {
String name = service.getName().toLowerCase(); // Duao: Name starts with" Shelly" rather than "shelly" String name = service.getName().toLowerCase(); // Shelly Duo: Name starts with" Shelly" rather than "shelly"
if (!name.startsWith("shelly")) { if (!name.startsWith("shelly")) {
return null; return null;
} }
@ -111,7 +111,7 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
String model = "unknown"; String model = "unknown";
String deviceName = ""; String deviceName = "";
ThingUID thingUID = null; ThingUID thingUID = null;
ShellyDeviceProfile profile = null; ShellyDeviceProfile profile;
Map<String, Object> properties = new TreeMap<>(); Map<String, Object> properties = new TreeMap<>();
name = service.getName().toLowerCase(); name = service.getName().toLowerCase();
@ -144,8 +144,8 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
profile = api.getDeviceProfile(thingType); profile = api.getDeviceProfile(thingType);
logger.debug("{}: Shelly settings : {}", name, profile.settingsJson); logger.debug("{}: Shelly settings : {}", name, profile.settingsJson);
deviceName = getString(profile.settings.name); deviceName = profile.name;
model = getString(profile.settings.device.type); model = profile.deviceType;
mode = profile.mode; mode = profile.mode;
properties = ShellyBaseHandler.fillDeviceProperties(profile); properties = ShellyBaseHandler.fillDeviceProperties(profile);
@ -174,6 +174,7 @@ public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
addProperty(properties, PROPERTY_SERVICE_NAME, name); addProperty(properties, PROPERTY_SERVICE_NAME, name);
addProperty(properties, PROPERTY_DEV_NAME, deviceName); addProperty(properties, PROPERTY_DEV_NAME, deviceName);
addProperty(properties, PROPERTY_DEV_TYPE, thingType); addProperty(properties, PROPERTY_DEV_TYPE, thingType);
addProperty(properties, PROPERTY_DEV_GEN, "1");
addProperty(properties, PROPERTY_DEV_MODE, mode); addProperty(properties, PROPERTY_DEV_MODE, mode);
logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString()); logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString());

View File

@ -84,6 +84,7 @@ public class ShellyThingCreator {
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON1_STR, THING_TYPE_SHELLYBUTTON1_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON1_STR, THING_TYPE_SHELLYBUTTON1_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON2_STR, THING_TYPE_SHELLYBUTTON2_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON2_STR, THING_TYPE_SHELLYBUTTON2_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYUNI_STR, THING_TYPE_SHELLYUNI_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYUNI_STR, THING_TYPE_SHELLYUNI_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYMOTION2_STR, THING_TYPE_SHELLYMOTION_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPROTECTED_STR, THING_TYPE_SHELLYPROTECTED_STR); THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPROTECTED_STR, THING_TYPE_SHELLYPROTECTED_STR);
} }
@ -144,11 +145,19 @@ public class ShellyThingCreator {
// Check general mapping // Check general mapping
if (!deviceType.isEmpty()) { if (!deviceType.isEmpty()) {
String res = THING_TYPE_MAPPING.get(deviceType); String res = THING_TYPE_MAPPING.get(deviceType); // by device type
if (res != null) {
return res;
}
String dt = mode.equals(SHELLY_MODE_RELAY) || mode.equals(SHELLY_MODE_ROLLER) ? deviceType + "-" + mode
: deviceType;
res = THING_TYPE_MAPPING.get(dt); // <DT>-relay / <DT>-roller
if (res != null) { if (res != null) {
return res; return res;
} }
} }
String res = THING_TYPE_MAPPING.get(type); String res = THING_TYPE_MAPPING.get(type);
if (res != null) { if (res != null) {
return res; return res;

View File

@ -30,6 +30,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.shelly.internal.api.ShellyApiException; import org.openhab.binding.shelly.internal.api.ShellyApiException;
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyInputState; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyInputState;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyOtaCheckResult; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyOtaCheckResult;
@ -74,14 +75,15 @@ import org.slf4j.LoggerFactory;
* @author Markus Michels - Initial contribution * @author Markus Michels - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceListener, ShellyManagerInterface { public class ShellyBaseHandler extends BaseThingHandler
implements ShellyDeviceListener, ShellyManagerInterface, ShellyThingInterface {
protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class); protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class);
protected final ShellyChannelDefinitions channelDefinitions; protected final ShellyChannelDefinitions channelDefinitions;
public String thingName = ""; public String thingName = "";
public String thingType = ""; public String thingType = "";
protected final ShellyHttpApi api; protected final ShellyApiInterface api;
protected ShellyBindingConfiguration bindingConfig; protected ShellyBindingConfiguration bindingConfig;
protected ShellyThingConfiguration config = new ShellyThingConfiguration(); protected ShellyThingConfiguration config = new ShellyThingConfiguration();
protected ShellyDeviceProfile profile = new ShellyDeviceProfile(); // init empty profile to avoid NPE protected ShellyDeviceProfile profile = new ShellyDeviceProfile(); // init empty profile to avoid NPE
@ -126,10 +128,12 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
int httpPort, final HttpClient httpClient) { int httpPort, final HttpClient httpClient) {
super(thing); super(thing);
this.thingName = getString(thing.getLabel());
this.messages = translationProvider; this.messages = translationProvider;
this.cache = new ShellyChannelCache(this); this.cache = new ShellyChannelCache(this);
this.channelDefinitions = new ShellyChannelDefinitions(messages); this.channelDefinitions = new ShellyChannelDefinitions(messages);
this.bindingConfig = bindingConfig; this.bindingConfig = bindingConfig;
this.config = getConfigAs(ShellyThingConfiguration.class);
this.localIP = localIP; this.localIP = localIP;
this.localPort = String.valueOf(httpPort); this.localPort = String.valueOf(httpPort);
@ -138,6 +142,16 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
coap = new ShellyCoapHandler(this, coapServer); coap = new ShellyCoapHandler(this, coapServer);
} }
@Override
public boolean checkRepresentation(String key) {
return key.equalsIgnoreCase(getUID()) || key.equalsIgnoreCase(config.deviceIp)
|| key.equalsIgnoreCase(config.serviceName) || key.equalsIgnoreCase(thing.getUID().getAsString());
}
public String getUID() {
return getThing().getUID().getAsString();
}
/** /**
* Schedule asynchronous Thing initialization, register thing to event dispatcher * Schedule asynchronous Thing initialization, register thing to event dispatcher
*/ */
@ -177,6 +191,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
}, 2, TimeUnit.SECONDS); }, 2, TimeUnit.SECONDS);
} }
@Override
public ShellyThingConfiguration getThingConfig() {
return config;
}
/** /**
* This routine is called every time the Thing configuration has been changed * This routine is called every time the Thing configuration has been changed
*/ */
@ -222,11 +241,14 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
} }
// Initialize API access, exceptions will be catched by initialize() // Initialize API access, exceptions will be catched by initialize()
ShellySettingsDevice devInfo = api.getDevInfo(); ShellySettingsDevice devInfo = api.getDeviceInfo();
if (devInfo.auth && config.userId.isEmpty()) { if (getBool(devInfo.auth) && config.userId.isEmpty()) {
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials"); setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-no-credentials");
return false; return false;
} }
if (config.serviceName.isEmpty()) {
config.serviceName = getString(profile.hostname).toLowerCase();
}
ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType); ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType);
if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) { if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) {
@ -244,24 +266,12 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
tmpPrf.coiotEndpoint = devInfo.coiot; tmpPrf.coiotEndpoint = devInfo.coiot;
} }
tmpPrf.auth = devInfo.auth; // missing in /settings tmpPrf.auth = devInfo.auth; // missing in /settings
logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {}", thingName,
tmpPrf.hostname, tmpPrf.deviceType, tmpPrf.hwRev, tmpPrf.hwBatchId, tmpPrf.fwVersion, tmpPrf.fwDate);
logger.debug("{}: Shelly settings info for {}: {}", thingName, tmpPrf.hostname, tmpPrf.settingsJson);
logger.debug("{}: Device "
+ "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{})"
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}"
+ ",alwaysOn:{}, ,updatePeriod:{}sec", thingName, tmpPrf.hasRelays, tmpPrf.numRelays, tmpPrf.isRoller,
tmpPrf.numRollers, tmpPrf.isDimmer, tmpPrf.numMeters, tmpPrf.isEMeter, tmpPrf.isSensor, tmpPrf.isDW,
tmpPrf.hasBattery, tmpPrf.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "",
tmpPrf.isSense, tmpPrf.isMotion, tmpPrf.isLight, profile.isBulb, tmpPrf.isDuo, tmpPrf.isRGBW2,
tmpPrf.inColor, tmpPrf.alwaysOn, tmpPrf.updatePeriod);
// update thing properties
tmpPrf.status = api.getStatus(); tmpPrf.status = api.getStatus();
tmpPrf.updateFromStatus(tmpPrf.status); tmpPrf.updateFromStatus(tmpPrf.status);
updateProperties(tmpPrf, tmpPrf.status);
showThingConfig(tmpPrf);
checkVersion(tmpPrf, tmpPrf.status); checkVersion(tmpPrf, tmpPrf.status);
if (config.eventsCoIoT && (tmpPrf.settings.coiot != null) && (tmpPrf.settings.coiot.enabled != null)) { if (config.eventsCoIoT && (tmpPrf.settings.coiot != null) && (tmpPrf.settings.coiot.enabled != null)) {
String devpeer = getString(tmpPrf.settings.coiot.peer); String devpeer = getString(tmpPrf.settings.coiot.peer);
String ourpeer = config.localIp + ":" + ShellyCoapJSonDTO.COIOT_PORT; String ourpeer = config.localIp + ":" + ShellyCoapJSonDTO.COIOT_PORT;
@ -298,11 +308,28 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
logger.debug("{}: Thing successfully initialized.", thingName); logger.debug("{}: Thing successfully initialized.", thingName);
profile = tmpPrf; profile = tmpPrf;
setThingOnline(); // if API call was successful the thing must be online
updateProperties(tmpPrf, tmpPrf.status);
setThingOnline(); // if API call was successful the thing must be online
return true; // success return true; // success
} }
private void showThingConfig(ShellyDeviceProfile profile) {
logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {}", thingName,
profile.hostname, profile.deviceType, profile.hwRev, profile.hwBatchId, profile.fwVersion,
profile.fwDate);
logger.debug("{}: Shelly settings info for {}: {}", thingName, profile.hostname, profile.settingsJson);
logger.debug("{}: Device "
+ "hasRelays:{} (numRelays={}),isRoller:{} (numRoller={}),isDimmer:{},numMeter={},isEMeter:{})"
+ ",isSensor:{},isDS:{},hasBattery:{}{},isSense:{},isMotion:{},isLight:{},isBulb:{},isDuo:{},isRGBW2:{},inColor:{}"
+ ",alwaysOn:{}, updatePeriod:{}sec", thingName, profile.hasRelays, profile.numRelays, profile.isRoller,
profile.numRollers, profile.isDimmer, profile.numMeters, profile.isEMeter, profile.isSensor,
profile.isDW, profile.hasBattery,
profile.hasBattery ? " (low battery threshold=" + config.lowBattery + "%)" : "", profile.isSense,
profile.isMotion, profile.isLight, profile.isBulb, profile.isDuo, profile.isRGBW2, profile.inColor,
profile.alwaysOn, profile.updatePeriod);
}
/** /**
* Handle Channel Commands * Handle Channel Commands
*/ */
@ -356,7 +383,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
logger.warn("{}: Invalid profile Id {} requested", thingName, profile); logger.warn("{}: Invalid profile Id {} requested", thingName, profile);
break; break;
} }
api.setProfile(0, profile); api.setValveProfile(0, profile);
break; break;
case CHANNEL_CONTROL_MODE: case CHANNEL_CONTROL_MODE:
logger.debug("{}: Set mode to {}", thingName, command); logger.debug("{}: Set mode to {}", thingName, command);
@ -364,7 +391,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
break; break;
case CHANNEL_CONTROL_SETTEMP: case CHANNEL_CONTROL_SETTEMP:
logger.debug("{}: Set temperature to {}", thingName, command); logger.debug("{}: Set temperature to {}", thingName, command);
api.setTemperature(0, (int) getNumber(command)); api.setValveTemperature(0, (int) getNumber(command));
break; break;
case CHANNEL_CONTROL_POSITION: case CHANNEL_CONTROL_POSITION:
logger.debug("{}: Set position to {}", thingName, command); logger.debug("{}: Set position to {}", thingName, command);
@ -372,11 +399,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
break; break;
case CHANNEL_CONTROL_BCONTROL: case CHANNEL_CONTROL_BCONTROL:
logger.debug("{}: Set boost mode to {}", thingName, command); logger.debug("{}: Set boost mode to {}", thingName, command);
api.startBoost(0, command == OnOffType.ON ? -1 : 0); api.startValveBoost(0, command == OnOffType.ON ? -1 : 0);
break; break;
case CHANNEL_CONTROL_BTIMER: case CHANNEL_CONTROL_BTIMER:
logger.debug("{}: Set boost timer to {}", thingName, command); logger.debug("{}: Set boost timer to {}", thingName, command);
api.setBoostTime(0, (int) getNumber(command)); api.setValveBoostTime(0, (int) getNumber(command));
break; break;
default: default:
@ -386,7 +413,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
restartWatchdog(); restartWatchdog();
if (update && !autoCoIoT && !isUpdateScheduled()) { if (update && !autoCoIoT && !isUpdateScheduled()) {
logger.debug("{}: Command process, request status update", thingName); logger.debug("{}: Command processed, request status update", thingName);
requestUpdates(1, false); requestUpdates(1, false);
} }
} catch (ShellyApiException e) { } catch (ShellyApiException e) {
@ -467,9 +494,6 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
// All channels must be created after the first cycle // All channels must be created after the first cycle
channelsCreated = true; channelsCreated = true;
// Restart watchdog when status update was successful (no exception)
restartWatchdog();
} }
} catch (ShellyApiException e) { } catch (ShellyApiException e) {
// http call failed: go offline except for battery devices, which might be in // http call failed: go offline except for battery devices, which might be in
@ -526,6 +550,10 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
@Override @Override
public void setThingOnline() { public void setThingOnline() {
if (stopping) {
logger.debug("{}: Thing should go ONLINE, but handler is shutting down, ignore!", thingName);
return;
}
if (!isThingOnline()) { if (!isThingOnline()) {
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
@ -538,14 +566,22 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
@Override @Override
public void setThingOffline(ThingStatusDetail detail, String messageKey) { public void setThingOffline(ThingStatusDetail detail, String messageKey) {
String message = messages.get(messageKey);
if (stopping) {
logger.debug("{}: Thing should go OFFLINE with status {}, but handler is shutting down -> ignore",
thingName, message);
return;
}
if (!isThingOffline()) { if (!isThingOffline()) {
updateStatus(ThingStatus.OFFLINE, detail, messages.get(messageKey)); updateStatus(ThingStatus.OFFLINE, detail, message);
watchdog = 0; watchdog = 0;
channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new channelsCreated = false; // check for new channels after devices gets re-initialized (e.g. new
} }
} }
public synchronized void restartWatchdog() { @Override
public void restartWatchdog() {
watchdog = now(); watchdog = now();
updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_HEARTBEAT, getTimestamp()); updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_HEARTBEAT, getTimestamp());
logger.trace("{}: Watchdog restarted (expires in {} sec)", thingName, profile.updatePeriod); logger.trace("{}: Watchdog restarted (expires in {} sec)", thingName, profile.updatePeriod);
@ -564,13 +600,20 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
return watchdog > 0; return watchdog > 0;
} }
@Override
public void reinitializeThing() { public void reinitializeThing() {
logger.debug("{}: Re-Initialize Thing", thingName); logger.debug("{}: Re-Initialize Thing", thingName);
updateStatus(ThingStatus.UNKNOWN); if (stopping) {
logger.debug("{}: Handler is shutting down, ignore", thingName);
return;
}
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING,
messages.get("offline.status-error-restarted"));
requestUpdates(0, true); requestUpdates(0, true);
} }
private void fillDeviceStatus(ShellySettingsStatus status, boolean updated) { @Override
public void fillDeviceStatus(ShellySettingsStatus status, boolean updated) {
String alarm = ""; String alarm = "";
// Update uptime and WiFi, internal temp // Update uptime and WiFi, internal temp
@ -586,9 +629,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
// Check various device indicators like overheating // Check various device indicators like overheating
if (checkRestarted(status)) { if (checkRestarted(status)) {
// Force re-initialization on next status update // Force re-initialization on next status update
if (profile.alwaysOn) {
reinitializeThing(); reinitializeThing();
}
} else if (getBool(status.overtemperature)) { } else if (getBool(status.overtemperature)) {
alarm = ALARM_TYPE_OVERTEMP; alarm = ALARM_TYPE_OVERTEMP;
} else if (getBool(status.overload)) { } else if (getBool(status.overload)) {
@ -600,12 +641,13 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
if (internalTemp != UnDefType.NULL) { if (internalTemp != UnDefType.NULL) {
int temp = ((Number) internalTemp).intValue(); int temp = ((Number) internalTemp).intValue();
if (temp > stats.maxInternalTemp) { if (temp > stats.maxInternalTemp) {
logger.debug("{}: Max Internal Temp for device changed to {}", thingName, temp);
stats.maxInternalTemp = temp; stats.maxInternalTemp = temp;
} }
} }
if (status.uptime != null) {
stats.lastUptime = getLong(status.uptime); stats.lastUptime = getLong(status.uptime);
}
stats.coiotMessages = coap.getMessageCount(); stats.coiotMessages = coap.getMessageCount();
stats.coiotErrors = coap.getErrorCount(); stats.coiotErrors = coap.getErrorCount();
@ -622,7 +664,8 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
private boolean checkRestarted(ShellySettingsStatus status) { private boolean checkRestarted(ShellySettingsStatus status) {
if (profile.isInitialized() && profile.alwaysOn /* exclude battery powered devices */ if (profile.isInitialized() && profile.alwaysOn /* exclude battery powered devices */
&& (status.uptime < stats.lastUptime || !profile.status.update.oldVersion.isEmpty() && (status.uptime != null && status.uptime < stats.lastUptime
|| !profile.status.update.oldVersion.isEmpty()
&& !status.update.oldVersion.equals(profile.status.update.oldVersion))) { && !status.update.oldVersion.equals(profile.status.update.oldVersion))) {
updateProperties(profile, status); updateProperties(profile, status);
return true; return true;
@ -635,6 +678,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
* *
* @param alarm Alarm Message * @param alarm Alarm Message
*/ */
@Override
public void postEvent(String event, boolean force) { public void postEvent(String event, boolean force) {
String channelId = mkChannelId(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ALARM); String channelId = mkChannelId(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ALARM);
State value = cache.getValue(channelId); State value = cache.getValue(channelId);
@ -643,20 +687,21 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
if (force || !lastAlarm.equals(event) if (force || !lastAlarm.equals(event)
|| (lastAlarm.equals(event) && now() > stats.lastAlarmTs + HEALTH_CHECK_INTERVAL_SEC)) { || (lastAlarm.equals(event) && now() > stats.lastAlarmTs + HEALTH_CHECK_INTERVAL_SEC)) {
switch (event) { switch (event) {
case "":
case "0": // DW2 1.8 case "0": // DW2 1.8
case SHELLY_WAKEUPT_SENSOR: case SHELLY_WAKEUPT_SENSOR:
case SHELLY_WAKEUPT_PERIODIC: case SHELLY_WAKEUPT_PERIODIC:
case SHELLY_WAKEUPT_BUTTON: case SHELLY_WAKEUPT_BUTTON:
case SHELLY_WAKEUPT_POWERON: case SHELLY_WAKEUPT_POWERON:
case SHELLY_WAKEUPT_EXT_POWER:
case SHELLY_WAKEUPT_UNKNOWN: case SHELLY_WAKEUPT_UNKNOWN:
logger.debug("{}: {}", thingName, messages.get("event.filtered", event)); logger.debug("{}: {}", thingName, messages.get("event.filtered", event));
case "":
case ALARM_TYPE_NONE: case ALARM_TYPE_NONE:
break; break;
default: default:
logger.debug("{}: {}", thingName, messages.get("event.triggered", event)); logger.debug("{}: {}", thingName, messages.get("event.triggered", event));
triggerChannel(channelId, event); triggerChannel(channelId, event);
cache.updateChannel(channelId, getStringType(event)); cache.updateChannel(channelId, getStringType(event.toUpperCase()));
stats.lastAlarm = event; stats.lastAlarm = event;
stats.lastAlarmTs = now(); stats.lastAlarmTs = now();
stats.alarms++; stats.alarms++;
@ -808,7 +853,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
config = getConfigAs(ShellyThingConfiguration.class); config = getConfigAs(ShellyThingConfiguration.class);
if (config.deviceIp.isEmpty()) { if (config.deviceIp.isEmpty()) {
logger.warn("{}: IP address for the device must not be empty", thingName); // may not set in .things file logger.debug("{}: IP address for the device must not be empty", thingName); // may not set in .things file
return; return;
} }
try { try {
@ -822,6 +867,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
logger.debug("{}: Unable to resolve hostname {}", thingName, config.deviceIp); logger.debug("{}: Unable to resolve hostname {}", thingName, config.deviceIp);
} }
config.serviceName = getString(properties.get(PROPERTY_SERVICE_NAME));
config.localIp = localIP; config.localIp = localIP;
config.localPort = localPort; config.localPort = localPort;
if (config.userId.isEmpty() && !bindingConfig.defaultUserId.isEmpty()) { if (config.userId.isEmpty() && !bindingConfig.defaultUserId.isEmpty()) {
@ -866,7 +912,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
if (bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0) if (bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0)
|| (prf.fwVersion.equalsIgnoreCase("production_test"))) { || (prf.fwVersion.equalsIgnoreCase("production_test"))) {
if (!config.eventsCoIoT) { if (!config.eventsCoIoT) {
logger.debug("{}: {}", thingName, messages.get("versioncheck.autocoiot")); logger.info("{}: {}", thingName, messages.get("versioncheck.autocoiot"));
} }
autoCoIoT = true; autoCoIoT = true;
} }
@ -887,10 +933,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
* @param response exception details including the http respone * @param response exception details including the http respone
* @return true if the authorization failed * @return true if the authorization failed
*/ */
private boolean isAuthorizationFailed(ShellyApiResult result) { protected boolean isAuthorizationFailed(ShellyApiResult result) {
if (result.isHttpAccessUnauthorized()) { if (result.isHttpAccessUnauthorized()) {
// If the device is password protected the API doesn't provide settings to the device settings // If the device is password protected the API doesn't provide settings to the device settings
logger.warn("{}: {}", thingName, messages.get("init.protected"));
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-access-denied"); setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-access-denied");
return true; return true;
} }
@ -972,6 +1017,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
* @param status Shelly device status * @param status Shelly device status
* @return true: one or more inputs were updated * @return true: one or more inputs were updated
*/ */
@Override
public boolean updateInputs(ShellySettingsStatus status) { public boolean updateInputs(ShellySettingsStatus status) {
boolean updated = false; boolean updated = false;
@ -1004,6 +1050,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
return updated; return updated;
} }
@Override
public boolean updateWakeupReason(@Nullable List<Object> valueArray) { public boolean updateWakeupReason(@Nullable List<Object> valueArray) {
boolean changed = false; boolean changed = false;
if (valueArray != null && !valueArray.isEmpty()) { if (valueArray != null && !valueArray.isEmpty()) {
@ -1019,6 +1066,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
return changed; return changed;
} }
@Override
public void triggerButton(String group, int idx, String value) { public void triggerButton(String group, int idx, String value) {
String trigger = mapButtonEvent(value); String trigger = mapButtonEvent(value);
if (trigger.isEmpty()) { if (trigger.isEmpty()) {
@ -1036,6 +1084,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
} }
} }
@Override
public void publishState(String channelId, State value) { public void publishState(String channelId, State value) {
String id = channelId.contains("$") ? substringBefore(channelId, "$") : channelId; String id = channelId.contains("$") ? substringBefore(channelId, "$") : channelId;
if (!stopping && isLinked(id)) { if (!stopping && isLinked(id)) {
@ -1044,10 +1093,12 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
} }
} }
@Override
public boolean updateChannel(String group, String channel, State value) { public boolean updateChannel(String group, String channel, State value) {
return updateChannel(mkChannelId(group, channel), value, false); return updateChannel(mkChannelId(group, channel), value, false);
} }
@Override
public boolean updateChannel(String channelId, State value, boolean force) { public boolean updateChannel(String channelId, State value, boolean force) {
return !stopping && cache.updateChannel(channelId, value, force); return !stopping && cache.updateChannel(channelId, value, force);
} }
@ -1057,6 +1108,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
return cache.getValue(group, channel); return cache.getValue(group, channel);
} }
@Override
public double getChannelDouble(String group, String channel) { public double getChannelDouble(String group, String channel) {
State value = getChannelValue(group, channel); State value = getChannelValue(group, channel);
if (value != UnDefType.NULL) { if (value != UnDefType.NULL) {
@ -1075,7 +1127,8 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
* *
* @param thingHandler * @param thingHandler
*/ */
protected void updateChannelDefinitions(Map<String, Channel> dynChannels) { @Override
public void updateChannelDefinitions(Map<String, Channel> dynChannels) {
if (channelsCreated) { if (channelsCreated) {
return; // already done return; // already done
} }
@ -1106,6 +1159,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
} }
} }
@Override
public boolean areChannelsCreated() { public boolean areChannelsCreated() {
return channelsCreated; return channelsCreated;
} }
@ -1119,13 +1173,9 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
protected void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus status) { protected void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus status) {
logger.debug("{}: Update properties", thingName); logger.debug("{}: Update properties", thingName);
Map<String, Object> properties = fillDeviceProperties(profile); Map<String, Object> properties = fillDeviceProperties(profile);
String serviceName = getString(getThing().getProperties().get(PROPERTY_SERVICE_NAME));
String hostname = getString(profile.settings.device.hostname).toLowerCase();
if (serviceName.isEmpty()) {
properties.put(PROPERTY_SERVICE_NAME, hostname);
logger.trace("{}: Updated serrviceName to {}", thingName, hostname);
}
String deviceName = getString(profile.settings.name); String deviceName = getString(profile.settings.name);
properties.put(PROPERTY_SERVICE_NAME, config.serviceName);
properties.put(PROPERTY_DEV_GEN, "1");
if (!deviceName.isEmpty()) { if (!deviceName.isEmpty()) {
properties.put(PROPERTY_DEV_NAME, deviceName); properties.put(PROPERTY_DEV_NAME, deviceName);
} }
@ -1155,6 +1205,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
* @param key Name of the property * @param key Name of the property
* @param value Value of the property * @param value Value of the property
*/ */
@Override
public void updateProperties(String key, String value) { public void updateProperties(String key, String value) {
Map<String, String> thingProperties = editProperties(); Map<String, String> thingProperties = editProperties();
if (thingProperties.containsKey(key)) { if (thingProperties.containsKey(key)) {
@ -1184,6 +1235,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
* @param key property name * @param key property name
* @return property value or "" if property is not set * @return property value or "" if property is not set
*/ */
@Override
public String getProperty(String key) { public String getProperty(String key) {
Map<String, String> thingProperties = getThing().getProperties(); Map<String, String> thingProperties = getThing().getProperties();
return getString(thingProperties.get(key)); return getString(thingProperties.get(key));
@ -1230,7 +1282,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
if (refreshSettings) { if (refreshSettings) {
profile = api.getDeviceProfile(thingType); profile = api.getDeviceProfile(thingType);
if (!isThingOnline()) { if (!isThingOnline()) {
logger.debug("{}:Device profile re-initialized (thingType={})", thingName, thingType); logger.debug("{}: Device profile re-initialized (thingType={})", thingName, thingType);
} }
} }
} finally { } finally {
@ -1244,14 +1296,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
return profile; return profile;
} }
protected ShellyHttpApi getShellyApi() {
return api;
}
protected ShellyDeviceProfile getDeviceProfile() { protected ShellyDeviceProfile getDeviceProfile() {
return profile; return profile;
} }
@Override
public void triggerChannel(String group, String channel, String payload) { public void triggerChannel(String group, String channel, String payload) {
String triggerCh = mkChannelId(group, channel); String triggerCh = mkChannelId(group, channel);
logger.debug("{}: Send event {} to channel {}", thingName, triggerCh, payload); logger.debug("{}: Send event {} to channel {}", thingName, triggerCh, payload);
@ -1324,7 +1373,7 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
} }
@Override @Override
public ShellyHttpApi getApi() { public ShellyApiInterface getApi() {
return api; return api;
} }
@ -1332,6 +1381,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
return stats.asProperties(); return stats.asProperties();
} }
@Override
public long getScheduledUpdates() {
return scheduledUpdates;
}
public String checkForUpdate() { public String checkForUpdate() {
try { try {
ShellyOtaCheckResult result = api.checkForUpdate(); ShellyOtaCheckResult result = api.checkForUpdate();
@ -1340,4 +1394,11 @@ public class ShellyBaseHandler extends BaseThingHandler implements ShellyDeviceL
return ""; return "";
} }
} }
@Override
public void triggerUpdateFromCoap() {
if ((!autoCoIoT && (getScheduledUpdates() < 1)) || (autoCoIoT && !profile.isLight && !profile.hasBattery)) {
requestUpdates(1, false);
}
}
} }

View File

@ -49,29 +49,35 @@ public class ShellyComponents {
* @param th Thing Handler instance * @param th Thing Handler instance
* @param profile ShellyDeviceProfile * @param profile ShellyDeviceProfile
*/ */
public static boolean updateDeviceStatus(ShellyBaseHandler thingHandler, ShellySettingsStatus status) { public static boolean updateDeviceStatus(ShellyThingInterface thingHandler, ShellySettingsStatus status) {
ShellyDeviceProfile profile = thingHandler.getProfile();
if (!thingHandler.areChannelsCreated()) { if (!thingHandler.areChannelsCreated()) {
thingHandler.updateChannelDefinitions(ShellyChannelDefinitions.createDeviceChannels(thingHandler.getThing(), thingHandler.updateChannelDefinitions(ShellyChannelDefinitions.createDeviceChannels(thingHandler.getThing(),
thingHandler.getProfile(), status)); thingHandler.getProfile(), status));
} }
Integer rssi = getInteger(status.wifiSta.rssi); if (getLong(status.uptime) > 10) {
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME, thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME,
toQuantityType((double) getLong(status.uptime), DIGITS_NONE, Units.SECOND)); toQuantityType((double) getLong(status.uptime), DIGITS_NONE, Units.SECOND));
}
Integer rssi = getInteger(status.wifiSta.rssi);
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI, mapSignalStrength(rssi)); thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI, mapSignalStrength(rssi));
if ((status.tmp != null) && !thingHandler.getProfile().isSensor) { if (getDouble(status.temperature) != SHELLY_API_INVTEMP) {
if (status.tmp != null && !thingHandler.getProfile().isSensor) {
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS)); toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS));
} else if (status.temperature != null) { } else if (status.temperature != null) {
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP, thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS)); toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS));
} }
}
thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SLEEPTIME, thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SLEEPTIME,
toQuantityType(getInteger(status.sleepTime), Units.SECOND)); toQuantityType(getInteger(status.sleepTime), Units.SECOND));
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE, getOnOff(status.hasUpdate)); thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE, getOnOff(status.hasUpdate));
ShellyDeviceProfile profile = thingHandler.getProfile();
if (profile.settings.calibrated != null) { if (profile.settings.calibrated != null) {
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CALIBRATED, thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CALIBRATED,
getOnOff(profile.settings.calibrated)); getOnOff(profile.settings.calibrated));
@ -87,7 +93,7 @@ public class ShellyComponents {
* @param profile ShellyDeviceProfile * @param profile ShellyDeviceProfile
* @param status Last ShellySettingsStatus * @param status Last ShellySettingsStatus
*/ */
public static boolean updateMeters(ShellyBaseHandler thingHandler, ShellySettingsStatus status) { public static boolean updateMeters(ShellyThingInterface thingHandler, ShellySettingsStatus status) {
ShellyDeviceProfile profile = thingHandler.getProfile(); ShellyDeviceProfile profile = thingHandler.getProfile();
double accumulatedWatts = 0.0; double accumulatedWatts = 0.0;
@ -99,25 +105,15 @@ public class ShellyComponents {
// We need to differ // We need to differ
// Roler+RGBW2 have multiple meters -> aggregate consumption to the functional device // Roler+RGBW2 have multiple meters -> aggregate consumption to the functional device
// Meter and EMeter have a different set of channels // Meter and EMeter have a different set of channels
if ((profile.numMeters > 0) && ((status.meters != null) || (status.emeters != null))) { if (status.meters != null || status.emeters != null) {
if (!profile.isRoller && !profile.isRGBW2) { if (!profile.isRoller && !profile.isRGBW2) {
thingHandler.logger.trace("{}: Updating {} {}meter(s)", thingHandler.thingName, profile.numMeters,
!profile.isEMeter ? "standard " : "e-");
// In Relay mode we map eacher meter to the matching channel group // In Relay mode we map eacher meter to the matching channel group
int m = 0; int m = 0;
if (!profile.isEMeter) { if (!profile.isEMeter) {
for (ShellySettingsMeter meter : status.meters) { for (ShellySettingsMeter meter : status.meters) {
Integer meterIndex = m + 1;
if (getBool(meter.isValid) || profile.isLight) { // RGBW2-white doesn't report valid flag if (getBool(meter.isValid) || profile.isLight) { // RGBW2-white doesn't report valid flag
// correctly in white mode // correctly in white mode
String groupName = ""; String groupName = profile.getMeterGroup(m);
if (profile.numMeters > 1) {
groupName = CHANNEL_GROUP_METER + meterIndex.toString();
} else {
groupName = CHANNEL_GROUP_METER;
}
if (!thingHandler.areChannelsCreated()) { if (!thingHandler.areChannelsCreated()) {
// skip for Shelly Bulb: JSON has a meter, but values don't get updated // skip for Shelly Bulb: JSON has a meter, but values don't get updated
if (!profile.isBulb) { if (!profile.isBulb) {
@ -141,17 +137,17 @@ public class ShellyComponents {
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1, updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
toQuantityType(getDouble(meter.counters[0]), DIGITS_WATT, Units.WATT)); toQuantityType(getDouble(meter.counters[0]), DIGITS_WATT, Units.WATT));
} }
if (meter.timestamp != null) {
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE, thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
getTimestamp(getString(profile.settings.timezone), getLong(meter.timestamp))); getTimestamp(getString(profile.settings.timezone), meter.timestamp));
}
} }
m++; m++;
} }
} else { } else {
for (ShellySettingsEMeter emeter : status.emeters) { for (ShellySettingsEMeter emeter : status.emeters) {
Integer meterIndex = m + 1;
if (getBool(emeter.isValid)) { if (getBool(emeter.isValid)) {
String groupName = profile.numMeters > 1 ? CHANNEL_GROUP_METER + meterIndex.toString() String groupName = profile.getMeterGroup(m);
: CHANNEL_GROUP_METER;
if (!thingHandler.areChannelsCreated()) { if (!thingHandler.areChannelsCreated()) {
thingHandler.updateChannelDefinitions(ShellyChannelDefinitions thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
.createEMeterChannels(thingHandler.getThing(), emeter, groupName)); .createEMeterChannels(thingHandler.getThing(), emeter, groupName));
@ -185,14 +181,23 @@ public class ShellyComponents {
} }
} else { } else {
// In Roller Mode we accumulate all meters to a single set of meters // In Roller Mode we accumulate all meters to a single set of meters
thingHandler.logger.trace("{}: Updating Meter (accumulated)", thingHandler.thingName);
double currentWatts = 0.0; double currentWatts = 0.0;
double totalWatts = 0.0; double totalWatts = 0.0;
double lastMin1 = 0.0; double lastMin1 = 0.0;
long timestamp = 0l; long timestamp = 0l;
String groupName = CHANNEL_GROUP_METER; String groupName = CHANNEL_GROUP_METER;
if (!thingHandler.areChannelsCreated()) {
ShellySettingsMeter m = status.meters.get(0);
if (getBool(m.isValid)) {
// Create channels for 1 Meter
thingHandler.updateChannelDefinitions(
ShellyChannelDefinitions.createMeterChannels(thingHandler.getThing(), m, groupName));
}
}
for (ShellySettingsMeter meter : status.meters) { for (ShellySettingsMeter meter : status.meters) {
if (meter.isValid) { if (getBool(meter.isValid)) {
currentWatts += getDouble(meter.power); currentWatts += getDouble(meter.power);
totalWatts += getDouble(meter.total); totalWatts += getDouble(meter.total);
if (meter.counters != null) { if (meter.counters != null) {
@ -203,11 +208,6 @@ public class ShellyComponents {
} }
} }
} }
// Create channels for 1 Meter
if (!thingHandler.areChannelsCreated()) {
thingHandler.updateChannelDefinitions(ShellyChannelDefinitions
.createMeterChannels(thingHandler.getThing(), status.meters.get(0), groupName));
}
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1, updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
toQuantityType(getDouble(lastMin1), DIGITS_WATT, Units.WATT)); toQuantityType(getDouble(lastMin1), DIGITS_WATT, Units.WATT));
@ -244,7 +244,7 @@ public class ShellyComponents {
} }
// EM: compute from provided values // EM: compute from provided values
if (Math.abs(emeter.power) + Math.abs(emeter.reactive) > 1.5) { if (emeter.reactive != null && Math.abs(emeter.power) + Math.abs(emeter.reactive) > 1.5) {
double pf = emeter.power / Math.sqrt(emeter.power * emeter.power + emeter.reactive * emeter.reactive); double pf = emeter.power / Math.sqrt(emeter.power * emeter.power + emeter.reactive * emeter.reactive);
return pf; return pf;
} }
@ -260,15 +260,14 @@ public class ShellyComponents {
* *
* @throws ShellyApiException * @throws ShellyApiException
*/ */
public static boolean updateSensors(ShellyBaseHandler thingHandler, ShellySettingsStatus status) public static boolean updateSensors(ShellyThingInterface thingHandler, ShellySettingsStatus status)
throws ShellyApiException { throws ShellyApiException {
ShellyDeviceProfile profile = thingHandler.getProfile(); ShellyDeviceProfile profile = thingHandler.getProfile();
boolean updated = false; boolean updated = false;
if (profile.isSensor || profile.hasBattery) { if (profile.isSensor || profile.hasBattery) {
ShellyStatusSensor sdata = thingHandler.api.getSensorStatus(); ShellyStatusSensor sdata = thingHandler.getApi().getSensorStatus();
if (!thingHandler.areChannelsCreated()) { if (!thingHandler.areChannelsCreated()) {
thingHandler.logger.trace("{}: Create missing sensor channel(s)", thingHandler.thingName);
thingHandler.updateChannelDefinitions( thingHandler.updateChannelDefinitions(
ShellyChannelDefinitions.createSensorChannels(thingHandler.getThing(), profile, sdata)); ShellyChannelDefinitions.createSensorChannels(thingHandler.getThing(), profile, sdata));
} }
@ -283,13 +282,12 @@ public class ShellyComponents {
String sensorError = sdata.sensorError; String sensorError = sdata.sensorError;
boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR,
getStringType(sensorError)); getStringType(sensorError));
if (!"0".equals(sensorError) && changed) { if (changed && !"0".equals(sensorError)) {
thingHandler.postEvent(getString(sdata.sensorError), true); thingHandler.postEvent(getString(sdata.sensorError), true);
}
updated |= changed; updated |= changed;
} }
}
if ((sdata.tmp != null) && getBool(sdata.tmp.isValid)) { if ((sdata.tmp != null) && getBool(sdata.tmp.isValid)) {
thingHandler.logger.trace("{}: Updating temperature", thingHandler.thingName);
Double temp = getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_CELSIUS) Double temp = getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_CELSIUS)
? getDouble(sdata.tmp.tC) ? getDouble(sdata.tmp.tC)
: getDouble(sdata.tmp.tF); : getDouble(sdata.tmp.tF);
@ -300,7 +298,7 @@ public class ShellyComponents {
temp = convertToC(temp, getString(sdata.tmp.units)); temp = convertToC(temp, getString(sdata.tmp.units));
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP, updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
toQuantityType(temp.doubleValue(), DIGITS_TEMP, SIUnits.CELSIUS)); toQuantityType(temp.doubleValue(), DIGITS_TEMP, SIUnits.CELSIUS));
} else if (status.thermostats != null && status.thermostats.size() > 0) { } else if (status.thermostats != null && profile.settings.thermostats != null) {
// Shelly TRV // Shelly TRV
ShellyThermnostat t = status.thermostats.get(0); ShellyThermnostat t = status.thermostats.get(0);
ShellyThermnostat ps = profile.settings.thermostats.get(0); ShellyThermnostat ps = profile.settings.thermostats.get(0);
@ -313,13 +311,11 @@ public class ShellyComponents {
updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_MODE, updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_MODE,
getStringType(getBool(t.targetTemp.enabled) ? SHELLY_TRV_MODE_AUTO : SHELLY_TRV_MODE_MANUAL)); getStringType(getBool(t.targetTemp.enabled) ? SHELLY_TRV_MODE_AUTO : SHELLY_TRV_MODE_MANUAL));
updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE, updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE,
getDecimal(getBool(t.schedule) ? t.profile : 0)); getDecimal(getBool(t.schedule) ? t.profile + 1 : 0));
updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SCHEDULE, updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SCHEDULE,
getOnOff(t.schedule)); getOnOff(t.schedule));
if (t.tmp != null) { if (t.tmp != null) {
Double temp = convertToC(t.tmp.value, getString(t.tmp.units)); Double temp = convertToC(t.tmp.value, getString(t.tmp.units));
// Some devices report values = -999 or 99 during fw update
boolean valid = temp.intValue() > -50 && temp.intValue() < 90;
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP, updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
toQuantityType(temp.doubleValue(), DIGITS_TEMP, SIUnits.CELSIUS)); toQuantityType(temp.doubleValue(), DIGITS_TEMP, SIUnits.CELSIUS));
temp = convertToC(t.targetTemp.value, getString(t.targetTemp.unit)); temp = convertToC(t.targetTemp.value, getString(t.targetTemp.unit));
@ -333,14 +329,13 @@ public class ShellyComponents {
getDouble(t.pos) > 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED); getDouble(t.pos) > 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
} }
} }
if (sdata.hum != null) { if (sdata.hum != null) {
thingHandler.logger.trace("{}: Updating humidity", thingHandler.thingName);
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM, updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM,
toQuantityType(getDouble(sdata.hum.value), DIGITS_PERCENT, Units.PERCENT)); toQuantityType(getDouble(sdata.hum.value), DIGITS_PERCENT, Units.PERCENT));
} }
if ((sdata.lux != null) && getBool(sdata.lux.isValid)) { if ((sdata.lux != null) && getBool(sdata.lux.isValid)) {
// lux:{value:30, illumination: dark, is_valid:true}, // lux:{value:30, illumination: dark, is_valid:true},
thingHandler.logger.trace("{}: Updating lux", thingHandler.thingName);
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX, updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX,
toQuantityType(getDouble(sdata.lux.value), DIGITS_LUX, Units.LUX)); toQuantityType(getDouble(sdata.lux.value), DIGITS_LUX, Units.LUX));
if (sdata.lux.illumination != null) { if (sdata.lux.illumination != null) {
@ -369,8 +364,8 @@ public class ShellyComponents {
getStringType(sdata.gasSensor.sensorState)); getStringType(sdata.gasSensor.sensorState));
} }
if ((sdata.concentration != null) && sdata.concentration.isValid) { if ((sdata.concentration != null) && sdata.concentration.isValid) {
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, toQuantityType(
getDecimal(sdata.concentration.ppm)); getInteger(sdata.concentration.ppm).doubleValue(), DIGITS_NONE, Units.PARTS_PER_MILLION));
} }
if ((sdata.adcs != null) && (sdata.adcs.size() > 0)) { if ((sdata.adcs != null) && (sdata.adcs.size() > 0)) {
ShellyADC adc = sdata.adcs.get(0); ShellyADC adc = sdata.adcs.get(0);
@ -384,18 +379,14 @@ public class ShellyComponents {
charger ? OnOffType.ON : OnOffType.OFF); charger ? OnOffType.ON : OnOffType.OFF);
} }
if (sdata.bat != null) { // no update for Sense if (sdata.bat != null) { // no update for Sense
// Shelly HT has external_power under settings, Sense and Motion charger under status
if (!charger || !profile.isHT) {
updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL, updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
toQuantityType(getDouble(sdata.bat.value), 0, Units.PERCENT)); toQuantityType(getDouble(sdata.bat.value), 0, Units.PERCENT));
} else {
updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL, int lowBattery = thingHandler.getThingConfig().lowBattery;
UnDefType.UNDEF);
}
boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW, boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW,
getDouble(sdata.bat.value) < thingHandler.config.lowBattery ? OnOffType.ON : OnOffType.OFF); !charger && getDouble(sdata.bat.value) < lowBattery ? OnOffType.ON : OnOffType.OFF);
updated |= changed; updated |= changed;
if (changed && getDouble(sdata.bat.value) < thingHandler.config.lowBattery) { if (!charger && changed && getDouble(sdata.bat.value) < lowBattery) {
thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false); thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false);
} }
} }

View File

@ -143,7 +143,7 @@ public class ShellyLightHandler extends ShellyBaseHandler {
int value = -1; int value = -1;
if (command instanceof OnOffType) { // Switch if (command instanceof OnOffType) { // Switch
logger.debug("{}: Switch light {}", thingName, command); logger.debug("{}: Switch light {}", thingName, command);
ShellyShortLightStatus light = api.setRelayTurn(lightId, ShellyShortLightStatus light = api.setLightTurn(lightId,
command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF); command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
col.power = getOnOff(light.ison); col.power = getOnOff(light.ison);
col.setBrightness(light.brightness); col.setBrightness(light.brightness);
@ -164,7 +164,7 @@ public class ShellyLightHandler extends ShellyBaseHandler {
} }
if (value == 0) { if (value == 0) {
logger.debug("{}: Brightness=0 -> switch light OFF", thingName); logger.debug("{}: Brightness=0 -> switch light OFF", thingName);
api.setRelayTurn(lightId, SHELLY_API_OFF); api.setLightTurn(lightId, SHELLY_API_OFF);
update = false; update = false;
} else { } else {
if (command instanceof IncreaseDecreaseType) { if (command instanceof IncreaseDecreaseType) {
@ -347,12 +347,14 @@ public class ShellyLightHandler extends ShellyBaseHandler {
ShellyColorUtils col = getCurrentColors(lightId); ShellyColorUtils col = getCurrentColors(lightId);
col.power = getOnOff(light.ison); col.power = getOnOff(light.ison);
if (profile.settings.lights != null) {
// Channel control/timer // Channel control/timer
ShellySettingsRgbwLight ls = profile.settings.lights.get(lightId); ShellySettingsRgbwLight ls = profile.settings.lights.get(lightId);
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(ls.autoOn)); updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(ls.autoOn));
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(ls.autoOff)); updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(ls.autoOff));
updated |= updateChannel(controlGroup, CHANNEL_LIGHT_POWER, col.power);
updated |= updateChannel(controlGroup, CHANNEL_TIMER_ACTIVE, getOnOff(light.hasTimer)); updated |= updateChannel(controlGroup, CHANNEL_TIMER_ACTIVE, getOnOff(light.hasTimer));
updated |= updateChannel(controlGroup, CHANNEL_LIGHT_POWER, col.power);
}
if (getBool(light.overpower)) { if (getBool(light.overpower)) {
postEvent(ALARM_TYPE_OVERPOWER, false); postEvent(ALARM_TYPE_OVERPOWER, false);

View File

@ -14,8 +14,8 @@ package org.openhab.binding.shelly.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.shelly.internal.api.ShellyApiException; import org.openhab.binding.shelly.internal.api.ShellyApiException;
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.State; import org.openhab.core.types.State;
@ -36,7 +36,7 @@ public interface ShellyManagerInterface {
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException; public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
public ShellyHttpApi getApi(); public ShellyApiInterface getApi();
public ShellyDeviceStats getStats(); public ShellyDeviceStats getStats();

View File

@ -335,7 +335,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException { public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException {
boolean updated = false; boolean updated = false;
// Check for Relay in Standard Mode // Check for Relay in Standard Mode
if (profile.hasRelays && !profile.isRoller && !profile.isDimmer) { if (profile.hasRelays && !profile.isDimmer) {
double voltage = -1; double voltage = -1;
if (status.voltage == null && profile.settings.supplyVoltage != null) { if (status.voltage == null && profile.settings.supplyVoltage != null) {
// Shelly 1PM/1L (fix) // Shelly 1PM/1L (fix)
@ -348,7 +348,9 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_VOLTAGE, updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_VOLTAGE,
toQuantityType(voltage, DIGITS_VOLT, Units.VOLT)); toQuantityType(voltage, DIGITS_VOLT, Units.VOLT));
} }
}
if (profile.hasRelays && !profile.isRoller && !profile.isDimmer) {
logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays); logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays);
int i = 0; int i = 0;
ShellyStatusRelay rstatus = api.getRelayStatus(i); ShellyStatusRelay rstatus = api.getRelayStatus(i);
@ -479,6 +481,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
toQuantityType(0.0, DIGITS_NONE, Units.PERCENT)); toQuantityType(0.0, DIGITS_NONE, Units.PERCENT));
} }
if (profile.settings.dimmers != null) {
ShellySettingsDimmer dsettings = profile.settings.dimmers.get(l); ShellySettingsDimmer dsettings = profile.settings.dimmers.get(l);
if (dsettings != null) { if (dsettings != null) {
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON, updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON,
@ -486,6 +489,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF, updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
toQuantityType(getDouble(dsettings.autoOff), Units.SECOND)); toQuantityType(getDouble(dsettings.autoOff), Units.SECOND));
} }
}
l++; l++;
} }

View File

@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.shelly.internal.handler;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.api.ShellyApiException;
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.State;
/**
* The {@link ShellyThingInterface} implements the interface for Shelly Manager to access the thing handler
*
* @author Markus Michels - Initial contribution
*/
@NonNullByDefault
public interface ShellyThingInterface {
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
public double getChannelDouble(String group, String channel);
public boolean updateChannel(String group, String channel, State value);
public boolean updateChannel(String channelId, State value, boolean force);
public void setThingOnline();
public void setThingOffline(ThingStatusDetail detail, String messageKey);
public boolean requestUpdates(int requestCount, boolean refreshSettings);
public void triggerUpdateFromCoap();
public void reinitializeThing();
public void restartWatchdog();
public void publishState(String channelId, State value);
public boolean areChannelsCreated();
public State getChannelValue(String group, String channel);
public boolean updateInputs(ShellySettingsStatus status);
public void updateChannelDefinitions(Map<String, Channel> dynChannels);
public void postEvent(String event, boolean force);
public void triggerChannel(String group, String channelID, String event);
public void triggerButton(String group, int idx, String value);
public ShellyDeviceStats getStats();
public void resetStats();
public Thing getThing();
public String getThingName();
public ShellyThingConfiguration getThingConfig();
public String getProperty(String key);
public void updateProperties(String key, String value);
public boolean updateWakeupReason(@Nullable List<Object> valueArray);
public ShellyApiInterface getApi();
public ShellyDeviceProfile getProfile();
public long getScheduledUpdates();
public void fillDeviceStatus(ShellySettingsStatus status, boolean updated);
public boolean checkRepresentation(String key);
}

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.shelly.internal.handler;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
/***
* The{@link ShellyThingTable} implements a simple table to allow dispatching incoming events to the proper thing
* handler
*
* @author Markus Michels - Initial contribution
*/
@NonNullByDefault
@Component(service = ShellyThingTable.class, configurationPolicy = ConfigurationPolicy.OPTIONAL)
public class ShellyThingTable {
private Map<String, ShellyThingInterface> thingTable = new ConcurrentHashMap<>();
public void addThing(String key, ShellyThingInterface thing) {
thingTable.put(key, thing);
}
public ShellyThingInterface getThing(String key) {
ShellyThingInterface t = thingTable.get(key);
if (t != null) {
return t;
}
for (Map.Entry<String, ShellyThingInterface> entry : thingTable.entrySet()) {
t = entry.getValue();
if (t.checkRepresentation(key)) {
return t;
}
}
throw new IllegalArgumentException();
}
public void removeThing(String key) {
if (thingTable.containsKey(key)) {
thingTable.remove(key);
}
}
public Map<String, ShellyThingInterface> getTable() {
return thingTable;
}
public int size() {
return thingTable.size();
}
}

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.shelly.internal.ShellyHandlerFactory; import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
import org.openhab.binding.shelly.internal.api.ShellyApiException; import org.openhab.binding.shelly.internal.api.ShellyApiException;
import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyOtaCheckResult; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyOtaCheckResult;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsLogin; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsLogin;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
@ -81,7 +82,7 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
ShellyThingConfiguration config = getThingConfig(th, properties); ShellyThingConfiguration config = getThingConfig(th, properties);
ShellyDeviceProfile profile = th.getProfile(); ShellyDeviceProfile profile = th.getProfile();
ShellyHttpApi api = th.getApi(); ShellyApiInterface api = th.getApi();
new ShellyHttpApi(uid, config, httpClient); new ShellyHttpApi(uid, config, httpClient);
int refreshTimer = 0; int refreshTimer = 0;
@ -326,7 +327,7 @@ public class ShellyManagerActionPage extends ShellyManagerPage {
!profile.settings.wifiRecoveryReboot ? "Enable WiFi Recovery" : "Disable WiFi Recovery"); !profile.settings.wifiRecoveryReboot ? "Enable WiFi Recovery" : "Disable WiFi Recovery");
} }
boolean set = (profile.settings.cloud != null) && profile.settings.cloud.enabled; boolean set = profile.settings.cloud != null && profile.settings.cloud.enabled;
list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud"); list.put(set ? ACTION_DISCLOUD : ACTION_ENCLOUD, set ? "Disable Cloud" : "Enable Cloud");
list.put(ACTION_RESET, "-Factory Reset"); list.put(ACTION_RESET, "-Factory Reset");

View File

@ -78,7 +78,8 @@ public class ShellyManagerServlet extends HttpServlet {
try { try {
httpService.registerServlet(SERVLET_URI, this, null, httpService.createDefaultHttpContext()); httpService.registerServlet(SERVLET_URI, this, null, httpService.createDefaultHttpContext());
logger.debug("{}: Started at '{}'", className, SERVLET_URI); // Promote Shelly Manager usage
logger.info("{}", translationProvider.get("status.managerstarted", localIp, localPort + ""));
} catch (NamespaceException | ServletException | IllegalArgumentException e) { } catch (NamespaceException | ServletException | IllegalArgumentException e) {
logger.warn("{}: Unable to initialize bindingConfig", className, e); logger.warn("{}: Unable to initialize bindingConfig", className, e);
} }

View File

@ -13,6 +13,7 @@
package org.openhab.binding.shelly.internal.provider; package org.openhab.binding.shelly.internal.provider;
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*; import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.SHELLY_API_INVTEMP;
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*; import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
import java.util.HashMap; import java.util.HashMap;
@ -41,7 +42,7 @@ import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLigh
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor; import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile; import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler; import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
import org.openhab.core.thing.Channel; import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -261,26 +262,24 @@ public class ShellyChannelDefinitions {
addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME); addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
if (!profile.isSensor) { if (!profile.isSensor && !profile.isIX3 && getDouble(status.temperature) != SHELLY_API_INVTEMP) {
// Only some devices report the internal device temp // Only some devices report the internal device temp
addChannel(thing, add, (status.tmp != null) || (status.temperature != null), CHGR_DEVST, addChannel(thing, add, status.tmp != null || status.temperature != null, CHGR_DEVST, CHANNEL_DEVST_ITEMP);
CHANNEL_DEVST_ITEMP);
} }
addChannel(thing, add, profile.settings.sleepTime != null, CHGR_SENSOR, CHANNEL_SENSOR_SLEEPTIME); addChannel(thing, add, profile.settings.sleepTime != null, CHGR_SENSOR, CHANNEL_SENSOR_SLEEPTIME);
// If device has more than 1 meter the channel accumulatedWatts receives the accumulated value // If device has more than 1 meter the channel accumulatedWatts receives the accumulated value
boolean accuChannel = !profile.isRoller && !profile.isRGBW2 boolean accuChannel = (((status.meters != null) && (status.meters.size() > 1) && !profile.isRoller
&& (((status.meters != null) && (status.meters.size() > 1)) && !profile.isRGBW2) || ((status.emeters != null && status.emeters.size() > 1)));
|| ((status.emeters != null && status.emeters.size() > 1)));
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS); addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS);
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL); addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL);
addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED); addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED);
addChannel(thing, add, status.voltage != null || profile.settings.supplyVoltage != null, CHGR_DEVST,
CHANNEL_DEVST_VOLTAGE);
addChannel(thing, add, addChannel(thing, add,
!profile.isRoller && !profile.isRGBW2 profile.status.uptime != null && (!profile.hasBattery || profile.isMotion || profile.isTRV), CHGR_DEVST,
&& (status.voltage != null || profile.settings.supplyVoltage != null), CHANNEL_DEVST_UPTIME);
CHGR_DEVST, CHANNEL_DEVST_VOLTAGE);
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE); addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE);
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPTIME);
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT); addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT);
addChannel(thing, add, profile.settings.ledPowerDisable != null, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE); addChannel(thing, add, profile.settings.ledPowerDisable != null, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE);
addChannel(thing, add, profile.settings.ledStatusDisable != null, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi addChannel(thing, add, profile.settings.ledStatusDisable != null, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi
@ -360,8 +359,7 @@ public class ShellyChannelDefinitions {
if (status.inputs != null) { if (status.inputs != null) {
// Create channels per input. For devices with more than 1 input (Dimmer, 1L) multiple channel sets are // Create channels per input. For devices with more than 1 input (Dimmer, 1L) multiple channel sets are
// created by adding the index to the channel name // created by adding the index to the channel name
boolean multi = ((profile.numRelays == 1) || profile.isDimmer || profile.isRoller) boolean multi = (profile.numRelays == 1 || profile.isDimmer || profile.isRoller) && profile.numInputs >= 2;
&& (profile.numInputs >= 2);
for (int i = 0; i < profile.numInputs; i++) { for (int i = 0; i < profile.numInputs; i++) {
String suffix = multi ? String.valueOf(i + 1) : ""; String suffix = multi ? String.valueOf(i + 1) : "";
ShellyInputState input = status.inputs.get(i); ShellyInputState input = status.inputs.get(i);
@ -390,7 +388,7 @@ public class ShellyChannelDefinitions {
addChannel(thing, add, roller.stopReason != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STOPR); addChannel(thing, add, roller.stopReason != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STOPR);
addChannel(thing, add, roller.safetySwitch != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_SAFETY); addChannel(thing, add, roller.safetySwitch != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_SAFETY);
ShellyBaseHandler handler = (ShellyBaseHandler) thing.getHandler(); ShellyThingInterface handler = (ShellyThingInterface) thing.getHandler();
if (handler != null) { if (handler != null) {
ShellySettingsGlobal settings = handler.getProfile().settings; ShellySettingsGlobal settings = handler.getProfile().settings;
if (getBool(settings.favoritesEnabled) && (settings.favorites != null)) { if (getBool(settings.favoritesEnabled) && (settings.favorites != null)) {
@ -404,7 +402,7 @@ public class ShellyChannelDefinitions {
Map<String, Channel> newChannels = new LinkedHashMap<>(); Map<String, Channel> newChannels = new LinkedHashMap<>();
addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS); addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS);
addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH); addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH);
addChannel(thing, newChannels, (meter.counters != null) && (meter.counters[0] != null), group, addChannel(thing, newChannels, meter.counters != null && meter.counters[0] != null, group,
CHANNEL_METER_LASTMIN1); CHANNEL_METER_LASTMIN1);
addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE); addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE);
return newChannels; return newChannels;
@ -509,14 +507,23 @@ public class ShellyChannelDefinitions {
ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:") ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:")
? new ChannelTypeUID(channelDef.typeId) ? new ChannelTypeUID(channelDef.typeId)
: new ChannelTypeUID(BINDING_ID, channelDef.typeId); : new ChannelTypeUID(BINDING_ID, channelDef.typeId);
Channel channel; ChannelBuilder builder;
if (channelDef.typeId.equalsIgnoreCase("system:button")) { if (channelDef.typeId.equalsIgnoreCase("system:button")) {
channel = ChannelBuilder.create(channelUID, null).withKind(ChannelKind.TRIGGER) builder = ChannelBuilder.create(channelUID, null).withKind(ChannelKind.TRIGGER);
.withType(channelTypeUID).build();
} else { } else {
channel = ChannelBuilder.create(channelUID, channelDef.itemType).withType(channelTypeUID).build(); builder = ChannelBuilder.create(channelUID, channelDef.itemType);
} }
newChannels.put(channelId, channel); if (!channelDef.label.isEmpty()) {
char grseq = lastChar(group);
char chseq = lastChar(channelName);
char sequence = isDigit(chseq) ? chseq : grseq;
String label = !isDigit(sequence) ? channelDef.label : channelDef.label + " " + sequence;
builder.withLabel(label);
}
if (!channelDef.description.isEmpty()) {
builder.withDescription(channelDef.description);
}
newChannels.put(channelId, builder.withType(channelTypeUID).build());
} }
} }
} }
@ -549,9 +556,21 @@ public class ShellyChannelDefinitions {
this.typeId = typeId; this.typeId = typeId;
groupLabel = getText(PREFIX_GROUP + group + ".label"); groupLabel = getText(PREFIX_GROUP + group + ".label");
if (groupLabel.contains(PREFIX_GROUP)) {
groupLabel = "";
}
groupDescription = getText(PREFIX_GROUP + group + ".description"); groupDescription = getText(PREFIX_GROUP + group + ".description");
label = getText(PREFIX_CHANNEL + channel + ".label"); if (groupDescription.contains(PREFIX_GROUP)) {
description = getText(PREFIX_CHANNEL + channel + ".description"); groupDescription = "";
}
label = getText(PREFIX_CHANNEL + typeId.replace(':', '.') + ".label");
if (label.contains(PREFIX_CHANNEL)) {
label = "";
}
description = getText(PREFIX_CHANNEL + typeId + ".description");
if (description.contains(PREFIX_CHANNEL)) {
description = "";
}
} }
public String getChanneId() { public String getChanneId() {

View File

@ -18,7 +18,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler; import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -33,14 +33,14 @@ import org.slf4j.LoggerFactory;
public class ShellyChannelCache { public class ShellyChannelCache {
private final Logger logger = LoggerFactory.getLogger(ShellyChannelCache.class); private final Logger logger = LoggerFactory.getLogger(ShellyChannelCache.class);
private final ShellyBaseHandler thingHandler; private final ShellyThingInterface thingHandler;
private final Map<String, State> channelData = new ConcurrentHashMap<>(); private final Map<String, State> channelData = new ConcurrentHashMap<>();
private String thingName = ""; private String thingName = "";
private boolean enabled = false; private boolean enabled = false;
public ShellyChannelCache(ShellyBaseHandler thingHandler) { public ShellyChannelCache(ShellyThingInterface thingHandler) {
this.thingHandler = thingHandler; this.thingHandler = thingHandler;
setThingName(thingHandler.thingName); setThingName(thingHandler.getThingName());
} }
public void setThingName(String thingName) { public void setThingName(String thingName) {

View File

@ -289,9 +289,6 @@ public class ShellyUtils {
public static DateTimeType getTimestamp(String zone, long timestamp) { public static DateTimeType getTimestamp(String zone, long timestamp) {
try { try {
if (timestamp == 0) {
throw new IllegalArgumentException("Timestamp value 0 is invalid");
}
ZoneId zoneId = !zone.isEmpty() ? ZoneId.of(zone) : ZoneId.systemDefault(); ZoneId zoneId = !zone.isEmpty() ? ZoneId.of(zone) : ZoneId.systemDefault();
ZonedDateTime zdt = LocalDateTime.now().atZone(zoneId); ZonedDateTime zdt = LocalDateTime.now().atZone(zoneId);
int delta = zdt.getOffset().getTotalSeconds(); int delta = zdt.getOffset().getTotalSeconds();
@ -346,4 +343,12 @@ public class ShellyUtils {
} }
return new DecimalType(strength); return new DecimalType(strength);
} }
public static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
public static char lastChar(String s) {
return s.length() > 1 ? s.charAt(s.length() - 1) : '*';
}
} }

View File

@ -163,6 +163,6 @@ public class ShellyVersionDTO {
return false; return false;
} }
return version.isEmpty() || version.contains("???") || version.toLowerCase().contains("master") return version.isEmpty() || version.contains("???") || version.toLowerCase().contains("master")
|| (version.toLowerCase().contains("-rc")); || (version.toLowerCase().contains("-rc") || version.toLowerCase().contains("beta"));
} }
} }

View File

@ -78,7 +78,7 @@
<default>0</default> <default>0</default>
</parameter> </parameter>
<parameter name="eventsRoller" type="boolean" required="false"> <parameter name="eventsRoller" type="boolean" required="false">
<label>@text/thing-type.config.shelly.roller.eventsRoller.label)</label> <label>@text/thing-type.config.shelly.roller.eventsRoller.label</label>
<description>@text/thing-type.config.shelly.roller.eventsRoller.description</description> <description>@text/thing-type.config.shelly.roller.eventsRoller.description</description>
<advanced>true</advanced> <advanced>true</advanced>
<default>false</default> <default>false</default>

View File

@ -18,7 +18,8 @@ message.config-status.error.missing-userid = No user ID in the Thing configurati
message.offline.conf-error-no-credentials = Device is password protected, but no credentials have been configured. message.offline.conf-error-no-credentials = Device is password protected, but no credentials have been configured.
message.offline.conf-error-access-denied = Access denied, check user id and password. message.offline.conf-error-access-denied = Access denied, check user id and password.
message.offline.conf-error-wrong-mode = Device is no longer in the configured device mode {0}, required {1}. Delete the thing and re-discover the device. message.offline.conf-error-wrong-mode = Device is no longer in the configured device mode {0}, required {1}. Delete the thing and re-discover the device.
message.offline.status-error-timeout = Device is not reachable (API timeout). message.offline.status-error-timeout = Device is not reachable (API timeout)
message.offline.status-error-unexpected-error = Unexpected error
message.offline.status-error-unexpected-api-result = An unexpected API response. Please verify the logfile to get more detailed information. message.offline.status-error-unexpected-api-result = An unexpected API response. Please verify the logfile to get more detailed information.
message.offline.status-error-watchdog = Device is not responding, seems to be unavailable. message.offline.status-error-watchdog = Device is not responding, seems to be unavailable.
message.offline.status-error-restarted = The device has restarted and will be re-initialized. message.offline.status-error-restarted = The device has restarted and will be re-initialized.
@ -31,12 +32,11 @@ message.versioncheck.tooold = WARNING: Firmware might be too old, installed: {0}
message.versioncheck.update = INFO: New firmware available: current version: {0}, new version: {1} message.versioncheck.update = INFO: New firmware available: current version: {0}, new version: {1}
message.versioncheck.autocoiot = INFO: Firmware is full-filling the minimum version to auto-enable CoIoT message.versioncheck.autocoiot = INFO: Firmware is full-filling the minimum version to auto-enable CoIoT
message.init.noipaddress = Unable to detect local IP address. Please make sure that IPv4 is enabled for this interface and check openHAB Network Configuration. message.init.noipaddress = Unable to detect local IP address. Please make sure that IPv4 is enabled for this interface and check openHAB Network Configuration.
message.init.protected = Device is password protected, enter correct credentials in thing configuration.
message.command.failed = ERROR: Unable to process command {0} for channel {1} message.command.failed = ERROR: Unable to process command {0} for channel {1}
message.command.init = Thing not yet initialized, command {0} triggers initialization message.command.init = Thing not yet initialized, command {0} triggered initialization
message.status.unknown.initializing = Initializing or device in sleep mode. message.status.unknown.initializing = Initializing or device in sleep mode.
message.statusupdate.failed = Unable to update status message.statusupdate.failed = Unable to update status
message.status.managerstarted = Shelly Manager started at http(s)://{0}:{1}/shelly/manager" message.status.managerstarted = Shelly Manager started at http(s)://{0}:{1}/shelly/manager
message.event.triggered = Event triggered: {0} message.event.triggered = Event triggered: {0}
message.event.filtered = Event filtered: {0} message.event.filtered = Event filtered: {0}
message.coap.init.failed = Unable to start CoIoT: {0} message.coap.init.failed = Unable to start CoIoT: {0}
@ -106,7 +106,6 @@ thing-type.config.shelly.eventsCoIoT.description = Activates the CoIoT-Protocol
thing-type.config.shelly.eventsSensorReport.label = Enable Sensor Events thing-type.config.shelly.eventsSensorReport.label = Enable Sensor Events
thing-type.config.shelly.eventsSensorReport.description = True: Register event URL for sensor updates. thing-type.config.shelly.eventsSensorReport.description = True: Register event URL for sensor updates.
# thing config - roller # thing config - roller
thing-type.config.shelly.roller.favoriteUP.label = Favorite ID for UP thing-type.config.shelly.roller.favoriteUP.label = Favorite ID for UP
thing-type.config.shelly.roller.favoriteUP.description = Specifies the favorite ID that is used during the command UP on the roller#control channel (use the Shelly App to configure favorites) thing-type.config.shelly.roller.favoriteUP.description = Specifies the favorite ID that is used during the command UP on the roller#control channel (use the Shelly App to configure favorites)
@ -167,6 +166,7 @@ channel-group-type.shelly.dimmerChannel.description = A Shelly Dimmer channel
channel-group-type.shelly.ix3Channel1.label = Input 1 channel-group-type.shelly.ix3Channel1.label = Input 1
channel-group-type.shelly.ix3Channel2.label = Input 2 channel-group-type.shelly.ix3Channel2.label = Input 2
channel-group-type.shelly.ix3Channel3.label = Input 3 channel-group-type.shelly.ix3Channel3.label = Input 3
channel-group-type.shelly.ix3Channel4.label = Input 4
channel-group-type.shelly.ix3Channel.description = Input Status channel-group-type.shelly.ix3Channel.description = Input Status
channel-group-type.shelly.rollerControl.label = Roller Control channel-group-type.shelly.rollerControl.label = Roller Control
channel-group-type.shelly.rollerControl.description = Controlling the roller mode channel-group-type.shelly.rollerControl.description = Controlling the roller mode
@ -183,9 +183,9 @@ channel-group-type.shelly.externalSensors.description = Temperatures from extern
channel-type.shelly.outputName.label = Output Name channel-type.shelly.outputName.label = Output Name
channel-type.shelly.outputName.description = Output/Channel Name as configured in the Shelly App channel-type.shelly.outputName.description = Output/Channel Name as configured in the Shelly App
channel-type.shelly.timerAutoOn.label = Auto-ON Timer channel-type.shelly.timerAutoOn.label = Auto-ON Timer
channel-type.shelly.timerAutoOn.description = When the relay is switched off, it will be switched on automatically after n seconds channel-type.shelly.timerAutoOn.description = When the output of the relay is switched on, it will be switched off automatically after n seconds
channel-type.shelly.timerAutoOff.label = Auto-OFF Timer channel-type.shelly.timerAutoOff.label = Auto-OFF Timer
channel-type.shelly.timerAutoOff.description = When the relay is switched on, it will be switched off automatically after n seconds channel-type.shelly.timerAutoOff.description = When the output of the relay is switched off, it will be switched on automatically after n seconds
channel-type.shelly.timerActive.label = Auto ON/OFF timer active channel-type.shelly.timerActive.label = Auto ON/OFF timer active
channel-type.shelly.timerActive.description = ON: A timer is active, OFF: no timer active channel-type.shelly.timerActive.description = ON: A timer is active, OFF: no timer active
channel-type.shelly.temperature.label = Temperature channel-type.shelly.temperature.label = Temperature
@ -208,9 +208,12 @@ channel-type.shelly.rollerFavorite.label = Position Favorite
channel-type.shelly.rollerFavorite.description = Set roller position by selecting favorite 1-4 (needs to be defined in the Shelly App, 0=n/a) channel-type.shelly.rollerFavorite.description = Set roller position by selecting favorite 1-4 (needs to be defined in the Shelly App, 0=n/a)
channel-type.shelly.rollerState.label = Roller State channel-type.shelly.rollerState.label = Roller State
channel-type.shelly.rollerState.description = State of the roller (open/close/stop) channel-type.shelly.rollerState.description = State of the roller (open/close/stop)
channel-type.shelly.rollerState.state.option.open = opening channel-type.shelly.rollerState.state.option.opening = opening
channel-type.shelly.rollerState.state.option.close = closing channel-type.shelly.rollerState.state.option.open = open
channel-type.shelly.rollerState.state.option.closing = closing
channel-type.shelly.rollerState.state.option.close = closed
channel-type.shelly.rollerState.state.option.stop = stopped channel-type.shelly.rollerState.state.option.stop = stopped
channel-type.shelly.rollerState.state.option.calibrating = calibrating
channel-type.shelly.rollerStop.label = Roller stop reason channel-type.shelly.rollerStop.label = Roller stop reason
channel-type.shelly.rollerStop.description = Last reason for stopping the Roller Shutter (normal, safety switch, obstacle detected) channel-type.shelly.rollerStop.description = Last reason for stopping the Roller Shutter (normal, safety switch, obstacle detected)
channel-type.shelly.rollerStop.state.option.normal = normal channel-type.shelly.rollerStop.state.option.normal = normal
@ -223,12 +226,12 @@ channel-type.shelly.rollerDirection.state.option.close = close
channel-type.shelly.rollerDirection.state.option.stop = stopped channel-type.shelly.rollerDirection.state.option.stop = stopped
channel-type.shelly.rollerSafety.label = Safety Switch channel-type.shelly.rollerSafety.label = Safety Switch
channel-type.shelly.rollerSafety.description = Status of the safety switch channel-type.shelly.rollerSafety.description = Status of the safety switch
channel-type.shelly.inputState.label = Input channel-type.shelly.inputState.label = Input/Button
channel-type.shelly.inputState.description = Input/Button state channel-type.shelly.inputState.description = Current state of the Input/Button
channel-type.shelly.inputState1.label = Input #1 channel-type.shelly.inputState1.label = Input #1
channel-type.shelly.inputState1.description = Input/Button state #1 channel-type.shelly.inputState1.description = Current state of the Input #1
channel-type.shelly.inputState2.label = Input #2 channel-type.shelly.inputState2.label = Input #2
channel-type.shelly.inputState2.description = Input/Button state #2 channel-type.shelly.inputState2.description = Current state of the Input #2
channel-type.shelly.dimmerBrightness.label = Brightness channel-type.shelly.dimmerBrightness.label = Brightness
channel-type.shelly.dimmerBrightness.description = Light Brightness in percent (0-100%, 0=OFF) channel-type.shelly.dimmerBrightness.description = Light Brightness in percent (0-100%, 0=OFF)
channel-type.shelly.whiteBrightness.label = Brightness channel-type.shelly.whiteBrightness.label = Brightness
@ -243,8 +246,8 @@ channel-type.shelly.meterAccuReturned.label = Accumulated Returned Power
channel-type.shelly.meterAccuReturned.description = Accumulated Returned Power in kW/h of the device (including all meters) channel-type.shelly.meterAccuReturned.description = Accumulated Returned Power in kW/h of the device (including all meters)
channel-type.shelly.meterReactive.label = Reactive Energy channel-type.shelly.meterReactive.label = Reactive Energy
channel-type.shelly.meterReactive.description = Instantaneous reactive power in Watts (W) channel-type.shelly.meterReactive.description = Instantaneous reactive power in Watts (W)
channel-type.shelly.lastPower1.label = Last Power #1 channel-type.shelly.lastPower1.label = Last Power
channel-type.shelly.lastPower1.description = Last power consumption #1 - one rounded minute channel-type.shelly.lastPower1.description = Rounded power consumption during last minute
channel-type.shelly.meterTotal.label = Total Energy Consumption channel-type.shelly.meterTotal.label = Total Energy Consumption
channel-type.shelly.meterTotal.description = Total energy consumption in kW/h since the device powered up (resets on restart) channel-type.shelly.meterTotal.description = Total energy consumption in kW/h since the device powered up (resets on restart)
channel-type.shelly.meterReturned.label = Total Returned Energy channel-type.shelly.meterReturned.label = Total Returned Energy
@ -398,7 +401,7 @@ channel-type.shelly.supplyVoltage.label = Supply Voltage
channel-type.shelly.supplyVoltage.description = External voltage supplied to the device channel-type.shelly.supplyVoltage.description = External voltage supplied to the device
channel-type.shelly.lastUpdate.label = Last Update channel-type.shelly.lastUpdate.label = Last Update
channel-type.shelly.lastUpdate.description = Timestamp of last status update channel-type.shelly.lastUpdate.description = Timestamp of last status update
channel-type.shelly.lastEvent.label = Event channel-type.shelly.lastEvent.label = Last Event
channel-type.shelly.lastEvent.description = Event Type (S=Short push, SS=Double-Short push, SSS=Triple-Short push, L=Long push, SL=Short-Long push, LS=Long-Short push) channel-type.shelly.lastEvent.description = Event Type (S=Short push, SS=Double-Short push, SSS=Triple-Short push, L=Long push, SL=Short-Long push, LS=Long-Short push)
channel-type.shelly.lastEvent.state.option.S = Short push channel-type.shelly.lastEvent.state.option.S = Short push
channel-type.shelly.lastEvent.state.option.SS = Double-Short push channel-type.shelly.lastEvent.state.option.SS = Double-Short push
@ -448,6 +451,9 @@ channel-type.shelly.sensorSleepTime.label = Sensor Sleep Time
channel-type.shelly.sensorSleepTime.description = The sensor will not send notifications and will not perform actions until the specified time expires (0=disable) channel-type.shelly.sensorSleepTime.description = The sensor will not send notifications and will not perform actions until the specified time expires (0=disable)
channel-type.shelly.deviceSchedule.label = Schedule active channel-type.shelly.deviceSchedule.label = Schedule active
channel-type.shelly.deviceSchedule.description = ON: A scheduled program is active channel-type.shelly.deviceSchedule.description = ON: A scheduled program is active
channel-type.shelly.system.power.label = Power
channel-type.shelly.system.button.label = Event Trigger
channel-type.shelly.system.brightness.label = Brightness
# Shelly Manager # Shelly Manager
message.manager.invalid-url = Invalid URL or syntax message.manager.invalid-url = Invalid URL or syntax

View File

@ -10,7 +10,6 @@
<channels> <channels>
<channel id="alarm" typeId="alarmTrigger"/> <channel id="alarm" typeId="alarmTrigger"/>
<channel id="wifiSignal" typeId="system.signal-strength"/> <channel id="wifiSignal" typeId="system.signal-strength"/>
<channel id="uptime" typeId="uptime"/>
</channels> </channels>
</channel-group-type> </channel-group-type>
@ -123,7 +122,7 @@
</state> </state>
</channel-type> </channel-type>
<channel-type id="supplyVoltage" advanced="true"> <channel-type id="supplyVoltage" advanced="true">
<item-type>Number:ElectricPotentia</item-type> <item-type>Number:ElectricPotential</item-type>
<label>@text/channel-type.shelly.supplyVoltage.label</label> <label>@text/channel-type.shelly.supplyVoltage.label</label>
<description>@text/channel-type.shelly.supplyVoltage.description</description> <description>@text/channel-type.shelly.supplyVoltage.description</description>
<category>Energy</category> <category>Energy</category>
@ -131,6 +130,7 @@
<tag>Measurement</tag> <tag>Measurement</tag>
<tag>Voltage</tag> <tag>Voltage</tag>
</tags> </tags>
<state readOnly="true" pattern="%.0f %unit%"></state>
</channel-type> </channel-type>
<channel-type id="selfTest"> <channel-type id="selfTest">
<item-type>String</item-type> <item-type>String</item-type>

View File

@ -379,7 +379,6 @@
<item-type>Dimmer</item-type> <item-type>Dimmer</item-type>
<label>@text/channel-type.shelly.rollerPosition.label</label> <label>@text/channel-type.shelly.rollerPosition.label</label>
<description>@text/channel-type.shelly.rollerPosition.description</description> <description>@text/channel-type.shelly.rollerPosition.description</description>
<category>Blinds</category>
<tags> <tags>
<tag>Measurement</tag> <tag>Measurement</tag>
<tag>Level</tag> <tag>Level</tag>

View File

@ -162,7 +162,7 @@
<item-type>String</item-type> <item-type>String</item-type>
<label>@text/channel-type.shelly.controlMode.label</label> <label>@text/channel-type.shelly.controlMode.label</label>
<description>@text/channel-type.shelly.controlMode.description</description> <description>@text/channel-type.shelly.controlMode.description</description>
<state> <state readOnly="false">
<options> <options>
<option value="manual">@text/channel-type.shelly.controlMode.state.option.manual</option> <option value="manual">@text/channel-type.shelly.controlMode.state.option.manual</option>
<option value="automatic">@text/channel-type.shelly.controlMode.state.option.automatic</option> <option value="automatic">@text/channel-type.shelly.controlMode.state.option.automatic</option>
@ -433,7 +433,7 @@
</channel-type> </channel-type>
<channel-type id="sensorPPM"> <channel-type id="sensorPPM">
<item-type>Number:Density</item-type> <item-type>Number:Dimensionless</item-type>
<label>@text/channel-type.shelly.sensorPPM.label</label> <label>@text/channel-type.shelly.sensorPPM.label</label>
<description>@text/channel-type.shelly.sensorPPM.description</description> <description>@text/channel-type.shelly.sensorPPM.description</description>
<category>Gas</category> <category>Gas</category>
@ -441,7 +441,7 @@
<tag>Measurement</tag> <tag>Measurement</tag>
<tag>Gas</tag> <tag>Gas</tag>
</tags> </tags>
<state readOnly="true" pattern="%.0f %unit%"> <state readOnly="true" pattern="%.0f ppm">
</state> </state>
</channel-type> </channel-type>