Otherwise a Thing of type shellyprotected is created in the Inbox and you could set the credentials while adding the Thing.
In this case the credentials are persisted as part of the Thing configuration.
+### Range Extender Mode
+
+The Plus/Pro devices support the so-called Range Extender Mode (not available for Gen1).
+This allows connect Shellys, which are normally no reachable, because of a lack of WiFi signal.
+Once enabled the Shelly acts as a hub to the linked devices, like a WiFi repeater.
+The hub device enables the access point, which can be seen by the linked device.
+The binding could then get access to the secondary device using <ub shelly ip>:<special port>.
+A special port on the hub device will be created for every linked device so one hub device could supported multiple linked devices.
+
+
+The binding communicates with the Shelly hub device, which then forwards the request to the secondary device.
+Once the thing for the primary Shelly goes online the binding detects the enabled range extender mode and adds all connected secondary devices to the Inbox.
+This means: The primary Shelly has to complete initialization before linked secondary devices are discovered.
+
+- Discover primary/hub Shelly
+- Add thing and wait until it goes ONLINE
+- Check Inbox to find the secondary/linked devices
+- Add secondary device as usual
+
+If you are adding another secondary device to the same hub device you need to suspend and resume the primary thing, this will run a new initialization and adds the new secondary device to the Inbox.
+
### Dynamic creation of channels
The Shelly series of devices has many combinations of relays, meters (different versions), sensors etc.
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor.ShellyMotionSettings;
+import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2APClientList;
import org.openhab.core.thing.CommonTriggerEvents;
import com.google.gson.annotations.SerializedName;
public Boolean wifiRecoveryReboot; // FW 1.10+
@SerializedName("ap_roaming")
public ShellyApRoaming apRoaming; // FW 1.10+
+ public Boolean rangeExtender; // Gen2: Range extender
public ShellySettingsMqtt mqtt = new ShellySettingsMqtt();
public ShellySettingsSntp sntp = new ShellySettingsSntp();
// /settings/sta for details
public ShellyStatusCloud cloud = new ShellyStatusCloud();
public ShellyStatusMqtt mqtt = new ShellyStatusMqtt();
+ public Shelly2APClientList rangeExtender;
public String time;
public Integer serial = -1;
public static final String SHELLYRPC_METHOD_LED_SETCONFIG = "WD_UI.SetConfig";
public static final String SHELLYRPC_METHOD_WIFIGETCONG = "Wifi.GetConfig";
public static final String SHELLYRPC_METHOD_WIFISETCONG = "Wifi.SetConfig";
+ public static final String SHELLYRPC_METHOD_WIFILISTAPCLIENTS = "WiFi.ListAPClients";
public static final String SHELLYRPC_METHOD_ETHGETCONG = "Eth.GetConfig";
public static final String SHELLYRPC_METHOD_ETHSETCONG = "Eth.SetConfig";
public static final String SHELLYRPC_METHOD_BLEGETCONG = "BLE.GetConfig";
public Shelly2GetConfigResult result;
}
+ public static class Shelly2APClientList {
+ public static class Shelly2APClient {
+ public String mac;
+ public String ip;
+ @SerializedName("ip_static")
+ public Boolean staticIP;
+ public Integer mport;
+ public Long since;
+ }
+
+ public Long ts;
+ @SerializedName("ap_clients")
+ public ArrayList<Shelly2APClient> apClients;
+ }
+
public static class Shelly2DeviceStatus {
public class Shelly2InputCounts {
public Integer total;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusLight;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusRelay;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyStatusSensor;
+import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2APClientList;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2AuthChallenge;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2ConfigParms;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2DeviceConfig.Shelly2DeviceConfigSta;
@Override
public void startScan() {
try {
- installScript(SHELLY2_BLU_GWSCRIPT, config.enableBluGateway);
+ if (getProfile().isBlu) {
+ installScript(SHELLY2_BLU_GWSCRIPT, config.enableBluGateway);
+ }
} catch (ShellyApiException e) {
}
}
profile.settings.wifiSta1 = new ShellySettingsWiFiNetwork();
fillWiFiSta(dc.wifi.sta, profile.settings.wifiSta);
fillWiFiSta(dc.wifi.sta1, profile.settings.wifiSta1);
+ if (dc.wifi.ap != null && dc.wifi.ap.rangeExtender != null) {
+ profile.settings.rangeExtender = getBool(dc.wifi.ap.rangeExtender.enable);
+ }
profile.numMeters = 0;
if (profile.hasRelays) {
}
fillDeviceStatus(status, ds, false);
+ if (getBool(profile.settings.rangeExtender)) {
+ try {
+ // Get List of AP clients
+ profile.status.rangeExtender = apiRequest(SHELLYRPC_METHOD_WIFILISTAPCLIENTS, null,
+ Shelly2APClientList.class);
+ logger.debug("{}: Range extender is enabled, {} clients connected", thingName,
+ profile.status.rangeExtender.apClients.size());
+ } catch (ShellyApiException e) {
+ logger.debug("{}: Range extender is enabled, but unable to read AP client list", thingName, e);
+ profile.settings.rangeExtender = false;
+ }
+ }
+
return status;
}
public String serviceName = "";
public Boolean enableBluGateway = false;
+ public Boolean enableRangeExtender = true;
+
+ @Override
+ public String toString() {
+ return "Device address=" + deviceAddress + ", HTTP user/password=" + userId + "/"
+ + (password.isEmpty() ? "<none>" : "***") + ", update interval=" + updateInterval + "\n"
+ + "Events: Button: " + eventsButton + ", Switch (on/off): " + eventsSwitch + ", Push: " + eventsPush
+ + ", Roller: " + eventsRoller + "Sensor: " + eventsSensorReport + ", CoIoT: " + eventsCoIoT + "\n"
+ + "Blu Gateway=" + enableBluGateway + ", Range Extender: " + enableRangeExtender;
+ }
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 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.discovery;
+
+import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
+import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
+import static org.openhab.core.thing.Thing.*;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.shelly.internal.api.ShellyApiException;
+import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
+import org.openhab.binding.shelly.internal.api.ShellyApiResult;
+import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
+import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
+import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
+import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc;
+import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
+import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
+import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
+import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
+import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Device discovery creates a thing in the inbox for each vehicle
+ * found in the data received from {@link ShellyBasicDiscoveryService}.
+ *
+ * @author Markus Michels - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class ShellyBasicDiscoveryService extends AbstractDiscoveryService {
+ private final Logger logger = LoggerFactory.getLogger(ShellyBasicDiscoveryService.class);
+
+ private final BundleContext bundleContext;
+ private final ShellyThingTable thingTable;
+ private static final int TIMEOUT = 10;
+ private @Nullable ServiceRegistration<?> discoveryService;
+
+ public ShellyBasicDiscoveryService(BundleContext bundleContext, ShellyThingTable thingTable) {
+ super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT);
+ this.bundleContext = bundleContext;
+ this.thingTable = thingTable;
+ }
+
+ public void registerDeviceDiscoveryService() {
+ if (discoveryService == null) {
+ discoveryService = bundleContext.registerService(DiscoveryService.class.getName(), this, new Hashtable<>());
+ }
+ }
+
+ @Override
+ protected void startScan() {
+ logger.debug("Starting BLU Discovery");
+ thingTable.startScan();
+ }
+
+ public void discoveredResult(ThingTypeUID tuid, String model, String serviceName, String address,
+ Map<String, Object> properties) {
+ ThingUID uid = ShellyThingCreator.getThingUID(serviceName, model, "", true);
+ logger.debug("Adding discovered thing with id {}", uid.toString());
+ properties.put(PROPERTY_MAC_ADDRESS, address);
+ String thingLabel = "Shelly BLU " + model + " (" + serviceName + ")";
+ DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
+ .withRepresentationProperty(PROPERTY_DEV_NAME).withLabel(thingLabel).build();
+ thingDiscovered(result);
+ }
+
+ public void discoveredResult(DiscoveryResult result) {
+ thingDiscovered(result);
+ }
+
+ public void unregisterDeviceDiscoveryService() {
+ if (discoveryService != null) {
+ discoveryService.unregister();
+ }
+ }
+
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ unregisterDeviceDiscoveryService();
+ }
+
+ public static @Nullable DiscoveryResult createResult(boolean gen2, String hostname, String ipAddress,
+ ShellyBindingConfiguration bindingConfig, HttpClient httpClient, ShellyTranslationProvider messages) {
+ Logger logger = LoggerFactory.getLogger(ShellyBasicDiscoveryService.class);
+ ThingUID thingUID = null;
+ ShellyDeviceProfile profile;
+ ShellySettingsDevice devInfo;
+ ShellyApiInterface api = null;
+ boolean auth = false;
+ String mac = "";
+ String model = "";
+ String mode = "";
+ String name = hostname;
+ String deviceName = "";
+ String thingType = "";
+ Map<String, Object> properties = new TreeMap<>();
+
+ try {
+ ShellyThingConfiguration config = fillConfig(bindingConfig, ipAddress);
+ api = gen2 ? new Shelly2ApiRpc(name, config, httpClient) : new Shelly1HttpApi(name, config, httpClient);
+ api.initialize();
+ devInfo = api.getDeviceInfo();
+ mac = getString(devInfo.mac);
+ model = devInfo.type;
+ auth = getBool(devInfo.auth);
+ if (name.isEmpty() || name.startsWith("shellyplusrange")) {
+ name = devInfo.hostname;
+ }
+ if (devInfo.name != null) {
+ deviceName = devInfo.name;
+ }
+
+ thingType = substringBeforeLast(name, "-");
+ profile = api.getDeviceProfile(thingType, devInfo);
+ api.close();
+ deviceName = profile.name;
+ mode = devInfo.mode;
+ properties = ShellyBaseHandler.fillDeviceProperties(profile);
+
+ // get thing type from device name
+ thingUID = ShellyThingCreator.getThingUID(name, model, mode, false);
+ } catch (ShellyApiException e) {
+ ShellyApiResult result = e.getApiResult();
+ if (result.isHttpAccessUnauthorized()) {
+ // create shellyunknown thing - will be changed during thing initialization with valid credentials
+ thingUID = ShellyThingCreator.getThingUID(name, model, mode, true);
+ }
+ } catch (IllegalArgumentException | IOException e) { // maybe some format description was buggy
+ logger.debug("Discovery: Unable to discover thing", e);
+ } finally {
+ if (api != null) {
+ api.close();
+ }
+ }
+
+ if (thingUID != null) {
+ addProperty(properties, PROPERTY_MAC_ADDRESS, mac);
+ addProperty(properties, CONFIG_DEVICEIP, ipAddress);
+ addProperty(properties, PROPERTY_MODEL_ID, model);
+ addProperty(properties, PROPERTY_SERVICE_NAME, name);
+ addProperty(properties, PROPERTY_DEV_NAME, deviceName);
+ addProperty(properties, PROPERTY_DEV_TYPE, thingType);
+ addProperty(properties, PROPERTY_DEV_GEN, gen2 ? "2" : "1");
+ addProperty(properties, PROPERTY_DEV_MODE, mode);
+ addProperty(properties, PROPERTY_DEV_AUTH, auth ? "yes" : "no");
+
+ String thingLabel = deviceName.isEmpty() ? name + " - " + ipAddress
+ : deviceName + " (" + name + "@" + ipAddress + ")";
+ return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(thingLabel)
+ .withRepresentationProperty(PROPERTY_SERVICE_NAME).build();
+ }
+
+ return null;
+ }
+
+ public static ShellyThingConfiguration fillConfig(ShellyBindingConfiguration bindingConfig, String address)
+ throws IOException {
+ ShellyThingConfiguration config = new ShellyThingConfiguration();
+ config.deviceIp = address;
+ config.userId = bindingConfig.defaultUserId;
+ config.password = bindingConfig.defaultPassword;
+ return config;
+ }
+
+ private static void addProperty(Map<String, Object> properties, String key, @Nullable String value) {
+ properties.put(key, value != null ? value : "");
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 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.discovery;
-
-import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
-import static org.openhab.core.thing.Thing.PROPERTY_MAC_ADDRESS;
-
-import java.util.Hashtable;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.shelly.internal.handler.ShellyThingTable;
-import org.openhab.core.config.discovery.AbstractDiscoveryService;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
-import org.openhab.core.config.discovery.DiscoveryService;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.ThingUID;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Device discovery creates a thing in the inbox for each vehicle
- * found in the data received from {@link ShellyBluDiscoveryService}.
- *
- * @author Markus Michels - Initial Contribution
- *
- */
-@NonNullByDefault
-public class ShellyBluDiscoveryService extends AbstractDiscoveryService {
- private final Logger logger = LoggerFactory.getLogger(ShellyBluDiscoveryService.class);
-
- private final BundleContext bundleContext;
- private final ShellyThingTable thingTable;
- private static final int TIMEOUT = 10;
- private @Nullable ServiceRegistration<?> discoveryService;
-
- public ShellyBluDiscoveryService(BundleContext bundleContext, ShellyThingTable thingTable) {
- super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT);
- this.bundleContext = bundleContext;
- this.thingTable = thingTable;
- }
-
- @SuppressWarnings("null")
- public void registerDeviceDiscoveryService() {
- if (discoveryService == null) {
- discoveryService = bundleContext.registerService(DiscoveryService.class.getName(), this, new Hashtable<>());
- }
- }
-
- @Override
- protected void startScan() {
- logger.debug("Starting BLU Discovery");
- thingTable.startScan();
- }
-
- public void discoveredResult(ThingTypeUID tuid, String model, String serviceName, String address,
- Map<String, Object> properties) {
- ThingUID uid = ShellyThingCreator.getThingUID(serviceName, model, "", true);
- logger.debug("Adding discovered thing with id {}", uid.toString());
- properties.put(PROPERTY_MAC_ADDRESS, address);
- String thingLabel = "Shelly BLU " + model + " (" + serviceName + ")";
- DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
- .withRepresentationProperty(PROPERTY_DEV_NAME).withLabel(thingLabel).build();
- thingDiscovered(result);
- }
-
- public void unregisterDeviceDiscoveryService() {
- if (discoveryService != null) {
- discoveryService.unregister();
- }
- }
-
- @Override
- public void deactivate() {
- super.deactivate();
- unregisterDeviceDiscoveryService();
- }
-}
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
-import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID;
import java.io.IOException;
import java.net.Inet4Address;
-import java.util.Map;
import java.util.Set;
-import java.util.TreeMap;
import javax.jmdns.ServiceInfo;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
-import org.openhab.binding.shelly.internal.api.ShellyApiException;
-import org.openhab.binding.shelly.internal.api.ShellyApiInterface;
-import org.openhab.binding.shelly.internal.api.ShellyApiResult;
-import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
-import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
-import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
-import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc;
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
-import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
return null;
}
- String address = "";
try {
- String mode = "";
- String model = "unknown";
- String deviceName = "";
- ThingUID thingUID = null;
- ShellyDeviceProfile profile;
- Map<String, Object> properties = new TreeMap<>();
-
+ String address = "";
name = service.getName().toLowerCase();
Inet4Address[] hostAddresses = service.getInet4Addresses();
if ((hostAddresses != null) && (hostAddresses.length > 0)) {
String gen = getString(service.getPropertyString("gen"));
boolean gen2 = "2".equals(gen) || "3".equals(gen);
- ShellyApiInterface api = null;
- boolean auth = false;
- ShellySettingsDevice devInfo;
- try {
- api = gen2 ? new Shelly2ApiRpc(name, config, httpClient) : new Shelly1HttpApi(name, config, httpClient);
- api.initialize();
- devInfo = api.getDeviceInfo();
- model = devInfo.type;
- gen2 = !(devInfo.gen == 1); // gen 2+3
- auth = getBool(devInfo.auth);
- if (devInfo.name != null) {
- deviceName = devInfo.name;
- }
-
- profile = api.getDeviceProfile(thingType, devInfo);
- api.close();
- logger.debug("{}: Shelly settings : {}", name, profile.settingsJson);
- deviceName = profile.name;
- mode = devInfo.mode;
- properties = ShellyBaseHandler.fillDeviceProperties(profile);
- logger.trace("{}: thingType={}, deviceType={}, mode={}, symbolic name={}", name, thingType,
- devInfo.type, mode.isEmpty() ? "<standard>" : mode, deviceName);
-
- // get thing type from device name
- thingUID = ShellyThingCreator.getThingUID(name, model, mode, false);
- } catch (ShellyApiException e) {
- ShellyApiResult result = e.getApiResult();
- if (result.isHttpAccessUnauthorized()) {
- logger.info("{}: {}", name, messages.get("discovery.protected", address));
-
- // create shellyunknown thing - will be changed during thing initialization with valid credentials
- thingUID = ShellyThingCreator.getThingUID(name, model, mode, true);
- } else {
- logger.debug("{}: {}", name, messages.get("discovery.failed", address, e.toString()));
- }
- } catch (IllegalArgumentException e) { // maybe some format description was buggy
- logger.debug("{}: Discovery failed!", name, e);
- } finally {
- if (api != null) {
- api.close();
- }
- }
-
- if (thingUID != null) {
- addProperty(properties, CONFIG_DEVICEIP, address);
- addProperty(properties, PROPERTY_MODEL_ID, model);
- addProperty(properties, PROPERTY_SERVICE_NAME, name);
- addProperty(properties, PROPERTY_DEV_NAME, deviceName);
- addProperty(properties, PROPERTY_DEV_TYPE, thingType);
- addProperty(properties, PROPERTY_DEV_GEN, gen2 ? "2" : "1");
- addProperty(properties, PROPERTY_DEV_MODE, mode);
- addProperty(properties, PROPERTY_DEV_AUTH, auth ? "yes" : "no");
-
- logger.debug("{}: Adding Shelly {}, UID={}", name, deviceName, thingUID.getAsString());
- String thingLabel = deviceName.isEmpty() ? name + " - " + address
- : deviceName + " (" + name + "@" + address + ")";
- return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(thingLabel)
- .withRepresentationProperty(PROPERTY_SERVICE_NAME).build();
- }
+ return ShellyBasicDiscoveryService.createResult(gen2, name, address, bindingConfig, httpClient, messages);
} catch (IOException | NullPointerException e) {
// maybe some format description was buggy
logger.debug("{}: Exception on processing serviceInfo '{}'", name, service.getNiceTextString(), e);
return null;
}
- private void addProperty(Map<String, Object> properties, String key, @Nullable String value) {
- properties.put(key, value != null ? value : "");
- }
-
@Nullable
@Override
public ThingUID getThingUID(@Nullable ServiceInfo service) throws IllegalArgumentException {
public static final String THING_TYPE_SHELLYPLUSI4DC_STR = "shellyplusi4dc";
public static final String THING_TYPE_SHELLYPLUSHT_STR = "shellyplusht";
public static final String THING_TYPE_SHELLYPLUSSMOKE_STR = "shellyplussmoke";
+ public static final String THING_TYPE_SHELLYPLUSUNI_STR = "shellyplusuni";
public static final String THING_TYPE_SHELLYPLUSPLUGS_STR = "shellyplusplug";
public static final String THING_TYPE_SHELLYPLUSPLUGUS_STR = "shellyplusplugus";
public static final String THING_TYPE_SHELLYPLUSDIMMERUS_STR = "shellypluswdus";
THING_TYPE_MAPPING.put(SHELLYDT_PLUSI4, THING_TYPE_SHELLYPLUSI4_STR);
THING_TYPE_MAPPING.put(SHELLYDT_PLUSHT, THING_TYPE_SHELLYPLUSHT_STR);
THING_TYPE_MAPPING.put(SHELLYDT_PLUSSMOKE, THING_TYPE_SHELLYPLUSSMOKE_STR);
+ THING_TYPE_MAPPING.put(SHELLYDT_PLUSUNI, THING_TYPE_SHELLYUNI_STR);
THING_TYPE_MAPPING.put(SHELLYDT_PLUSDIMMERUS, THING_TYPE_SHELLYPLUSDIMMERUS_STR);
THING_TYPE_MAPPING.put(SHELLYDT_PLUSDIMMER10V, THING_TYPE_SHELLYPLUSDIMMER10V_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSI4_STR, THING_TYPE_SHELLYPLUSI4_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSHT_STR, THING_TYPE_SHELLYPLUSHT_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSSMOKE_STR, THING_TYPE_SHELLYPLUSSMOKE_STR);
+ THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSUNI_STR, THING_TYPE_SHELLYUNI_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSDIMMERUS_STR, THING_TYPE_SHELLYPLUSDIMMERUS_STR);
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPLUSDIMMER10V_STR, THING_TYPE_SHELLYPLUSDIMMER10V_STR);
import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO;
import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer;
import org.openhab.binding.shelly.internal.api1.Shelly1HttpApi;
+import org.openhab.binding.shelly.internal.api2.Shelly2ApiJsonDTO.Shelly2APClientList.Shelly2APClient;
import org.openhab.binding.shelly.internal.api2.Shelly2ApiRpc;
import org.openhab.binding.shelly.internal.api2.ShellyBluApi;
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
+import org.openhab.binding.shelly.internal.discovery.ShellyBasicDiscoveryService;
import org.openhab.binding.shelly.internal.discovery.ShellyThingCreator;
import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.util.ShellyChannelCache;
import org.openhab.binding.shelly.internal.util.ShellyVersionDTO;
+import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.OpenClosedType;
protected final ShellyApiInterface api;
private final HttpClient httpClient;
+ private final ShellyThingTable thingTable;
private ShellyBindingConfiguration bindingConfig;
protected ShellyThingConfiguration config = new ShellyThingConfiguration();
final Shelly1CoapServer coapServer, final HttpClient httpClient) {
super(thing);
+ this.thingTable = thingTable;
this.thingName = getString(thing.getLabel());
this.messages = translationProvider;
this.cache = new ShellyChannelCache(this);
initJob = scheduler.schedule(() -> {
boolean start = true;
try {
- initializeThingConfig();
- logger.debug("{}: Device config: Device address={}, HTTP user/password={}/{}, update interval={}",
- thingName, config.deviceAddress, config.userId.isEmpty() ? "<non>" : config.userId,
- config.password.isEmpty() ? "<none>" : "***", config.updateInterval);
- logger.debug(
- "{}: Configured Events: Button: {}, Switch (on/off): {}, Push: {}, Roller: {}, Sensor: {}, CoIoT: {}, Enable AutoCoIoT: {}",
- thingName, config.eventsButton, config.eventsSwitch, config.eventsPush, config.eventsRoller,
- config.eventsSensorReport, config.eventsCoIoT, bindingConfig.autoCoIoT);
- start = initializeThing();
+ if (initializeThingConfig()) {
+ logger.debug("{}: Config: {}", thingName, config);
+ start = initializeThing();
+ }
} catch (ShellyApiException e) {
start = handleApiException(e);
} catch (IllegalArgumentException e) {
if (api.isInitialized()) {
api.startScan();
}
+
+ checkRangeExtender(profile);
}
/**
updateProperties(tmpPrf, tmpPrf.status);
checkVersion(tmpPrf, tmpPrf.status);
+ // Check for Range Extender mode, add secondary device to Inbox
+ checkRangeExtender(tmpPrf);
+
startCoap(config, tmpPrf);
if (!gen2 && !blu) {
api.setActionURLs(); // register event urls
}
}
+ private void checkRangeExtender(ShellyDeviceProfile prf) {
+ if (getBool(prf.settings.rangeExtender) && config.enableRangeExtender && prf.status.rangeExtender != null
+ && prf.status.rangeExtender.apClients != null) {
+ for (Shelly2APClient client : profile.status.rangeExtender.apClients) {
+ String secondaryIp = config.deviceIp + ":" + client.mport.toString();
+ String name = "shellyplusrange-" + client.mac.replaceAll(":", "");
+ DiscoveryResult result = ShellyBasicDiscoveryService.createResult(true, name, secondaryIp,
+ bindingConfig, httpClient, messages);
+ if (result != null) {
+ thingTable.discoveredResult(result);
+ }
+ }
+ }
+ }
+
private void showThingConfig(ShellyDeviceProfile profile) {
logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {}", thingName,
profile.device.hostname, profile.device.type, profile.hwRev, profile.hwBatchId, profile.fwVersion,
/**
* Initialize the binding's thing configuration, calc update counts
*/
- protected void initializeThingConfig() {
+ protected boolean initializeThingConfig() {
thingType = getThing().getThingTypeUID().getId();
final Map<String, String> properties = getThing().getProperties();
thingName = getString(properties.get(PROPERTY_SERVICE_NAME));
if (config.deviceAddress.isEmpty()) {
logger.debug("{}: IP/MAC address for the device must not be empty", thingName); // may not set in .things
// file
- return;
+ return false;
}
config.deviceAddress = config.deviceAddress.toLowerCase().replace(":", ""); // remove : from MAC address and
// convert to lower case
if (!config.deviceIp.isEmpty()) {
try {
- InetAddress addr = InetAddress.getByName(config.deviceIp);
+ String ip = config.deviceIp.contains(":") ? substringBefore(config.deviceIp, ":") : config.deviceIp;
+ String port = config.deviceIp.contains(":") ? substringAfter(config.deviceIp, ":") : "";
+ InetAddress addr = InetAddress.getByName(ip);
String saddr = addr.getHostAddress();
- if (!config.deviceIp.equals(saddr)) {
+ if (!ip.equals(saddr)) {
logger.debug("{}: hostname {} resolved to IP address {}", thingName, config.deviceIp, saddr);
- config.deviceIp = saddr;
+ config.deviceIp = saddr + (port.isEmpty() ? ip : ip + ":" + port);
}
} catch (UnknownHostException e) {
logger.debug("{}: Unable to resolve hostname {}", thingName, config.deviceIp);
if (config.localIp.startsWith("169.254")) {
setThingOffline(ThingStatusDetail.COMMUNICATION_ERROR, "config-status.error.network-config",
config.localIp);
- return;
+ return false;
}
if (!profile.isGen2 && config.userId.isEmpty() && !bindingConfig.defaultUserId.isEmpty()) {
skipCount = config.updateInterval / UPDATE_STATUS_INTERVAL_SECONDS;
logger.trace("{}: updateInterval = {}s -> skipCount = {}", thingName, config.updateInterval, skipCount);
+ return true;
}
private void checkVersion(ShellyDeviceProfile prf, ShellySettingsStatus status) {
properties.replace(PROPERTY_DEV_MODE, mode);
updateProperties(properties);
changeThingType(thingTypeUID, getConfig());
+ } else {
+ logger.debug("{}: to {}", thingName, thingType);
+ setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "Unable to change thing type to " + thingType);
}
}
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.shelly.internal.discovery.ShellyBluDiscoveryService;
+import org.openhab.binding.shelly.internal.discovery.ShellyBasicDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.thing.ThingTypeUID;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Component;
@Component(service = ShellyThingTable.class, configurationPolicy = ConfigurationPolicy.OPTIONAL)
public class ShellyThingTable {
private Map<String, ShellyThingInterface> thingTable = new ConcurrentHashMap<>();
- private @Nullable ShellyBluDiscoveryService bluDiscoveryService;
+ private @Nullable ShellyBasicDiscoveryService discoveryService;
public void addThing(String key, ShellyThingInterface thing) {
if (thingTable.containsKey(key)) {
}
public void startDiscoveryService(BundleContext bundleContext) {
- if (bluDiscoveryService == null) {
- bluDiscoveryService = new ShellyBluDiscoveryService(bundleContext, this);
- bluDiscoveryService.registerDeviceDiscoveryService();
+ if (discoveryService == null) {
+ discoveryService = new ShellyBasicDiscoveryService(bundleContext, this);
+ discoveryService.registerDeviceDiscoveryService();
}
}
}
public void stopDiscoveryService() {
- if (bluDiscoveryService != null) {
- bluDiscoveryService.unregisterDeviceDiscoveryService();
- bluDiscoveryService = null;
+ if (discoveryService != null) {
+ discoveryService.unregisterDeviceDiscoveryService();
+ discoveryService = null;
}
}
public void discoveredResult(ThingTypeUID uid, String model, String serviceName, String address,
Map<String, Object> properties) {
- if (bluDiscoveryService != null) {
- bluDiscoveryService.discoveredResult(uid, model, serviceName, address, properties);
+ if (discoveryService != null) {
+ discoveryService.discoveredResult(uid, model, serviceName, address, properties);
+ }
+ }
+
+ public void discoveredResult(DiscoveryResult result) {
+ if (discoveryService != null) {
+ discoveryService.discoveredResult(result);
}
}
<parameter name="deviceIp" type="text" required="true">
<label>@text/thing-type.config.shelly.deviceIp.label</label>
<description>@text/thing-type.config.shelly.deviceIp.description</description>
- <context>network-address</context>
</parameter>
<parameter name="password" type="text" required="false">
<label>@text/thing-type.config.shelly.password.label</label>
<description>@text/thing-type.config.shelly.enableBluGateway.description</description>
<default>false</default>
</parameter>
+ <parameter name="enableRangeExtender" type="boolean" required="false">
+ <label>@text/thing-type.config.shelly.enableRangeExtender.label</label>
+ <description>@text/thing-type.config.shelly.enableRangeExtender.description</description>
+ <default>true</default>
+ </parameter>
</config-description>
<config-description uri="thing-type:shelly:roller-gen2">
thing-type.config.shelly.updateInterval.description = Interval for the device status update
thing-type.config.shelly.enableBluGateway.label = Enable BLU Gateway Support
thing-type.config.shelly.enableBluGateway.description = Enables BLU Gateway support including auto-upload of the required script
+thing-type.config.shelly.enableRangeExtender.label = Enable Range Extender Support
+thing-type.config.shelly.enableRangeExtender.description = Auto discovers devices, which are connected using the Shelly Range Extender support
thing-type.config.shelly.eventsButton.label = Button Events
thing-type.config.shelly.eventsButton.description = Activates the Button Action URLS
thing-type.config.shelly.eventsPush.label = Push Events