[shelly] Improved TRV profile handling (selectable list via dynamic state options (#13227)

provider); some improvements & fixes

Signed-off-by: Markus Michels <markus7017@gmail.com>
This commit is contained in:
Markus Michels 2022-08-09 10:04:07 +02:00 committed by GitHub
parent 6c78fb3161
commit 5b038786d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 212 additions and 82 deletions

View File

@ -27,7 +27,7 @@ Also check out the [Shelly Manager](doc/ShellyManager.md), which
## Supported Devices ## Supported Devices
### Generation 1: ### Generation 1
| thing-type | Model | Vendor ID | | thing-type | Model | Vendor ID |
|--------------------|--------------------------------------------------------|-----------| |--------------------|--------------------------------------------------------|-----------|

View File

@ -138,10 +138,10 @@ public class ShellyBindingConstants {
public static final String CHANNEL_CONTROL_SETTEMP = "targetTemp"; public static final String CHANNEL_CONTROL_SETTEMP = "targetTemp";
public static final String CHANNEL_CONTROL_POSITION = "position"; public static final String CHANNEL_CONTROL_POSITION = "position";
public static final String CHANNEL_CONTROL_MODE = "mode"; public static final String CHANNEL_CONTROL_MODE = "mode";
public static final String CHANNEL_CONTROL_PROFILE = "selectedProfile";
public static final String CHANNEL_CONTROL_BCONTROL = "boost"; public static final String CHANNEL_CONTROL_BCONTROL = "boost";
public static final String CHANNEL_CONTROL_BTIMER = "boostTimer"; public static final String CHANNEL_CONTROL_BTIMER = "boostTimer";
public static final String CHANNEL_CONTROL_SCHEDULE = "schedule"; public static final String CHANNEL_CONTROL_SCHEDULE = "schedule";
public static final String CHANNEL_CONTROL_PROFILE = "selectedProfile";
// External sensors for Shelly1/1PM // External sensors for Shelly1/1PM
public static final String CHANNEL_ESENDOR_TEMP1 = CHANNEL_SENSOR_TEMP + "1"; public static final String CHANNEL_ESENDOR_TEMP1 = CHANNEL_SENSOR_TEMP + "1";

View File

@ -65,8 +65,6 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
private final Shelly1CoapServer coapServer; private final Shelly1CoapServer coapServer;
private final ShellyThingTable thingTable; private final ShellyThingTable thingTable;
private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration(); private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
private String localIP = "";
private int httpPort = -1;
/** /**
* Activate the bundle: save properties * Activate the bundle: save properties
@ -85,7 +83,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
this.thingTable = thingTable; this.thingTable = thingTable;
bindingConfig.updateFromProperties(configProperties); bindingConfig.updateFromProperties(configProperties);
localIP = bindingConfig.localIP; String localIP = bindingConfig.localIP;
if (localIP.isEmpty()) { if (localIP.isEmpty()) {
localIP = ShellyUtils.getString(networkAddressService.getPrimaryIpv4HostAddress()); localIP = ShellyUtils.getString(networkAddressService.getPrimaryIpv4HostAddress());
} }
@ -94,11 +92,13 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
} }
this.httpClient = httpClientFactory.getCommonHttpClient(); this.httpClient = httpClientFactory.getCommonHttpClient();
httpPort = HttpServiceUtil.getHttpServicePort(componentContext.getBundleContext()); int httpPort = HttpServiceUtil.getHttpServicePort(componentContext.getBundleContext());
if (httpPort == -1) { if (httpPort == -1) {
httpPort = 8080; httpPort = 8080;
} }
logger.debug("Using OH HTTP port {}", httpPort); logger.debug("Using OH HTTP port {}", httpPort);
bindingConfig.localIP = localIP;
bindingConfig.httpPort = httpPort;
this.coapServer = new Shelly1CoapServer(); this.coapServer = new Shelly1CoapServer();
} }
@ -117,8 +117,7 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
if (thingType.equals(THING_TYPE_SHELLYPROTECTED_STR)) { if (thingType.equals(THING_TYPE_SHELLYPROTECTED_STR)) {
logger.debug("{}: Create new thing of type {} using ShellyProtectedHandler", thing.getLabel(), logger.debug("{}: Create new thing of type {} using ShellyProtectedHandler", thing.getLabel(),
thingTypeUID.toString()); thingTypeUID.toString());
handler = new ShellyProtectedHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort, handler = new ShellyProtectedHandler(thing, messages, bindingConfig, thingTable, coapServer, httpClient);
httpClient);
} else if (thingType.equals(THING_TYPE_SHELLYBULB_STR) || thingType.equals(THING_TYPE_SHELLYDUO_STR) } else if (thingType.equals(THING_TYPE_SHELLYBULB_STR) || thingType.equals(THING_TYPE_SHELLYDUO_STR)
|| thingType.equals(THING_TYPE_SHELLYRGBW2_COLOR_STR) || thingType.equals(THING_TYPE_SHELLYRGBW2_COLOR_STR)
|| thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE_STR) || thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE_STR)
@ -126,11 +125,11 @@ public class ShellyHandlerFactory extends BaseThingHandlerFactory {
|| thingType.equals(THING_TYPE_SHELLYVINTAGE_STR)) { || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR)) {
logger.debug("{}: Create new thing of type {} using ShellyLightHandler", thing.getLabel(), logger.debug("{}: Create new thing of type {} using ShellyLightHandler", thing.getLabel(),
thingTypeUID.toString()); thingTypeUID.toString());
handler = new ShellyLightHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort, httpClient); handler = new ShellyLightHandler(thing, messages, bindingConfig, thingTable, coapServer, httpClient);
} else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) { } else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
logger.debug("{}: Create new thing of type {} using ShellyRelayHandler", thing.getLabel(), logger.debug("{}: Create new thing of type {} using ShellyRelayHandler", thing.getLabel(),
thingTypeUID.toString()); thingTypeUID.toString());
handler = new ShellyRelayHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort, httpClient); handler = new ShellyRelayHandler(thing, messages, bindingConfig, thingTable, coapServer, httpClient);
} }
if (handler != null) { if (handler != null) {

View File

@ -330,17 +330,19 @@ public class ShellyDeviceProfile {
return -1; return -1;
} }
public String getValueProfile(int profileId) { public String[] getValveProfileList(int valveId) {
int id = profileId; if (isTRV && settings.thermostats != null && valveId <= settings.thermostats.size()) {
if (settings.thermostats != null) { ShellyThermnostat t = settings.thermostats.get(valveId);
ShellyThermnostat t = settings.thermostats.get(0); return t.profileNames;
id = profileId == 0 ? getInteger(t.profile) : profileId;
if (id <= 0) {
return "DISABLED";
}
return id <= t.profileNames.length ? getString(t.profileNames[id - 1]) : "" + id;
} }
return new String[0];
}
public String getValueProfile(int valveId, int profileId) {
int id = profileId;
if (id <= 0 && settings.thermostats != null) {
id = settings.thermostats.get(0).profile;
}
return "" + id; return "" + id;
} }

View File

@ -1143,7 +1143,7 @@ public class Shelly1ApiJsonDTO {
/** /**
* Shelly Dimmer returns light[]. However, the structure doesn't match the lights[] of a Bulb/RGBW2. * Shelly Dimmer returns light[]. However, the structure doesn't match the lights[] of a Bulb/RGBW2.
* The tag lights[] will be replaced with dimmers[] so this could be mapped to a different Gson structure. * The tag lights[] will be replaced with dimmers[] so this could be mapped to a different Gson structure.
* The function requires that it's only called when the device is a dimmer - on get settings and get status * The function requires that it's only called when the device is a dimmer - on get settings and get status
* *
* @param json Input Json as received by the API * @param json Input Json as received by the API

View File

@ -58,7 +58,6 @@ public class Shelly1CoIoTProtocol {
// Due to the fact that the device reports only the current/last status, but no real events, we need to distinguish // Due to the fact that the device reports only the current/last status, but no real events, we need to distinguish
// between a real update or just a repeated status on periodic updates // between a real update or just a repeated status on periodic updates
protected int lastCfgCount = -1;
protected int[] lastEventCount = { -1, -1, -1, -1, -1, -1, -1, -1 }; // 4Pro has 4 relays, so 8 should be fine protected int[] lastEventCount = { -1, -1, -1, -1, -1, -1, -1, -1 }; // 4Pro has 4 relays, so 8 should be fine
protected String[] inputEvent = { "", "", "", "", "", "", "", "" }; protected String[] inputEvent = { "", "", "", "", "", "", "", "" };
protected String lastWakeup = ""; protected String lastWakeup = "";

View File

@ -48,6 +48,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class Shelly1CoIoTVersion2 extends Shelly1CoIoTProtocol implements Shelly1CoIoTInterface { public class Shelly1CoIoTVersion2 extends Shelly1CoIoTProtocol implements Shelly1CoIoTInterface {
private final Logger logger = LoggerFactory.getLogger(Shelly1CoIoTVersion2.class); private final Logger logger = LoggerFactory.getLogger(Shelly1CoIoTVersion2.class);
private int lastCfgCount = -1;
public Shelly1CoIoTVersion2(String thingName, ShellyThingInterface thingHandler, Map<String, CoIotDescrBlk> blkMap, public Shelly1CoIoTVersion2(String thingName, ShellyThingInterface thingHandler, Map<String, CoIotDescrBlk> blkMap,
Map<String, CoIotDescrSen> sensorMap) { Map<String, CoIotDescrSen> sensorMap) {
@ -107,7 +108,7 @@ public class Shelly1CoIoTVersion2 extends Shelly1CoIoTProtocol implements Shelly
case "3117": // S, mode, 0-5 (0=disabled) case "3117": // S, mode, 0-5 (0=disabled)
value = getDouble(s.value).intValue(); value = getDouble(s.value).intValue();
updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE, updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE,
getStringType(profile.getValueProfile((int) value))); getStringType(profile.getValueProfile(0, (int) value)));
updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SCHEDULE, getOnOff(value > 0)); updateChannel(updates, CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SCHEDULE, getOnOff(value > 0));
break; break;
case "3118": // Valve state case "3118": // Valve state

View File

@ -21,9 +21,13 @@ import static org.openhab.core.thing.Thing.*;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -39,7 +43,6 @@ import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyInputSta
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyOtaCheckResult;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsDevice;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus; import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettingsStatus;
import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellyThermnostat;
import org.openhab.binding.shelly.internal.api1.Shelly1CoapHandler; import org.openhab.binding.shelly.internal.api1.Shelly1CoapHandler;
import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO; import org.openhab.binding.shelly.internal.api1.Shelly1CoapJSonDTO;
import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer; import org.openhab.binding.shelly.internal.api1.Shelly1CoapServer;
@ -48,6 +51,7 @@ import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
import org.openhab.binding.shelly.internal.discovery.ShellyThingCreator; import org.openhab.binding.shelly.internal.discovery.ShellyThingCreator;
import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions; import org.openhab.binding.shelly.internal.provider.ShellyChannelDefinitions;
import org.openhab.binding.shelly.internal.provider.ShellyStateDescriptionProvider;
import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider; import org.openhab.binding.shelly.internal.provider.ShellyTranslationProvider;
import org.openhab.binding.shelly.internal.util.ShellyChannelCache; import org.openhab.binding.shelly.internal.util.ShellyChannelCache;
import org.openhab.binding.shelly.internal.util.ShellyVersionDTO; import org.openhab.binding.shelly.internal.util.ShellyVersionDTO;
@ -62,10 +66,13 @@ import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.binding.builder.ThingBuilder; import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -79,8 +86,21 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class ShellyBaseHandler extends BaseThingHandler public class ShellyBaseHandler extends BaseThingHandler
implements ShellyDeviceListener, ShellyManagerInterface, ShellyThingInterface { implements ShellyDeviceListener, ShellyManagerInterface, ShellyThingInterface {
private class OptionEntry {
public ChannelTypeUID uid;
public String key;
public String value;
public OptionEntry(ChannelTypeUID uid, String key, String value) {
this.uid = uid;
this.key = key;
this.value = value;
}
}
protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class); protected final Logger logger = LoggerFactory.getLogger(ShellyBaseHandler.class);
protected final ShellyChannelDefinitions channelDefinitions; protected final ShellyChannelDefinitions channelDefinitions;
private final CopyOnWriteArrayList<OptionEntry> stateOptions = new CopyOnWriteArrayList<>();
public String thingName = ""; public String thingName = "";
public String thingType = ""; public String thingType = "";
@ -111,9 +131,6 @@ public class ShellyBaseHandler extends BaseThingHandler
private final int cacheCount = UPDATE_SETTINGS_INTERVAL_SECONDS / UPDATE_STATUS_INTERVAL_SECONDS; private final int cacheCount = UPDATE_SETTINGS_INTERVAL_SECONDS / UPDATE_STATUS_INTERVAL_SECONDS;
private final ShellyChannelCache cache; private final ShellyChannelCache cache;
private String localIP = "";
private String localPort = "";
private String lastWakeupReason = ""; private String lastWakeupReason = "";
private int vibrationFilter = 0; private int vibrationFilter = 0;
@ -128,8 +145,8 @@ public class ShellyBaseHandler extends BaseThingHandler
* @param httpPort from httpService * @param httpPort from httpService
*/ */
public ShellyBaseHandler(final Thing thing, final ShellyTranslationProvider translationProvider, public ShellyBaseHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
final ShellyBindingConfiguration bindingConfig, final Shelly1CoapServer coapServer, final String localIP, final ShellyBindingConfiguration bindingConfig, ShellyThingTable thingTable,
int httpPort, final HttpClient httpClient) { final Shelly1CoapServer coapServer, final HttpClient httpClient) {
super(thing); super(thing);
this.thingName = getString(thing.getLabel()); this.thingName = getString(thing.getLabel());
@ -140,8 +157,6 @@ public class ShellyBaseHandler extends BaseThingHandler
this.config = getConfigAs(ShellyThingConfiguration.class); this.config = getConfigAs(ShellyThingConfiguration.class);
this.httpClient = httpClient; this.httpClient = httpClient;
this.localIP = localIP;
this.localPort = String.valueOf(httpPort);
this.api = new Shelly1HttpApi(thingName, config, httpClient); this.api = new Shelly1HttpApi(thingName, config, httpClient);
coap = new Shelly1CoapHandler(this, coapServer); coap = new Shelly1CoapHandler(this, coapServer);
@ -153,6 +168,11 @@ public class ShellyBaseHandler extends BaseThingHandler
|| key.equalsIgnoreCase(config.serviceName) || key.equalsIgnoreCase(thing.getUID().getAsString()); || key.equalsIgnoreCase(config.serviceName) || key.equalsIgnoreCase(thing.getUID().getAsString());
} }
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(ShellyStateDescriptionProvider.class);
}
public String getUID() { public String getUID() {
return getThing().getUID().getAsString(); return getThing().getUID().getAsString();
} }
@ -213,7 +233,9 @@ public class ShellyBaseHandler extends BaseThingHandler
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) { public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
super.handleConfigurationUpdate(configurationParameters); super.handleConfigurationUpdate(configurationParameters);
logger.debug("{}: Thing config updated, re-initialize", thingName); logger.debug("{}: Thing config updated, re-initialize", thingName);
coap.stop(); if (coap != null) {
coap.stop();
}
requestUpdates(1, true);// force re-initialization requestUpdates(1, true);// force re-initialization
} }
@ -231,8 +253,6 @@ public class ShellyBaseHandler extends BaseThingHandler
stopping = false; stopping = false;
refreshSettings = false; refreshSettings = false;
lastWakeupReason = ""; lastWakeupReason = "";
profile.initFromThingType(thingType);
api.setConfig(thingName, config);
cache.setThingName(thingName); cache.setThingName(thingName);
cache.clear(); cache.clear();
@ -260,6 +280,8 @@ public class ShellyBaseHandler extends BaseThingHandler
config.serviceName = getString(profile.hostname).toLowerCase(); config.serviceName = getString(profile.hostname).toLowerCase();
} }
api.setConfig(thingName, config);
api.initialize();
ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType); ShellyDeviceProfile tmpPrf = api.getDeviceProfile(thingType);
if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) { if (this.getThing().getThingTypeUID().equals(THING_TYPE_SHELLYPROTECTED)) {
changeThingType(thingName, tmpPrf.mode); changeThingType(thingName, tmpPrf.mode);
@ -293,8 +315,18 @@ public class ShellyBaseHandler extends BaseThingHandler
tmpPrf.status = api.getStatus(); tmpPrf.status = api.getStatus();
tmpPrf.updateFromStatus(tmpPrf.status); tmpPrf.updateFromStatus(tmpPrf.status);
if (tmpPrf.isTRV) {
String[] profileNames = tmpPrf.getValveProfileList(0);
String channelId = mkChannelId(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE);
logger.debug("{}: Adding TRV profile names to channel description: {}", thingName, profileNames);
clearStateOptions(channelId);
addStateOption(channelId, "0", "DISABLED");
for (int i = 0; i < profileNames.length; i++) {
addStateOption(channelId, "" + (i + 1), profileNames[i]);
}
}
showThingConfig(tmpPrf); showThingConfig(tmpPrf);
// update thing properties
checkVersion(tmpPrf, tmpPrf.status); checkVersion(tmpPrf, tmpPrf.status);
if (config.eventsCoIoT && (tmpPrf.settings.coiot != null) && (tmpPrf.settings.coiot.enabled != null)) { if (config.eventsCoIoT && (tmpPrf.settings.coiot != null) && (tmpPrf.settings.coiot.enabled != null)) {
@ -411,24 +443,8 @@ public class ShellyBaseHandler extends BaseThingHandler
break; break;
case CHANNEL_CONTROL_PROFILE: case CHANNEL_CONTROL_PROFILE:
logger.debug("{}: Select profile {}", thingName, command); logger.debug("{}: Select profile {}", thingName, command);
int id = -1; String cmd = command.toString();
if (command instanceof Number) { int id = Integer.parseInt(cmd);
id = (int) getNumber(command);
} else {
String cmd = command.toString();
if (isDigit(cmd.charAt(0))) {
id = Integer.parseInt(cmd);
} else if (cmd.equalsIgnoreCase("DISABLED")) {
id = 0;
} else if (profile.settings.thermostats != null) {
ShellyThermnostat t = profile.settings.thermostats.get(0);
for (int i = 0; i < t.profileNames.length; i++) {
if (t.profileNames[i].equalsIgnoreCase(cmd)) {
id = i + 1;
}
}
}
}
if (id < 0 || id > 5) { if (id < 0 || id > 5) {
logger.warn("{}: Invalid profile Id {} requested", thingName, profile); logger.warn("{}: Invalid profile Id {} requested", thingName, profile);
} else { } else {
@ -463,7 +479,6 @@ public class ShellyBaseHandler extends BaseThingHandler
restartWatchdog(); restartWatchdog();
if (update && !autoCoIoT && !isUpdateScheduled()) { if (update && !autoCoIoT && !isUpdateScheduled()) {
logger.debug("{}: Command processed, request status update", thingName);
requestUpdates(1, false); requestUpdates(1, false);
} }
} catch (ShellyApiException e) { } catch (ShellyApiException e) {
@ -517,8 +532,6 @@ public class ShellyBaseHandler extends BaseThingHandler
initializeThing(); // may fire an exception if initialization failed initializeThing(); // may fire an exception if initialization failed
} }
// Get profile, if refreshSettings == true reload settings from device // Get profile, if refreshSettings == true reload settings from device
logger.trace("{}: Updating status (scheduledUpdates={}, refreshSettings={})", thingName,
scheduledUpdates, refreshSettings);
ShellySettingsStatus status = api.getStatus(); ShellySettingsStatus status = api.getStatus();
boolean restarted = checkRestarted(status); boolean restarted = checkRestarted(status);
profile = getProfile(refreshSettings || restarted); profile = getProfile(refreshSettings || restarted);
@ -590,6 +603,17 @@ public class ShellyBaseHandler extends BaseThingHandler
} }
} }
@Override
public ThingStatus getThingStatus() {
return getThing().getStatus();
}
@Override
public ThingStatusDetail getThingStatusDetail() {
return getThing().getStatusInfo().getStatusDetail();
}
@Override
public boolean isThingOnline() { public boolean isThingOnline() {
return getThing().getStatus() == ThingStatus.ONLINE; return getThing().getStatus() == ThingStatus.ONLINE;
} }
@ -699,9 +723,10 @@ public class ShellyBaseHandler extends BaseThingHandler
if (status.uptime != null) { if (status.uptime != null) {
stats.lastUptime = getLong(status.uptime); stats.lastUptime = getLong(status.uptime);
} }
stats.coiotMessages = coap.getMessageCount(); if (coap != null) {
stats.coiotErrors = coap.getErrorCount(); stats.coiotMessages = coap.getMessageCount();
stats.coiotErrors = coap.getErrorCount();
}
if (!alarm.isEmpty()) { if (!alarm.isEmpty()) {
postEvent(alarm, false); postEvent(alarm, false);
} }
@ -911,7 +936,7 @@ public class ShellyBaseHandler extends BaseThingHandler
InetAddress addr = InetAddress.getByName(config.deviceIp); InetAddress addr = InetAddress.getByName(config.deviceIp);
String saddr = addr.getHostAddress(); String saddr = addr.getHostAddress();
if (!config.deviceIp.equals(saddr)) { if (!config.deviceIp.equals(saddr)) {
logger.debug("{}: hostname {} resolved to IP address {}", thingName, config.deviceIp, saddr); logger.debug("{}: hostname {} resolved to IP address {}", thingName, config.deviceIp, saddr);
config.deviceIp = saddr; config.deviceIp = saddr;
} }
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
@ -919,8 +944,8 @@ public class ShellyBaseHandler extends BaseThingHandler
} }
config.serviceName = getString(properties.get(PROPERTY_SERVICE_NAME)); config.serviceName = getString(properties.get(PROPERTY_SERVICE_NAME));
config.localIp = localIP; config.localIp = bindingConfig.localIP;
config.localPort = localPort; config.localPort = String.valueOf(bindingConfig.httpPort);
if (config.userId.isEmpty() && !bindingConfig.defaultUserId.isEmpty()) { if (config.userId.isEmpty() && !bindingConfig.defaultUserId.isEmpty()) {
config.userId = bindingConfig.defaultUserId; config.userId = bindingConfig.defaultUserId;
config.password = bindingConfig.defaultPassword; config.password = bindingConfig.defaultPassword;
@ -1221,8 +1246,7 @@ public class ShellyBaseHandler extends BaseThingHandler
* @param profile The device profile * @param profile The device profile
* @param status the /status result * @param status the /status result
*/ */
protected void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus status) { public void updateProperties(ShellyDeviceProfile profile, ShellySettingsStatus status) {
logger.debug("{}: Update properties", thingName);
Map<String, Object> properties = fillDeviceProperties(profile); Map<String, Object> properties = fillDeviceProperties(profile);
String deviceName = getString(profile.settings.name); String deviceName = getString(profile.settings.name);
properties.put(PROPERTY_SERVICE_NAME, config.serviceName); properties.put(PROPERTY_SERVICE_NAME, config.serviceName);
@ -1347,6 +1371,35 @@ public class ShellyBaseHandler extends BaseThingHandler
return profile; return profile;
} }
@Override
public List<StateOption> getStateOptions(ChannelTypeUID uid) {
List<StateOption> options = new ArrayList<>();
for (OptionEntry oe : stateOptions) {
if (oe.uid.equals(uid)) {
options.add(new StateOption(oe.key, oe.value));
}
}
if (!options.isEmpty()) {
logger.debug("{}: Return {} state options for channel uid {}", thingName, options.size(), uid.getId());
}
return options;
}
private void addStateOption(String channelId, String key, String value) {
ChannelTypeUID uid = channelDefinitions.getChannelTypeUID(channelId);
stateOptions.addIfAbsent(new OptionEntry(uid, key, value));
}
private void clearStateOptions(String channelId) {
ChannelTypeUID uid = channelDefinitions.getChannelTypeUID(channelId);
for (OptionEntry oe : stateOptions) {
if (oe.uid.equals(uid)) {
stateOptions.remove(oe);
}
}
}
protected ShellyDeviceProfile getDeviceProfile() { protected ShellyDeviceProfile getDeviceProfile() {
return profile; return profile;
} }
@ -1361,7 +1414,7 @@ public class ShellyBaseHandler extends BaseThingHandler
logger.debug("{}: Duplicate vibration events will be absorbed for the next {} sec", thingName, logger.debug("{}: Duplicate vibration events will be absorbed for the next {} sec", thingName,
vibrationFilter * UPDATE_STATUS_INTERVAL_SECONDS); vibrationFilter * UPDATE_STATUS_INTERVAL_SECONDS);
} else { } else {
logger.debug("{}: Vibration event absorbed, {} sec remaining", thingName, logger.debug("{}: Vibration event absorbed, {} sec remaining", thingName,
vibrationFilter * UPDATE_STATUS_INTERVAL_SECONDS); vibrationFilter * UPDATE_STATUS_INTERVAL_SECONDS);
return; return;
} }
@ -1379,7 +1432,9 @@ public class ShellyBaseHandler extends BaseThingHandler
logger.debug("{}: Shelly statusJob stopped", thingName); logger.debug("{}: Shelly statusJob stopped", thingName);
} }
coap.stop(); if (coap != null) {
coap.stop();
}
profile.initialized = false; profile.initialized = false;
} }

