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

View File

@ -69,9 +69,29 @@ public class ShellyBindingConstants {
public static final String THING_TYPE_SHELLYSENSE_STR = "shellysense";
public static final String THING_TYPE_SHELLYTRV_STR = "shellytrv";
public static final String THING_TYPE_SHELLYMOTION_STR = "shellymotion";
public static final String THING_TYPE_SHELLYMOTION2_STR = "shellymotion2";
public static final String THING_TYPE_SHELLYBUTTON1_STR = "shellybutton1";
public static final String THING_TYPE_SHELLYBUTTON2_STR = "shellybutton2";
public static final String THING_TYPE_SHELLYUNI_STR = "shellyuni";
// Shelly Plus Seriens
public static final String THING_TYPE_SHELLYPLUS1_STR = "shellyplus1";
public static final String THING_TYPE_SHELLYPLUS1PM_STR = "shellyplus1pm";
public static final String THING_TYPE_SHELLYPLUS2PM_RELAY_STR = "shellyplus2pm-relay";
public static final String THING_TYPE_SHELLYPLUS2PM_ROLLER_STR = "shellyplus2pm-roller";
public static final String THING_TYPE_SHELLYPLUSI4_STR = "shellyplusi4";
public static final String THING_TYPE_SHELLYPLUSHT_STR = "shellyplusht";
public static final String THING_TYPE_SHELLYPLUSPLUGUS_STR = "shellyplusplugus";
// Shelly Pro Series
public static final String THING_TYPE_SHELLYPRO1_STR = "shellypro1";
public static final String THING_TYPE_SHELLYPRO1PM_STR = "shellypro1pm";
public static final String THING_TYPE_SHELLYPRO2_RELAY_STR = "shellypro2-relay";
public static final String THING_TYPE_SHELLYPRO2_ROLLER_STR = "shellypro2-roller";
public static final String THING_TYPE_SHELLYPRO2PM_RELAY_STR = "shellypro2pm-relay";
public static final String THING_TYPE_SHELLYPRO2PM_ROLLER_STR = "shellypro2pm-roller";
public static final String THING_TYPE_SHELLYPRO4PM_STR = "shellypro4pm";
public static final String THING_TYPE_SHELLYPROTECTED_STR = "shellydevice";
public static final String THING_TYPE_SHELLYUNKNOWN_STR = "shellyunknown";
@ -107,6 +127,24 @@ public class ShellyBindingConstants {
public static final String SHELLYDT_UNI = "SHUNI-1";
public static final String SHELLYDT_TRV = "SHTRV-01";
// Shelly Plus Series
public static final String SHELLYDT_PLUS1 = "SNSW-001X16EU";
public static final String SHELLYDT_PLUS1PM = "SNSW-001P16EU";
public static final String SHELLYDT_PLUS2PM_RELAY = "SNSW-002P16EU-relay";
public static final String SHELLYDT_PLUS2PM_ROLLER = "SNSW-002P16EU-roller";
public static final String SHELLYDT_PLUSPLUGUS = "SNPL-00116US";
public static final String SHELLYDT_PLUSI4 = "SNSN-0024X";
public static final String SHELLYDT_PLUSHT = "SNSN-0013A";
// Shelly Pro Series
public static final String SHELLYDT_PRO1 = "SPSW-001XE16EU";
public static final String SHELLYDT_PRO1PM = "SPSW-001PE16EU";
public static final String SHELLYDT_PRO2_RELAY = "SPSW-002XE16EU-relay";
public static final String SHELLYDT_PRO2_ROLLER = "SPSW-002XE16EU-roller";
public static final String SHELLYDT_PRO2PM_RELAY = "SPSW-002PE16EU-relay";
public static final String SHELLYDT_PRO2PM_ROLLER = "SPSW-002PE16EU-roller";
public static final String SHELLYDT_PRO4PM = "SPSW-004PE16EU";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_SHELLY1 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1_STR);
public static final ThingTypeUID THING_TYPE_SHELLY1L = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1L_STR);
@ -186,6 +224,7 @@ public class ShellyBindingConstants {
public static final String PROPERTY_DEV_NAME = "deviceName";
public static final String PROPERTY_DEV_TYPE = "deviceType";
public static final String PROPERTY_DEV_MODE = "deviceMode";
public static final String PROPERTY_DEV_GEN = "deviceGeneration";
public static final String PROPERTY_HWREV = "deviceHwRev";
public static final String PROPERTY_HWBATCH = "deviceHwBatch";
public static final String PROPERTY_UPDATE_PERIOD = "devUpdatePeriod";
@ -340,7 +379,7 @@ public class ShellyBindingConstants {
public static final String CHANNEL_BUTTON_TRIGGER2 = CHANNEL_BUTTON_TRIGGER + "2";
public static final String SERVICE_TYPE = "_http._tcp.local.";
public static final String SHELLY_API_MIN_FWVERSION = "v1.5.7";// v1.5.7+
public static final String SHELLY_API_MIN_FWVERSION = "v1.8.2";
public static final String SHELLY_API_MIN_FWCOIOT = "v1.6";// v1.6.0+
public static final String SHELLY_API_FWCOIOT2 = "v1.8";// CoAP 2 with FW 1.8+
public static final String SHELLY_API_FW_110 = "v1.10"; // FW 1.10 or newer detected, activates some add feature

View File

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

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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