| shellyplugs | Shelly Plug-S | SHPLG-S |
| shellyem | Shelly EM with integrated Power Meters | SHEM |
| shellyem3 | Shelly 3EM with 3 integrated Power Meter | SHEM-3 |
-| shellyrgbw2 | Shelly RGB Controller | SHRGBW2 |
-| shellybulb | Shelly Bulb in Color or White Mode | SHBLB-1 |
+| shellyrgbw2-color | Shelly RGBW2 Controller in Color Mode | SHRGBW2 |
+| shellyrgbw2-white | Shelly RGBW2 Controller in White Mode | SHRGBW2 |
+| shellybulb-color | Shelly Bulb in Color Mode | SHBLB-1 |
+| shellybulb-white | Shelly Bulb in White Mode | SHBLB-1 |
| shellybulbduo | Shelly Duo White | SHBDUO-1 |
| shellybulbduo | Shelly Duo White G10 | SHBDUO-1 |
| shellycolorbulb | Shelly Duo Color G10 | SHCB-1 |
| shellyflood | Shelly Flood Sensor | SHWT-1 |
| shellysmoke | Shelly Smoke Sensor | SHSM-1 |
| shellymotion | Shelly Motion Sensor | SHMOS-01 |
+| shellymotion2 | Shelly Motion Sensor 2 | SHMOS-02 |
| shellygas | Shelly Gas Sensor | SHGS-1 |
| shellydw | Shelly Door/Window | SHDW-1 |
| shellydw2 | Shelly Door/Window 2 | SHDW-2 |
`true`: Brightness will be set and device output is powered = light turns on with the new brightness
`false`: Brightness will be set, but output stays unchanged so light will not be switched on when it's currently off.
+### Shelly RGBW2 in White Mode (thing-type: shellyrgbw2-color)
+
+|Group |Channel |Type |read-only|Description |
+|----------|-------------|---------|---------|-----------------------------------------------------------------------|
+|control |power |Switch |r/w |Switch light ON/OFF |
+| |input |Switch |yes |State of Input |
+| |autoOn |Number |r/w |Sets a timer to turn the device ON after every OFF; in sec |
+| |autoOff |Number |r/w |Sets a timer to turn the device OFF after every ON: in sec |
+| |timerActive |Switch |yes |ON: An auto-on/off timer is active |
+|color | | | |Color settings: only valid in COLOR mode |
+| |hsb |HSB |r/w |Represents the color picker (HSBType), control r/g/b, but not white |
+|meter |currentWatts |Number |yes |Current power consumption in Watts (all channels) |
+
+Please note that the settings of channel group color are only valid in color mode and vice versa for white mode.
+The current firmware doesn't support the timestamp report for the meters.
+The binding emulates this by using the system time on every update.
+
### Shelly RGBW2 in White Mode (thing-type: shellyrgbw2-white)
|Group |Channel |Type |read-only|Description |
You switch off the light and want to leave the room, but the motion sensor immediately switches light back on.
Using 'sensorSleepTime' you could suppress motion events while leaving the room, e.g. for 5sec and the light doesn's switch on.
+### Shelly Motion 2 (thing-type: shellymotion2)
+
+|Group |Channel |Type |read-only|Description |
+|----------|---------------|---------|---------|---------------------------------------------------------------------|
+|sensors |motion |Switch |yes |ON: Motion was detected |
+| |motionTimestamp|DateTime |yes |Time when motion started/was detected |
+| |lux |Number |yes |Brightness in Lux |
+| |illumination |String |yes |Current illumination: dark/twilight/bright |
+| |temperature |Number |yes |Temperature measured by the sensor |
+| |vibration |Switch |yes |ON: Vibration detected |
+| |charger |Switch |yes |ON: USB charging cable is connected external power supply activated. |
+| |motionActive |Switch |yes |ON: Motion detection is currently active |
+| |sensorSleepTime|Number |no |Specifies the number of sec the sensor should not report events ]
+| |lastUpdate |DateTime |yes |Timestamp of the last update (any sensor value changed) |
+|battery |batteryLevel |Number |yes |Battery Level in % |
+| |lowBattery |Switch |yes |Low battery alert (< 20%) |
+
### Shelly TRV (thing-type: shellytrv)
Note: You might need to reboot the device to enable the discovery mode for 3 minutes(use the Web UI).
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";
public static final String SHELLYDT_UNI = "SHUNI-1";
public static final String SHELLYDT_TRV = "SHTRV-01";
+ // Shelly Plus Series
+ public static final String SHELLYDT_PLUS1 = "SNSW-001X16EU";
+ public static final String SHELLYDT_PLUS1PM = "SNSW-001P16EU";
+ public static final String SHELLYDT_PLUS2PM_RELAY = "SNSW-002P16EU-relay";
+ public static final String SHELLYDT_PLUS2PM_ROLLER = "SNSW-002P16EU-roller";
+ public static final String SHELLYDT_PLUSPLUGUS = "SNPL-00116US";
+ public static final String SHELLYDT_PLUSI4 = "SNSN-0024X";
+ public static final String SHELLYDT_PLUSHT = "SNSN-0013A";
+
+ // Shelly Pro Series
+ public static final String SHELLYDT_PRO1 = "SPSW-001XE16EU";
+ public static final String SHELLYDT_PRO1PM = "SPSW-001PE16EU";
+ public static final String SHELLYDT_PRO2_RELAY = "SPSW-002XE16EU-relay";
+ public static final String SHELLYDT_PRO2_ROLLER = "SPSW-002XE16EU-roller";
+ public static final String SHELLYDT_PRO2PM_RELAY = "SPSW-002PE16EU-relay";
+ public static final String SHELLYDT_PRO2PM_ROLLER = "SPSW-002PE16EU-roller";
+ public static final String SHELLYDT_PRO4PM = "SPSW-004PE16EU";
+
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_SHELLY1 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1_STR);
public static final ThingTypeUID THING_TYPE_SHELLY1L = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1L_STR);
public static final 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";
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
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;
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;
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;
*/
@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;
}
logger.debug("Using OH HTTP port {}", httpPort);
+ this.thingTable = thingTable;
this.coapServer = new ShellyCoapServer();
// Promote Shelly Manager usage
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;
}
}
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;
}
/**
protected synchronized void removeHandler(@NonNull ThingHandler thingHandler) {
if (thingHandler instanceof ShellyBaseHandler) {
String uid = thingHandler.getThing().getUID().getAsString();
- deviceListeners.remove(uid);
+ thingTable.removeThing(uid);
}
}
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;
--- /dev/null
+/**
+ * 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;
+}
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
//
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";
//
// API values
//
+ public static final double SHELLY_API_INVTEMP = -999.0;
+
public static final String SHELLY_BTNT_MOMENTARY = "momentary";
public static final String SHELLY_BTNT_MOM_ON_RELEASE = "momentary_on_release";
public static final String SHELLY_BTNT_ONE_BUTTON = "one_button";
public static final String SHELLY_STATE_STOP = "stop";
public static final String SHELLY_INP_MODE_OPENCLOSE = "openclose";
+ public static final String SHELLY_INP_MODE_ONEBUTTON = "onebutton";
+
public static final String SHELLY_OBSTMODE_DISABLED = "disabled";
public static final String SHELLY_SAFETYM_WHILEOPENING = "while_opening";
public static 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")
public String pushLongUrl; // to access when roller stopped
@SerializedName("shortpush_url")
public String pushShortUrl; // to access when roller stopped
+
+ // Status information
+ public Boolean ison;
+ public Boolean overpower;
+ @SerializedName("is_valid")
+ public Boolean isValid;
+ @SerializedName("ext_temperature")
+ public ShellyStatusSensor.ShellyExtTemperature extTemperature; // Shelly 1/1PM: sensor values
+ @SerializedName("ext_humidity")
+ public ShellyStatusSensor.ShellyExtHumidity extHumidity; // Shelly 1/1PM: sensor values
}
public static class ShellySettingsDimmer {
public 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
// Roller with FW 1.9.2+
@SerializedName("favorites_enabled")
- public Boolean favoritesEnabled;
+ public Boolean favoritesEnabled = false;
public ArrayList<ShellyFavPos> favorites;
// Motion
public ShellyStatusMqtt mqtt;
public String time;
- public Integer serial;
+ public Integer serial = -1;
@SerializedName("has_update")
public Boolean hasUpdate;
public String mac;
// 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
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;
}
public ShellySettingsStatus status = new ShellySettingsStatus();
public String hostname = "";
+ public String name = "";
public String mode = "";
public boolean discoverable = true;
public boolean auth = false;
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()
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) {
// 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 "";
}
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) {
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);
* @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";
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);
}
* @return Initialized ShellyDeviceProfile
* @throws ShellyApiException
*/
+ @Override
public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException {
String json = request(SHELLY_URL_SETTINGS);
if (json.contains("\"type\":\"SHDM-")) {
return profile;
}
+ @Override
public boolean isInitialized() {
return profile.initialized;
}
* @return Device settings/status as ShellySettingsStatus object
* @throws ShellyApiException
*/
+ @Override
public ShellySettingsStatus getStatus() throws ShellyApiException {
String json = "";
try {
}
}
+ @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) {
return status;
}
+ @Override
public void setTimer(int index, String timerName, int value) throws ShellyApiException {
String type = SHELLY_CLASS_RELAY;
if (profile.isRoller) {
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) {
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));
}
return callApi(SHELLY_URL_SETTINGS_LIGHT, ShellySettingsLight.class);
}
+ @Override
public ShellyStatusLight getLightStatus() throws ShellyApiException {
return callApi(SHELLY_URL_STATUS, ShellyStatusLight.class);
}
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);
}
}
+ @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);
}
return callApi("/ota?" + uri, ShellySettingsUpdate.class);
}
+ @Override
public String setCloud(boolean enabled) throws ShellyApiException {
return callApi("/settings/cloud/?enabled=" + (enabled ? "1" : "0"), String.class);
}
* @param mode
* @throws ShellyApiException
*/
+ @Override
public void setLightMode(String mode) throws ShellyApiException {
if (!mode.isEmpty() && !profile.mode.equals(mode)) {
setLightSetting(SHELLY_API_MODE, mode);
* @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()) {
* @throws ShellyApiException
* @throws IllegalArgumentException
*/
+ @Override
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException {
String type = "";
if (profile.irCodes.containsKey(keyCode)) {
* @param ShellyApiException
* @throws ShellyApiException
*/
+ @Override
public void setActionURLs() throws ShellyApiException {
setRelayEvents();
setDimmerEvents();
return uri;
}
+ @Override
public int getTimeoutErrors() {
return timeoutErrors;
}
+ @Override
public int getTimeoutsRecovered() {
return timeoutsRecovered;
}
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;
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();
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;
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;
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);
}
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));
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;
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);
}
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;
// 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);
}
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) {
}
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;
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;
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;
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;
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;
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));
// 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);
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) {
public String localIp = ""; // local ip addresses used to create callback url
public String localPort = "8080";
+ public String serviceName = "";
}
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;
@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;
}
String model = "unknown";
String deviceName = "";
ThingUID thingUID = null;
- ShellyDeviceProfile profile = null;
+ ShellyDeviceProfile profile;
Map<String, Object> properties = new TreeMap<>();
name = service.getName().toLowerCase();
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);
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());
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);
}
// 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;
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;
* @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
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);
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
*/
}, 2, TimeUnit.SECONDS);
}
+ @Override
+ public ShellyThingConfiguration getThingConfig() {
+ return config;
+ }
+
/**
* This routine is called every time the Thing configuration has been changed
*/
}
// 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)) {
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;
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
*/
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);
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);
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:
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) {
// 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
@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);
@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);
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
// 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)) {
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();
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;
}
*
* @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);
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++;
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 {
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()) {
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;
}
* @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;
}
* @param status Shelly device status
* @return true: one or more inputs were updated
*/
+ @Override
public boolean updateInputs(ShellySettingsStatus status) {
boolean updated = false;
return updated;
}
+ @Override
public boolean updateWakeupReason(@Nullable List<Object> valueArray) {
boolean changed = false;
if (valueArray != null && !valueArray.isEmpty()) {
return changed;
}
+ @Override
public void triggerButton(String group, int idx, String value) {
String trigger = mapButtonEvent(value);
if (trigger.isEmpty()) {
}
}
+ @Override
public void publishState(String channelId, State value) {
String id = channelId.contains("$") ? substringBefore(channelId, "$") : channelId;
if (!stopping && isLinked(id)) {
}
}
+ @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);
}
return cache.getValue(group, channel);
}
+ @Override
public double getChannelDouble(String group, String channel) {
State value = getChannelValue(group, channel);
if (value != UnDefType.NULL) {
*
* @param thingHandler
*/
- protected void updateChannelDefinitions(Map<String, Channel> dynChannels) {
+ @Override
+ public void updateChannelDefinitions(Map<String, Channel> dynChannels) {
if (channelsCreated) {
return; // already done
}
}
}
+ @Override
public boolean areChannelsCreated() {
return channelsCreated;
}
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);
}
* @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)) {
* @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));
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 {
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);
}
@Override
- public ShellyHttpApi getApi() {
+ public ShellyApiInterface getApi() {
return api;
}
return stats.asProperties();
}
+ @Override
+ public long getScheduledUpdates() {
+ return scheduledUpdates;
+ }
+
public String checkForUpdate() {
try {
ShellyOtaCheckResult result = api.checkForUpdate();
return "";
}
}
+
+ @Override
+ public void triggerUpdateFromCoap() {
+ if ((!autoCoIoT && (getScheduledUpdates() < 1)) || (autoCoIoT && !profile.isLight && !profile.hasBattery)) {
+ requestUpdates(1, false);
+ }
+ }
}
* @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));
* @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;
// 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) {
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));
}
} 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) {
}
}
}
- // 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));
}
// 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;
}
*
* @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));
}
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);
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);
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));
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) {
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);
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);
}
}
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);
}
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) {
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);
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;
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
- public ShellyHttpApi getApi();
+ public ShellyApiInterface getApi();
public ShellyDeviceStats getStats();
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)
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);
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++;
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
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;
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;
!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");
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);
}
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;
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;
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
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);
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)) {
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;
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());
}
}
}
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() {
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;
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) {
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();
}
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) : '*';
+ }
}
return false;
}
return version.isEmpty() || version.contains("???") || version.toLowerCase().contains("master")
- || (version.toLowerCase().contains("-rc"));
+ || (version.toLowerCase().contains("-rc") || version.toLowerCase().contains("beta"));
}
}
<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>
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.
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}
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)
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
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
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
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
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
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
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
<channels>
<channel id="alarm" typeId="alarmTrigger"/>
<channel id="wifiSignal" typeId="system.signal-strength"/>
- <channel id="uptime" typeId="uptime"/>
</channels>
</channel-group-type>
</state>
</channel-type>
<channel-type id="supplyVoltage" advanced="true">
- <item-type>Number:ElectricPotentia</item-type>
+ <item-type>Number:ElectricPotential</item-type>
<label>@text/channel-type.shelly.supplyVoltage.label</label>
<description>@text/channel-type.shelly.supplyVoltage.description</description>
<category>Energy</category>
<tag>Measurement</tag>
<tag>Voltage</tag>
</tags>
+ <state readOnly="true" pattern="%.0f %unit%"></state>
</channel-type>
<channel-type id="selfTest">
<item-type>String</item-type>
<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>
<item-type>String</item-type>
<label>@text/channel-type.shelly.controlMode.label</label>
<description>@text/channel-type.shelly.controlMode.description</description>
- <state>
+ <state readOnly="false">
<options>
<option value="manual">@text/channel-type.shelly.controlMode.state.option.manual</option>
<option value="automatic">@text/channel-type.shelly.controlMode.state.option.automatic</option>
</channel-type>
<channel-type id="sensorPPM">
- <item-type>Number:Density</item-type>
+ <item-type>Number:Dimensionless</item-type>
<label>@text/channel-type.shelly.sensorPPM.label</label>
<description>@text/channel-type.shelly.sensorPPM.description</description>
<category>Gas</category>
<tag>Measurement</tag>
<tag>Gas</tag>
</tags>
- <state readOnly="true" pattern="%.0f %unit%">
+ <state readOnly="true" pattern="%.0f ppm">
</state>
</channel-type>