View File

@ -398,7 +398,7 @@ public class ShellyComponents {
updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SCHEDULE, updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_SCHEDULE,
getOnOff(t.schedule)); getOnOff(t.schedule));
updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE, updated |= thingHandler.updateChannel(CHANNEL_GROUP_CONTROL, CHANNEL_CONTROL_PROFILE,
getStringType(profile.getValueProfile(pid))); getStringType(profile.getValueProfile(0, pid)));
if (t.tmp != null) { if (t.tmp != null) {
Double temp = convertToC(t.tmp.value, getString(t.tmp.units)); Double temp = convertToC(t.tmp.value, getString(t.tmp.units));
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP, updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,

View File

@ -66,9 +66,9 @@ public class ShellyLightHandler extends ShellyBaseHandler {
* @param httpPort port of the openHAB HTTP API * @param httpPort port of the openHAB HTTP API
*/ */
public ShellyLightHandler(final Thing thing, final ShellyTranslationProvider translationProvider, public ShellyLightHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
final ShellyBindingConfiguration bindingConfig, final Shelly1CoapServer coapServer, final String localIP, final ShellyBindingConfiguration bindingConfig, final ShellyThingTable thingTable,
int httpPort, final HttpClient httpClient) { final Shelly1CoapServer coapServer, final HttpClient httpClient) {
super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient); super(thing, translationProvider, bindingConfig, thingTable, coapServer, httpClient);
channelColors = new TreeMap<>(); channelColors = new TreeMap<>();
} }

View File

@ -36,9 +36,9 @@ public class ShellyProtectedHandler extends ShellyBaseHandler {
* @param httpPort port of the openHAB HTTP API * @param httpPort port of the openHAB HTTP API
*/ */
public ShellyProtectedHandler(final Thing thing, final ShellyTranslationProvider translationProvider, public ShellyProtectedHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
final ShellyBindingConfiguration bindingConfig, final Shelly1CoapServer coapServer, final String localIP, final ShellyBindingConfiguration bindingConfig, ShellyThingTable thingTable,
int httpPort, final HttpClient httpClient) { final Shelly1CoapServer coapService, final HttpClient httpClient) {
super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient); super(thing, translationProvider, bindingConfig, thingTable, coapService, httpClient);
} }
@Override @Override

