[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
33 changed files with 817 additions and 280 deletions

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>