View File

@ -64,9 +64,9 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
* @param httpPort port of the openHAB HTTP API * @param httpPort port of the openHAB HTTP API
*/ */
public ShellyRelayHandler(final Thing thing, final ShellyTranslationProvider translationProvider, public ShellyRelayHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
final ShellyBindingConfiguration bindingConfig, final Shelly1CoapServer coapServer, final String localIP, final ShellyBindingConfiguration bindingConfig, ShellyThingTable thingTable,
int httpPort, final HttpClient httpClient) { final Shelly1CoapServer coapServer, final HttpClient httpClient) {
super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient); super(thing, translationProvider, bindingConfig, thingTable, coapServer, httpClient);
} }
@Override @Override
@ -329,7 +329,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
*/ */
public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException { public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException {
boolean updated = false; boolean updated = false;
// Check for Relay in Standard Mode
if (profile.hasRelays && !profile.isDimmer) { if (profile.hasRelays && !profile.isDimmer) {
double voltage = -1; double voltage = -1;
if (status.voltage == null && profile.settings.supplyVoltage != null) { if (status.voltage == null && profile.settings.supplyVoltage != null) {
@ -384,7 +384,7 @@ public class ShellyRelayHandler extends ShellyBaseHandler {
ShellySettingsStatus dstatus = fromJson(gson, Shelly1ApiJsonDTO.fixDimmerJson(orgStatus.json), ShellySettingsStatus dstatus = fromJson(gson, Shelly1ApiJsonDTO.fixDimmerJson(orgStatus.json),
ShellySettingsStatus.class); ShellySettingsStatus.class);
logger.trace("{}: Updating {} dimmers(s)", thingName, dstatus.dimmers.size()); logger.trace("{}: Updating {} dimmers(s)", thingName, dstatus.dimmers.size());
int l = 0; int l = 0;
for (ShellyShortLightStatus dimmer : dstatus.dimmers) { for (ShellyShortLightStatus dimmer : dstatus.dimmers) {
Integer r = l + 1; Integer r = l + 1;

View File

@ -25,8 +25,11 @@ import org.openhab.binding.shelly.internal.api1.Shelly1ApiJsonDTO.ShellySettings
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration; import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
import org.openhab.core.thing.Channel; import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.StateOption;
/** /**
* The {@link ShellyThingInterface} implements the interface for Shelly Manager to access the thing handler * The {@link ShellyThingInterface} implements the interface for Shelly Manager to access the thing handler
@ -38,6 +41,8 @@ public interface ShellyThingInterface {
public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException; public ShellyDeviceProfile getProfile(boolean forceRefresh) throws ShellyApiException;
public List<StateOption> getStateOptions(ChannelTypeUID uid);
public double getChannelDouble(String group, String channel); public double getChannelDouble(String group, String channel);
public boolean updateChannel(String group, String channel, State value); public boolean updateChannel(String group, String channel, State value);
@ -48,6 +53,12 @@ public interface ShellyThingInterface {
public void setThingOffline(ThingStatusDetail detail, String messageKey); public void setThingOffline(ThingStatusDetail detail, String messageKey);
public ThingStatus getThingStatus();
public ThingStatusDetail getThingStatusDetail();
public boolean isThingOnline();
public boolean requestUpdates(int requestCount, boolean refreshSettings); public boolean requestUpdates(int requestCount, boolean refreshSettings);
public void triggerUpdateFromCoap(); public void triggerUpdateFromCoap();

View File

@ -514,6 +514,11 @@ public class ShellyChannelDefinitions {
return newChannels; return newChannels;
} }
public ChannelTypeUID getChannelTypeUID(String channelId) {
ShellyChannel channelDef = getDefinition(channelId);
return new ChannelTypeUID(BINDING_ID, channelDef.typeId);
}
private static void addChannel(Thing thing, Map<String, Channel> newChannels, boolean supported, String group, private static void addChannel(Thing thing, Map<String, Channel> newChannels, boolean supported, String group,
String channelName) throws IllegalArgumentException { String channelName) throws IllegalArgumentException {
if (supported) { if (supported) {

View File

@ -0,0 +1,58 @@
/**
* 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.provider;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.shelly.internal.handler.ShellyThingInterface;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.StateDescription;
/**
* This class provides the list of valid inputs for the input channel of a source.
*
* @author Markus Michels - Initial contribution
*
*/
@NonNullByDefault
public class ShellyStateDescriptionProvider extends BaseDynamicStateDescriptionProvider implements ThingHandlerService {
private @Nullable ShellyThingInterface handler;
@Override
public void setThingHandler(ThingHandler handler) {
this.handler = (ShellyThingInterface) handler;
}
@Override
public @Nullable ThingHandler getThingHandler() {
return (ThingHandler) handler;
}
@SuppressWarnings("null")
@Override
public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
@Nullable Locale locale) {
ChannelTypeUID uid = channel.getChannelTypeUID();
if (uid != null && handler != null) {
setStateOptions(channel.getUID(), handler.getStateOptions(uid));
}
return super.getStateDescription(channel, original, locale);
}
}

View File

@ -81,7 +81,7 @@ public class ShellyUtils {
if (classOfT.isInstance(json)) { if (classOfT.isInstance(json)) {
return wrap(classOfT).cast(json); return wrap(classOfT).cast(json);
} else if (json.isEmpty()) { // update GSON might return null } else if (json.isEmpty()) { // update GSON might return null
throw new ShellyApiException(PRE + className + "from empty JSON"); throw new ShellyApiException(PRE + className + " from empty JSON");
} else { } else {
try { try {
@Nullable @Nullable
@ -92,7 +92,7 @@ public class ShellyUtils {
return obj; return obj;
} catch (JsonSyntaxException e) { } catch (JsonSyntaxException e) {
throw new ShellyApiException( throw new ShellyApiException(
PRE + className + "from JSON (syntax/format error: " + e.getMessage() + "): " + json, e); PRE + className + " from JSON (syntax/format error: " + e.getMessage() + "): " + json, e);
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new ShellyApiException(PRE + className + " from JSON: " + json, e); throw new ShellyApiException(PRE + className + " from JSON: " + json, e);
} }

View File

@ -388,10 +388,10 @@ channel-type.shelly.controlMode.label = Mode
channel-type.shelly.controlMode.description = Sensor/Control Mode channel-type.shelly.controlMode.description = Sensor/Control Mode
channel-type.shelly.controlMode.state.option.manual = Manual channel-type.shelly.controlMode.state.option.manual = Manual
channel-type.shelly.controlMode.state.option.automatic = Automatic channel-type.shelly.controlMode.state.option.automatic = Automatic
channel-type.shelly.controlSchedule.label = Schedule active
channel-type.shelly.controlSchedule.description = ON: A scheduled program is active
channel-type.shelly.controlProfile.label = Selected Profile channel-type.shelly.controlProfile.label = Selected Profile
channel-type.shelly.controlProfile.description = Selected Profile configured in the Shelly App channel-type.shelly.controlProfile.description = Id of the selected Profile configured in the Shelly App
channel-type.shelly.controlSchedule.label = Schedule Active
channel-type.shelly.controlSchedule.description = ON: A scheduled program is active
channel-type.shelly.boostControl.label = Boost Mode channel-type.shelly.boostControl.label = Boost Mode
channel-type.shelly.boostControl.description = ON: Boost mode is activated (overwrites automatic temperature mode) channel-type.shelly.boostControl.description = ON: Boost mode is activated (overwrites automatic temperature mode)
channel-type.shelly.boostTimer.label = Boost Timer channel-type.shelly.boostTimer.label = Boost Timer