| shellydimmer | Shelly Dimmer | SHDM-1 |
| shellydimmer2 | Shelly Dimmer2 | SHDM-2 |
| shellyix3 | Shelly ix3 | SHIX3-1 |
+| shellyuni | Shelly UNI | SHUNI-1 |
| shellyplug | Shelly Plug | SHPLG2-1 |
| shellyplugs | Shelly Plug-S | SHPLG-S |
| shellyem | Shelly EM with integrated Power Meters | SHEM |
| |lastEvent |String |yes |S/SS/SSS for 1/2/3x Shortpush or L for Longpush |
| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. |
+### Shelly UNI - Low voltage sensor/actor: shellyuni)
+
+|Group |Channel |Type |read-only|Description |
+|----------|-------------|---------|---------|----------------------------------------------------------------------------|
+|relay1 | | | |See group relay1 for Shelly 2, no autoOn/autoOff/timerActive channels |
+|relay2 | | | |See group relay1 for Shelly 2, no autoOn/autoOff/timerActive channels |
+|sensors |temperature1 |Number |yes |Temperature value of external sensor #1 (if connected to temp/hum addon) |
+| |temperature2 |Number |yes |Temperature value of external sensor #2 (if connected to temp/hum addon) |
+| |temperature3 |Number |yes |Temperature value of external sensor #3 (if connected to temp/hum addon) |
+| |humidity |Number |yes |Humidity in percent (if connected to temp/hum addon) |
+| |voltage |Number |yes |ADCS voltage |
+|status |input1 |Switch |yes |State of Input 1 |
+| |input2 |Switch |yes |State of Input 2 |
+| |button |Trigger |yes |Event trigger, see section Button Events |
+| |lastEvent |String |yes |S/SS/SSS for 1/2/3x Shortpush or L for Longpush |
+| |eventCount |Number |yes |Counter gets incremented every time the device issues a button event. |
+
### Shelly Bulb (thing-type: shellybulb)
|Group |Channel |Type |read-only|Description |
THING_TYPE_SHELLYVINTAGE, THING_TYPE_SHELLYDUORGBW, THING_TYPE_SHELLYRGBW2_COLOR,
THING_TYPE_SHELLYRGBW2_WHITE, THING_TYPE_SHELLYHT, THING_TYPE_SHELLYSENSE, THING_TYPE_SHELLYEYE,
THING_TYPE_SHELLYSMOKE, THING_TYPE_SHELLYGAS, THING_TYPE_SHELLYFLOOD, THING_TYPE_SHELLYDOORWIN,
- THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1, /* THING_TYPE_SHELLMOTION, */
+ THING_TYPE_SHELLYDOORWIN2, THING_TYPE_SHELLYBUTTON1, THING_TYPE_SHELLMOTION,
THING_TYPE_SHELLYPROTECTED, THING_TYPE_SHELLYUNKNOWN).collect(Collectors.toSet()));
// Thing Configuration Properties
public static final String ALARM_TYPE_OVERPOWER = "OVERPOWER";
public static final String ALARM_TYPE_OVERLOAD = "OVERLOAD";
public static final String ALARM_TYPE_LOADERR = "LOAD_ERROR";
+ public static final String ALARM_TYPE_SENSOR_ERROR = "SENSOR_ERROR";
public static final String ALARM_TYPE_LOW_BATTERY = "LOW_BATTERY";
// Event types
@NonNullByDefault
@Component(service = { ThingHandlerFactory.class, ShellyHandlerFactory.class }, configurationPid = "binding.shelly")
public class ShellyHandlerFactory extends BaseThingHandlerFactory {
+ private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = ShellyBindingConstants.SUPPORTED_THING_TYPES_UIDS;
+
private final Logger logger = LoggerFactory.getLogger(ShellyHandlerFactory.class);
private final HttpClient httpClient;
private final ShellyTranslationProvider messages;
private final ShellyCoapServer coapServer;
- private final Set<ShellyBaseHandler> deviceListeners = ConcurrentHashMap.newKeySet();
- private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = ShellyBindingConstants.SUPPORTED_THING_TYPES_UIDS;
+
+ private final Map<String, ShellyBaseHandler> deviceListeners = new ConcurrentHashMap<>();
private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
private String localIP = "";
private int httpPort = -1;
}
if (handler != null) {
- deviceListeners.add(handler);
+ String uid = thing.getUID().getAsString();
+ deviceListeners.put(uid, handler);
+ logger.debug("Thing handler for uid {} added, total things = {}", uid, deviceListeners.size());
return handler;
}
return null;
}
+ public Map<String, ShellyBaseHandler> getThingHandlers() {
+ return deviceListeners;
+ }
+
/**
* Remove handler of things.
*/
@Override
protected synchronized void removeHandler(@NonNull ThingHandler thingHandler) {
if (thingHandler instanceof ShellyBaseHandler) {
- deviceListeners.remove(thingHandler);
+ String uid = thingHandler.getThing().getUID().getAsString();
+ deviceListeners.remove(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 (ShellyBaseHandler listener : deviceListeners) {
- if (listener.onEvent(ipAddress, deviceName, componentIndex, eventType, parameters)) {
+ for (Map.Entry<String, ShellyBaseHandler> listener : deviceListeners.entrySet()) {
+ ShellyBaseHandler thingHandler = listener.getValue();
+ if (thingHandler.onEvent(ipAddress, deviceName, componentIndex, eventType, parameters)) {
// event processed
return;
}
public static final String SHELLY_URL_SETTINGS_CLOUD = "/settings/cloud";
public static final String SHELLY_URL_LIST_IR = "/ir/list";
public static final String SHELLY_URL_SEND_IR = "/ir/emit";
+ public static final String SHELLY_URL_RESTART = "/reboot";
public static final String SHELLY_URL_SETTINGS_RELAY = "/settings/relay";
public static final String SHELLY_URL_STATUS_RELEAY = "/status/relay";
public String hostname;
public String fw;
public Boolean auth;
+
+ @SerializedName("coiot") // Shelly Motion Multicast Endpoint
+ public String coiot;
+ public Integer longid;
+
@SerializedName("num_outputs")
public Integer numOutputs;
@SerializedName("num_meters")
public String newVersion;
@SerializedName("old_version")
public String oldVersion;
+ @SerializedName("beta_version")
+ public String betaVersion;
}
public static class ShellySettingsGlobal {
ShellyStatusCloud cloud;
@SerializedName("sleep_mode")
public ShellySensorSleepMode sleepMode; // FW 1.6
+ @SerializedName("external_power")
+ public Integer externalPower; // H&T FW 1.6, seems to be the same like charger for the Sense
public String timezone;
public Double lat;
public boolean isSensor = false; // true for HT & Smoke
public boolean hasBattery = false; // true if battery device
public boolean isSense = false; // true if thing is a Shelly Sense
+ public boolean isMotion = false; // true if thing is a Shelly Sense
public boolean isHT = false; // true for H&T
public boolean isDW = false; // true for Door Window sensor
public boolean isButton = false; // true for a Shelly Button 1
public int updatePeriod = 2 * UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
+ public String coiotEndpoint = "";
+
public Map<String, String> irCodes = new HashMap<>(); // Sense: list of stored IR codes
public ShellyDeviceProfile() {
boolean isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR);
boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_STR);
boolean isUNI = thingType.equals(THING_TYPE_SHELLYUNI_STR);
- boolean isMotion = thingType.equals(THING_TYPE_SHELLYMOTION_STR);
isHT = thingType.equals(THING_TYPE_SHELLYHT_STR);
isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_STR);
+ isMotion = thingType.startsWith(THING_TYPE_SHELLYMOTION_STR);
isSense = thingType.equals(THING_TYPE_SHELLYSENSE_STR);
isIX3 = thingType.equals(THING_TYPE_SHELLYIX3_STR);
isButton = thingType.equals(THING_TYPE_SHELLYBUTTON1_STR);
- isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isUNI || isSense;
+ isSensor = isHT || isFlood || isDW || isSmoke || isGas || isButton || isUNI || isMotion || isSense;
hasBattery = isHT || isFlood || isDW || isSmoke || isButton || isMotion; // we assume that Sense is connected to
// the charger
}
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySenseKeyCode;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDevice;
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsLight;
+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.ShellySettingsUpdate;
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;
request(SHELLY_URL_SETTINGS + "?" + parm + "=" + value);
}
+ public ShellySettingsLogin getLoginSettings() throws ShellyApiException {
+ return callApi(SHELLY_URL_SETTINGS + "/login", ShellySettingsLogin.class);
+ }
+
+ public ShellySettingsLogin setLoginCredentials(String user, String password) throws ShellyApiException {
+ return callApi(SHELLY_URL_SETTINGS + "/login?enabled=yes&username=" + user + "&password=" + password,
+ ShellySettingsLogin.class);
+ }
+
+ public String deviceReboot() throws ShellyApiException {
+ return callApi(SHELLY_URL_RESTART, String.class);
+ }
+
+ public String factoryReset() throws ShellyApiException {
+ return callApi(SHELLY_URL_SETTINGS + "?reset=true", String.class);
+ }
+
+ public ShellySettingsUpdate firmwareUpdate(String uri) throws ShellyApiException {
+ return callApi("/ota?" + uri, ShellySettingsUpdate.class);
+ }
+
/**
* Change between White and Color Mode
*
switch (sen.type.toLowerCase()) {
case "b": // BatteryLevel +
updateChannel(updates, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
- toQuantityType(s.value, DIGITS_PERCENT, Units.PERCENT));
+ toQuantityType(s.value, 0, Units.PERCENT));
break;
case "h" /* Humidity */:
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM,
break;
case "3119": // Motion timestamp
// {"I":3119,"T":"S","D":"timestamp","U":"s","R":["U32","-1"],"L":1},
- updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS,
- getTimestamp(getString(profile.settings.timezone), (long) s.value));
+ if (s.value != 0) {
+ updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS,
+ getTimestamp(getString(profile.settings.timezone), (long) s.value));
+ }
break;
case "3120": // motionActive
// {"I":3120,"T":"S","D":"motionActive","R":["0/1","-1"],"L":1},
private Request reqDescription = new Request(Code.GET, Type.CON);
private Request reqStatus = new Request(Code.GET, Type.CON);
private boolean discovering = false;
+ private int coiotPort = COIOT_PORT;
+ private long coiotMessages = 0;
+ private long coiotErrors = 0;
private int lastSerial = -1;
private String lastPayload = "";
private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
}
logger.debug("{}: Starting CoAP Listener", thingName);
- coapServer.start(config.localIp, this);
- statusClient = new CoapClient(completeUrl(config.deviceIp, COLOIT_URI_DEVSTATUS))
+ if (!profile.coiotEndpoint.isEmpty() && profile.coiotEndpoint.contains(":")) {
+ String ps = substringAfter(profile.coiotEndpoint, ":");
+ coiotPort = Integer.parseInt(ps);
+ }
+ coapServer.start(config.localIp, coiotPort, this);
+ statusClient = new CoapClient(completeUrl(config.deviceIp, coiotPort, COLOIT_URI_DEVSTATUS))
.setTimeout((long) SHELLY_API_TIMEOUT_MS).useNONs().setEndpoint(coapServer.getEndpoint());
@Nullable
Endpoint endpoint = null;
@Override
public void processResponse(@Nullable Response response) {
if (response == null) {
+ coiotErrors++;
return; // other device instance
}
+ ResponseCode code = response.getCode();
+ if (code != ResponseCode.CONTENT) {
+ // error handling
+ logger.debug("{}: Unknown Response Code {} received, payload={}", thingName, code,
+ response.getPayloadString());
+ coiotErrors++;
+ return;
+ }
+
+ List<Option> options = response.getOptions().asSortedList();
String ip = response.getSourceContext().getPeerAddress().toString();
- if (!ip.contains(config.deviceIp)) {
+ boolean match = ip.contains(config.deviceIp);
+ if (!match) {
+ // We can't identify device by IP, so we need to check the CoAP header's Global Device ID
+ for (Option opt : options) {
+ if (opt.getNumber() == COIOT_OPTION_GLOBAL_DEVID) {
+ String devid = opt.getStringValue();
+ if (devid.contains("#")) {
+ // Format: <device type>#<mac address>#<coap version>
+ String macid = substringBetween(devid, "#", "#");
+ if (profile.mac.toUpperCase().contains(macid.toUpperCase())) {
+ match = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!match) {
+ // other instance
return;
}
String uri = "";
int serial = -1;
try {
+ coiotMessages++;
if (logger.isDebugEnabled()) {
logger.debug("{}: CoIoT Message from {} (MID={}): {}", thingName,
response.getSourceContext().getPeerAddress(), response.getMID(), response.getPayloadString());
}
if (response.isCanceled() || response.isDuplicate() || response.isRejected()) {
logger.debug("{} ({}): Packet was canceled, rejected or is a duplicate -> discard", thingName, devId);
+ coiotErrors++;
return;
}
- if (response.getCode() == ResponseCode.CONTENT) {
- payload = response.getPayloadString();
- List<Option> options = response.getOptions().asSortedList();
- int i = 0;
- while (i < options.size()) {
- Option opt = options.get(i);
- switch (opt.getNumber()) {
- case OptionNumberRegistry.URI_PATH:
- uri = COLOIT_URI_BASE + opt.getStringValue();
- break;
- case COIOT_OPTION_GLOBAL_DEVID:
- devId = opt.getStringValue();
- String sVersion = substringAfterLast(devId, "#");
- int iVersion = Integer.parseInt(sVersion);
- if (coiotBound && (coiotVers != iVersion)) {
- logger.debug(
- "{}: CoIoT versopm has changed from {} to {}, maybe the firmware was upgraded",
- thingName, coiotVers, iVersion);
- thingHandler.reinitializeThing();
- coiotBound = false;
- }
- if (!coiotBound) {
- thingHandler.updateProperties(PROPERTY_COAP_VERSION, sVersion);
- logger.debug("{}: CoIoT Version {} detected", thingName, iVersion);
- if (iVersion == COIOT_VERSION_1) {
- coiot = new ShellyCoIoTVersion1(thingName, thingHandler, blkMap, sensorMap);
- } else if (iVersion == COIOT_VERSION_2) {
- coiot = new ShellyCoIoTVersion2(thingName, thingHandler, blkMap, sensorMap);
- } else {
- logger.warn("{}: Unsupported CoAP version detected: {}", thingName, sVersion);
- return;
- }
- coiotVers = iVersion;
- coiotBound = true;
+ payload = response.getPayloadString();
+ for (Option opt : options) {
+ switch (opt.getNumber()) {
+ case OptionNumberRegistry.URI_PATH:
+ uri = COLOIT_URI_BASE + opt.getStringValue();
+ break;
+ case OptionNumberRegistry.URI_HOST: // ignore
+ break;
+ case OptionNumberRegistry.CONTENT_FORMAT: // ignore
+ break;
+ case COIOT_OPTION_GLOBAL_DEVID:
+ devId = opt.getStringValue();
+ String sVersion = substringAfterLast(devId, "#");
+ int iVersion = Integer.parseInt(sVersion);
+ if (coiotBound && (coiotVers != iVersion)) {
+ logger.debug("{}: CoIoT versopm has changed from {} to {}, maybe the firmware was upgraded",
+ thingName, coiotVers, iVersion);
+ thingHandler.reinitializeThing();
+ coiotBound = false;
+ }
+ if (!coiotBound) {
+ thingHandler.updateProperties(PROPERTY_COAP_VERSION, sVersion);
+ logger.debug("{}: CoIoT Version {} detected", thingName, iVersion);
+ if (iVersion == COIOT_VERSION_1) {
+ coiot = new ShellyCoIoTVersion1(thingName, thingHandler, blkMap, sensorMap);
+ } else if (iVersion == COIOT_VERSION_2) {
+ coiot = new ShellyCoIoTVersion2(thingName, thingHandler, blkMap, sensorMap);
+ } else {
+ logger.warn("{}: Unsupported CoAP version detected: {}", thingName, sVersion);
+ return;
}
- break;
- case COIOT_OPTION_STATUS_VALIDITY:
- // validity = o.getIntegerValue();
- break;
- case COIOT_OPTION_STATUS_SERIAL:
- serial = opt.getIntegerValue();
- break;
- default:
- logger.debug("{} ({}): COAP option {} with value {} skipped", thingName, devId,
- opt.getNumber(), opt.getValue());
- }
- i++;
+ coiotVers = iVersion;
+ coiotBound = true;
+ }
+ break;
+ case COIOT_OPTION_STATUS_VALIDITY:
+ break;
+ case COIOT_OPTION_STATUS_SERIAL:
+ serial = opt.getIntegerValue();
+ break;
+ default:
+ logger.debug("{} ({}): COAP option {} with value {} skipped", thingName, devId, opt.getNumber(),
+ opt.getValue());
}
+ }
- // If we received a CoAP message successful the thing must be online
- thingHandler.setThingOnline();
+ // If we received a CoAP message successful the thing must be online
+ thingHandler.setThingOnline();
- // The device changes the serial on every update, receiving a message with the same serial is a
- // duplicate, excep for battery devices! Those reset the serial every time when they wake-up
- if ((serial == lastSerial) && payload.equals(lastPayload) && (!profile.hasBattery
- || coiot.getLastWakeup().equalsIgnoreCase("ext_power") || ((serial & 0xFF) != 0))) {
- logger.debug("{}: Serial {} was already processed, ignore update", thingName, serial);
- return;
- }
+ // The device changes the serial on every update, receiving a message with the same serial is a
+ // duplicate, excep for battery devices! Those reset the serial every time when they wake-up
+ if ((serial == lastSerial) && payload.equals(lastPayload) && (!profile.hasBattery
+ || coiot.getLastWakeup().equalsIgnoreCase("ext_power") || ((serial & 0xFF) != 0))) {
+ logger.debug("{}: Serial {} was already processed, ignore update", thingName, serial);
+ return;
+ }
- // fixed malformed JSON :-(
- payload = fixJSON(payload);
+ // fixed malformed JSON :-(
+ payload = fixJSON(payload);
- try {
- if (uri.equalsIgnoreCase(COLOIT_URI_DEVDESC)
- || (uri.isEmpty() && payload.contains(COIOT_TAG_BLK))) {
- handleDeviceDescription(devId, payload);
- } else if (uri.equalsIgnoreCase(COLOIT_URI_DEVSTATUS)
- || (uri.isEmpty() && payload.contains(COIOT_TAG_GENERIC))) {
- handleStatusUpdate(devId, payload, serial);
- }
- } catch (ShellyApiException e) {
- logger.debug("{}: Unable to process CoIoT message: {}", thingName, e.toString());
+ try {
+ if (uri.equalsIgnoreCase(COLOIT_URI_DEVDESC) || (uri.isEmpty() && payload.contains(COIOT_TAG_BLK))) {
+ handleDeviceDescription(devId, payload);
+ } else if (uri.equalsIgnoreCase(COLOIT_URI_DEVSTATUS)
+ || (uri.isEmpty() && payload.contains(COIOT_TAG_GENERIC))) {
+ handleStatusUpdate(devId, payload, serial);
}
- } else {
- // error handling
- logger.debug("{}: Unknown Response Code {} received, payload={}", thingName, response.getCode(),
- response.getPayloadString());
+ } catch (ShellyApiException e) {
+ logger.debug("{}: Unable to process CoIoT message: {}", thingName, e.toString());
+ coiotErrors++;
}
if (!discovering) {
} catch (JsonSyntaxException | IllegalArgumentException | NullPointerException e) {
logger.debug("{}: Unable to process CoIoT Message for payload={}", thingName, payload, e);
resetSerial();
+ coiotErrors++;
}
}
}
resetSerial();
- return newRequest(ipAddress, uri, con).send();
+ return newRequest(ipAddress, coiotPort, uri, con).send();
}
/**
* @return new packet
*/
- private Request newRequest(String ipAddress, String uri, Type con) {
+ private Request newRequest(String ipAddress, int port, String uri, Type con) {
// We need to build our own Request to set an empty Token
Request request = new Request(Code.GET, con);
- request.setURI(completeUrl(ipAddress, uri));
+ request.setURI(completeUrl(ipAddress, port, uri));
request.setToken(EMPTY_BYTE);
request.addMessageObserver(new MessageObserverAdapter() {
@Override
if (isStarted()) {
logger.debug("{}: Stopping CoAP Listener", thingName);
coapServer.stop(this);
- if (statusClient != null) {
- statusClient.shutdown();
+ CoapClient cclient = statusClient;
+ if (cclient != null) {
+ cclient.shutdown();
statusClient = null;
}
- if (!reqDescription.isCanceled()) {
- reqDescription.cancel();
+ Request request = reqDescription;
+ if (!request.isCanceled()) {
+ request.cancel();
}
- if (!reqStatus.isCanceled()) {
- reqStatus.cancel();
+ request = reqStatus;
+ if (!request.isCanceled()) {
+ request.cancel();
}
}
resetSerial();
coiotBound = false;
}
+ public long getMessageCount() {
+ return coiotMessages;
+ }
+
+ public long getErrorCount() {
+ return coiotErrors;
+ }
+
public void dispose() {
stop();
}
- private static String completeUrl(String ipAddress, String uri) {
- return "coap://" + ipAddress + ":" + COIOT_PORT + uri;
+ private static String completeUrl(String ipAddress, int port, String uri) {
+ return "coap://" + ipAddress + ":" + port + uri;
}
}
boolean started = false;
private CoapEndpoint statusEndpoint = new CoapEndpoint.Builder().build();
private @Nullable UdpMulticastConnector statusConnector;
- private final CoapServer server = new CoapServer(NetworkConfig.getStandard(), COIOT_PORT);;
+ private CoapServer server = new CoapServer(NetworkConfig.getStandard(), COIOT_PORT);
private final Set<ShellyCoapListener> coapListeners = ConcurrentHashMap.newKeySet();
protected class ShellyStatusListener extends CoapResource {
Code code = exchange.getRequest().getCode();
switch (code) {
case CUSTOM_30:
+ case PUT: // Shelly Motion beta: incorrect, but handle the format
listener.processResponse(createResponse(request));
break;
default:
}
}
- public synchronized void start(String localIp, ShellyCoapListener listener)
+ public synchronized void start(String localIp, int port, ShellyCoapListener listener)
throws UnknownHostException, SocketException {
if (!started) {
- logger.debug("Initializing CoIoT listener (local IP={}:{})", localIp, COIOT_PORT);
+ logger.debug("Initializing CoIoT listener (local IP={}:{})", localIp, port);
NetworkConfig nc = NetworkConfig.getStandard();
InetAddress localAddr = InetAddress.getByName(localIp);
- InetSocketAddress localPort = new InetSocketAddress(COIOT_PORT);
+ InetSocketAddress localPort = new InetSocketAddress(port);
// Join the multicast group on the selected network interface
statusConnector = new UdpMulticastConnector(localAddr, localPort, CoAP.MULTICAST_IPV4); // bind UDP listener
statusEndpoint = new CoapEndpoint.Builder().setNetworkConfig(nc).setConnector(statusConnector).build();
+ server = new CoapServer(NetworkConfig.getStandard(), port);
server.addEndpoint(statusEndpoint);
CoapResource cit = new ShellyStatusListener("cit", this);
CoapResource s = new ShellyStatusListener("s", this);
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link ShellyBindingConfiguration} class contains fields mapping binding configuration parameters.
public static final String CONFIG_LOCAL_IP = "localIP";
public static final String CONFIG_AUTOCOIOT = "autoCoIoT";
- public String defaultUserId = ""; // default for http basic user id
- public String defaultPassword = ""; // default for http basic auth password
+ 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 boolean autoCoIoT = true;
}
}
- public void updateFromProperties(Dictionary<String, Object> properties) {
+ public void updateFromProperties(@Nullable Dictionary<String, Object> properties) {
+ if (properties == null) { // saw this once
+ return;
+ }
List<String> keys = Collections.list(properties.keys());
Map<String, Object> dictCopy = keys.stream().collect(Collectors.toMap(Function.identity(), properties::get));
updateFromProperties(dictCopy);
// create shellyunknown thing - will be changed during thing initialization with valid credentials
thingUID = ShellyThingCreator.getThingUID(name, model, mode, true);
} else {
- logger.info("{}: {}", name, messages.get("discovery.failed", address, e.toString()));
+ 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);
if (name.startsWith(THING_TYPE_SHELLYRGBW2_PREFIX)) {
return mode.equals(SHELLY_MODE_COLOR) ? THING_TYPE_SHELLYRGBW2_COLOR_STR : THING_TYPE_SHELLYRGBW2_WHITE_STR;
}
+ if (name.startsWith(THING_TYPE_SHELLYMOTION_STR)) {
+ // depending on firmware release the Motion advertises under shellymotion-xxx or shellymotionsensor-xxxx
+ return THING_TYPE_SHELLYMOTION_STR;
+ }
// Check general mapping
if (!deviceType.isEmpty()) {
protected ShellyBindingConfiguration bindingConfig;
protected ShellyThingConfiguration config = new ShellyThingConfiguration();
protected ShellyDeviceProfile profile = new ShellyDeviceProfile(); // init empty profile to avoid NPE
+ protected ShellyDeviceStats stats = new ShellyDeviceStats();
private final ShellyCoapHandler coap;
public boolean autoCoIoT = false;
protected boolean stopping = false;
private boolean channelsCreated = false;
- private long lastUptime = 0;
- private long lastAlarmTs = 0;
- private long lastTimeoutErros = -1;
private long watchdog = now();
private @Nullable ScheduledFuture<?> statusJob;
}
/**
- * This routine is called every time the Thing configuration has been changed.
+ * This routine is called every time the Thing configuration has been changed
*/
@Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
// Setup CoAP listener to we get the CoAP message, which triggers initialization even the thing could not be
// fully initialized here. In this case the CoAP messages triggers auto-initialization (like the Action URL does
// when enabled)
- if (config.eventsCoIoT && profile.hasBattery && !profile.isSense) {
+ if (config.eventsCoIoT && profile.hasBattery && !profile.isMotion && !profile.isSense) {
coap.start(thingName, config);
}
setThingOffline(ThingStatusDetail.CONFIGURATION_ERROR, "offline.conf-error-wrong-mode");
return false;
}
+ if (!getString(devInfo.coiot).isEmpty()) {
+ // New Shelly devices might use a different endpoint for the CoAP listener
+ tmpPrf.coiotEndpoint = devInfo.coiot;
+ }
logger.debug("{}: Initializing device {}, type {}, Hardware: Rev: {}, batch {}; Firmware: {} / {} ({})",
thingName, tmpPrf.hostname, tmpPrf.deviceType, tmpPrf.hwRev, tmpPrf.hwBatchId, tmpPrf.fwVersion,
tmpPrf.updatePeriod);
// update thing properties
- ShellySettingsStatus status = api.getStatus();
- tmpPrf.updateFromStatus(status);
- updateProperties(tmpPrf, status);
- checkVersion(tmpPrf, status);
+ tmpPrf.status = api.getStatus();
+ tmpPrf.updateFromStatus(tmpPrf.status);
+ updateProperties(tmpPrf, tmpPrf.status);
+ checkVersion(tmpPrf, tmpPrf.status);
if (autoCoIoT) {
logger.debug("{}: Auto-CoIoT is enabled, disabling action urls", thingName);
config.eventsCoIoT = true;
}
// All initialization done, so keep the profile and set Thing to ONLINE
- profile = tmpPrf;
- fillDeviceStatus(status, false);
+ fillDeviceStatus(tmpPrf.status, false);
postEvent(ALARM_TYPE_NONE, false);
api.setActionURLs(); // register event urls
if (config.eventsCoIoT) {
}
logger.debug("{}: Thing successfully initialized.", thingName);
+ profile = tmpPrf;
setThingOnline(); // if API call was successful the thing must be online
return true; // success
logger.trace("{}: Updating status", thingName);
ShellySettingsStatus status = api.getStatus();
+ profile.status = status;
profile.updateFromStatus(status);
// If status update was successful the thing must be online
updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_NAME, getStringType(profile.settings.name));
updated |= this.updateDeviceStatus(status);
updated |= ShellyComponents.updateDeviceStatus(this, status);
+ fillDeviceStatus(status, updated);
updated |= updateInputs(status);
updated |= updateMeters(this, status);
updated |= updateSensors(this, status);
// Restart watchdog when status update was successful (no exception)
restartWatchdog();
-
- if (scheduledUpdates <= 1) {
- fillDeviceStatus(status, updated);
- }
}
} catch (ShellyApiException e) {
// http call failed: go offline except for battery devices, which might be in
if (!isWatchdogExpired()) {
logger.debug("{}: Ignore API Timeout, retry later", thingName);
} else {
- logger.debug("{}: Watchdog expired after {}sec,", thingName, profile.updatePeriod);
if (isThingOnline()) {
status = "offline.status-error-watchdog";
}
}
private boolean isWatchdogExpired() {
- long timeout = profile.hasBattery ? profile.updatePeriod : profile.updatePeriod;
long delta = now() - watchdog;
- if ((watchdog > 0) && (delta > timeout)) {
- logger.trace("{}: Watchdog expired after {}sec (started={}, now={}", thingName, delta, watchdog, now());
+ if ((watchdog > 0) && (delta > profile.updatePeriod)) {
+ stats.remainingWatchdog = delta;
return true;
}
return false;
}
private boolean isWatchdogStarted() {
- logger.trace("{}: Watchdog is {}", thingName, watchdog > 0 ? "started" : "inactive");
return watchdog > 0;
}
private void fillDeviceStatus(ShellySettingsStatus status, boolean updated) {
String alarm = "";
boolean force = false;
- Map<String, String> propertyUpdates = new TreeMap<>();
// Update uptime and WiFi, internal temp
ShellyComponents.updateDeviceStatus(this, status);
- if (api.isInitialized() && (lastTimeoutErros != api.getTimeoutErrors())) {
- propertyUpdates.put(PROPERTY_STATS_TIMEOUTS, String.valueOf(api.getTimeoutErrors()));
- propertyUpdates.put(PROPERTY_STATS_TRECOVERED, String.valueOf(api.getTimeoutsRecovered()));
- lastTimeoutErros = api.getTimeoutErrors();
+ if (api.isInitialized()) {
+ stats.timeoutErrors = api.getTimeoutErrors();
+ stats.timeoutsRecorvered = api.getTimeoutsRecovered();
}
+ stats.remainingWatchdog = watchdog > 0 ? now() - watchdog : 0;
// Check various device indicators like overheating
- if ((status.uptime < lastUptime) && (profile.isInitialized()) && !profile.hasBattery) {
+ logger.debug("{}: status.update={}, lastUpdate={}", thingName, status.uptime, stats.lastUptime);
+ if ((status.uptime < stats.lastUptime) && profile.isInitialized()) {
alarm = ALARM_TYPE_RESTARTED;
force = true;
+ stats.unexpectedRestarts++;
+ logger.debug("{}: Device restart #{} detected", thingName, stats.unexpectedRestarts);
+
// Force re-initialization on next status update
- if (!profile.hasBattery) {
+ if (!profile.hasBattery || profile.isMotion) {
reinitializeThing();
}
} else if (getBool(status.overtemperature)) {
} else if (getBool(status.loaderror)) {
alarm = ALARM_TYPE_LOADERR;
}
- lastUptime = getLong(status.uptime);
+ stats.lastUptime = getLong(status.uptime);
+ stats.coiotMessages = coap.getMessageCount();
+ stats.coiotErrors = coap.getErrorCount();
if (!alarm.isEmpty()) {
postEvent(alarm, force);
}
-
- if (!propertyUpdates.isEmpty()) {
- flushProperties(propertyUpdates);
- }
}
/**
State value = cache.getValue(channelId);
String lastAlarm = value != UnDefType.NULL ? value.toString() : "";
- if (force || !lastAlarm.equals(alarm) || (now() > (lastAlarmTs + HEALTH_CHECK_INTERVAL_SEC))) {
- if (alarm.equals(ALARM_TYPE_NONE)) {
+ if (force || !lastAlarm.equals(alarm) || (now() > (stats.lastAlarmTs + HEALTH_CHECK_INTERVAL_SEC))) {
+ if (alarm.isEmpty() || alarm.equals(ALARM_TYPE_NONE)) {
cache.updateChannel(channelId, getStringType(alarm));
} else {
logger.info("{}: {}", thingName, messages.get("event.triggered", alarm));
triggerChannel(channelId, alarm);
cache.updateChannel(channelId, getStringType(alarm));
- lastAlarmTs = now();
+ stats.lastAlarm = alarm;
+ stats.lastAlarmTs = now();
+ stats.alarms++;
}
}
}
prf.fwId, SHELLY_API_MIN_FWVERSION));
}
}
- if (bindingConfig.autoCoIoT && (version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT) >= 0)) {
+ if (bindingConfig.autoCoIoT && ((version.compare(prf.fwVersion, SHELLY_API_MIN_FWCOIOT)) >= 0)
+ || (prf.fwVersion.equalsIgnoreCase("production_test"))) {
if (!config.eventsCoIoT) {
logger.info("{}: {}", thingName, messages.get("versioncheck.autocoiot"));
}
properties.put(PROPERTY_SERVICE_NAME, hostname);
logger.trace("{}: Updated serrviceName to {}", thingName, hostname);
}
+ String deviceName = getString(profile.settings.name);
+ if (!deviceName.isEmpty()) {
+ properties.put(PROPERTY_DEV_NAME, deviceName);
+ }
// add status properties
if (status.wifiSta != null) {
public boolean updateDeviceStatus(ShellySettingsStatus status) throws ShellyApiException {
return false;
}
+
+ public ShellyDeviceStats getStats() {
+ return stats;
+ }
+
+ public Map<String, String> getStatsProp() {
+ return stats.asProperties(getString(profile.settings.timezone));
+ }
+
+ public void resetStats() {
+ // reset statistics
+ stats = new ShellyDeviceStats();
+ }
}
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
+import org.openhab.core.types.UnDefType;
/***
* The{@link ShellyComponents} implements updates for supplemental components
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) {
+ 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) {
}
}
- if (updated && !profile.isRoller && !profile.isRGBW2) {
+ if (!profile.isRoller && !profile.isRGBW2) {
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUWATTS,
toQuantityType(accumulatedWatts, DIGITS_WATT, Units.WATT));
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUTOTAL,
ShellyDeviceProfile profile = thingHandler.getProfile();
boolean updated = false;
- if (profile.isSensor || profile.hasBattery || profile.isSense) {
+ if (profile.isSensor || profile.hasBattery) {
ShellyStatusSensor sdata = thingHandler.api.getSensorStatus();
if (!thingHandler.areChannelsCreated()) {
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
getString(sdata.sensor.state).equalsIgnoreCase(SHELLY_API_DWSTATE_OPEN) ? OpenClosedType.OPEN
: OpenClosedType.CLOSED);
+ String sensorError = getString(sdata.sensorError);
boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR,
getStringType(sdata.sensorError));
if (changed) {
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VOLTAGE,
getDecimal(adc.voltage));
}
+
+ boolean charger = (getInteger(profile.settings.externalPower) == 1) || getBool(sdata.charger);
+ if ((profile.settings.externalPower != null) || (sdata.charger != null)) {
+ updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
+ charger ? OnOffType.ON : OnOffType.OFF);
+ }
if (sdata.bat != null) { // no update for Sense
- thingHandler.logger.trace("{}: Updating battery", thingHandler.thingName);
- updated |= thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
- toQuantityType(getDouble(sdata.bat.value), DIGITS_PERCENT, Units.PERCENT));
+ // 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);
+ }
boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW,
getDouble(sdata.bat.value) < thingHandler.config.lowBattery ? OnOffType.ON : OnOffType.OFF);
updated |= changed;
thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false);
}
}
+
if (sdata.motion != null) { // Shelly Sense
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
getOnOff(sdata.motion));
if (sdata.sensor != null) { // Shelly Motion
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
getOnOff(sdata.sensor.motion));
- updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS,
- getTimestamp(getString(profile.settings.timezone), sdata.sensor.motionTimestamp));
+ long timestamp = getLong(sdata.sensor.motionTimestamp);
+ if (timestamp != 0) {
+ updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION_TS,
+ getTimestamp(getString(profile.settings.timezone), timestamp));
+ }
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
getOnOff(sdata.sensor.vibration));
}
- if (sdata.charger != null) {
- updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
- getOnOff(sdata.charger));
- }
updated |= thingHandler.updateInputs(status);
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.shelly.internal.util.ShellyUtils;
+
+/***
+ * {@link ShellyDeviceStats} some statistical values for the thing
+ *
+ * @author Markus Michels - Initial contribution
+ */
+@NonNullByDefault
+public class ShellyDeviceStats {
+ public long lastUptime = 0;
+ public long unexpectedRestarts = 0;
+ public long timeoutErrors = 0;
+ public long timeoutsRecorvered = 0;
+ public long remainingWatchdog = 0;
+ public long alarms = 0;
+ public String lastAlarm = "";
+ public long lastAlarmTs = 0;
+ public long coiotMessages = 0;
+ public long coiotErrors = 0;
+
+ public Map<String, String> asProperties(String timeZone) {
+ Map<String, String> prop = new HashMap<>();
+ prop.put("lastUptime", String.valueOf(lastUptime));
+ prop.put("unexpectedRestarts", String.valueOf(unexpectedRestarts));
+ prop.put("timeoutErrors", String.valueOf(timeoutErrors));
+ prop.put("timeoutsRecovered", String.valueOf(timeoutsRecorvered));
+ prop.put("remainingWatchdog", String.valueOf(remainingWatchdog));
+ prop.put("alarmCount", String.valueOf(alarms));
+ prop.put("lastAlarm", lastAlarm);
+ prop.put("lastAlarmTs",
+ lastAlarmTs != 0 ? ShellyUtils.getTimestamp(timeZone, lastAlarmTs).format(null).replace('T', ' ') : "");
+ prop.put("coiotMessages", String.valueOf(coiotMessages));
+ prop.put("coiotErrors", String.valueOf(coiotErrors));
+ return prop;
+ }
+}
}
}
- if ((command == UpDownType.UP) || (command == OnOffType.ON)) {
+ if (command == UpDownType.UP || command == OnOffType.ON) {
logger.debug("{}: Open roller", thingName);
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
int pos = profile.getRollerFav(config.favoriteUP - 1);
logger.debug("{}: Use favoriteUP id {} for positioning roller({}%)", thingName, config.favoriteUP,
pos);
}
- } else if ((command == UpDownType.DOWN) || (command == OnOffType.OFF)) {
+ } else if (command == UpDownType.DOWN || command == OnOffType.OFF) {
logger.debug("{}: Closing roller", thingName);
int pos = profile.getRollerFav(config.favoriteDOWN - 1);
if (pos > 0) {
CHANNEL_SENSOR_ILLUM);
addChannel(thing, newChannels, sdata.flood != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
addChannel(thing, newChannels, sdata.smoke != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD);
- addChannel(thing, newChannels, sdata.charger != null, CHGR_DEVST, CHANNEL_DEVST_CHARGER);
- addChannel(thing, newChannels, sdata.motion != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION);
- if (sdata.sensor != null) { // DW2 or Motion
+ addChannel(thing, newChannels, (profile.settings.externalPower != null) || (sdata.charger != null), CHGR_DEVST,
+ CHANNEL_DEVST_CHARGER);
+ addChannel(thing, newChannels,
+ sdata.motion != null || ((sdata.sensor != null) && (sdata.sensor.motion != null)), CHANNEL_GROUP_SENSOR,
+ CHANNEL_SENSOR_MOTION);
+ if (sdata.sensor != null) { // DW, Sense or Motion
addChannel(thing, newChannels, sdata.sensor.state != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT); // DW/DW2
addChannel(thing, newChannels, sdata.sensor.motionTimestamp != null, CHANNEL_GROUP_SENSOR, // Motion
CHANNEL_SENSOR_MOTION_TS);
*
* SPDX-License-Identifier: EPL-2.0
*/
+
package org.openhab.binding.shelly.internal.util;
import static org.openhab.binding.shelly.internal.util.ShellyUtils.mkChannelId;
}
public static String substringAfterLast(@Nullable String string, String pattern) {
- if (string != null) {
- int pos = string.lastIndexOf(pattern);
- if (pos != -1) {
- return string.substring(pos + pattern.length());
- }
+ if (string == null) {
+ return "";
}
- return "";
+ int pos = string.lastIndexOf(pattern);
+ if (pos != -1) {
+ return string.substring(pos + pattern.length());
+ }
+ return string;
}
public static String substringBetween(@Nullable String string, String begin, String end) {
}
}
- public static String urlEncode(String input) throws ShellyApiException {
+ public static String urlEncode(String input) {
try {
return URLEncoder.encode(input, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
- throw new ShellyApiException(
- "Unsupported encoding format: " + StandardCharsets.UTF_8.toString() + ", input=" + input, e);
+ return input;
}
}
binding.shelly.config.autoCoIoT.description = If enabled CoIoT will be automatically used when the devices runs a firmware version 1.6 or newer; false: Use thing configuration to enabled/disable CoIoT events.
discovery.failed# Config status messages
-config-status.error.missing-device-ip=IP address of the Shelly device is missing.
+config-status.error.missing-device-ip = IP address of the Shelly device is missing.
# Thing status descriptions
offline.conf-error-no-credentials = Device is password protected, but no credentials have been configured.
offline.status-error-unexpected-api-result = An unexpected API response. Please verify the logfile to get more detailed information.
offline.status-error-watchdog = Device is not responding, seems to be unavailable.
offline.status-error-restarted = The device has restarted and will be re-initialized.
+offline.status-error-fwupgrade = Firmware upgrade in progress
message.versioncheck.failed = Unable to check firmware version: {0}
message.versioncheck.beta = Device is running a Beta version: {0}/{1} ({2}),make sure this is newer than {3} release build.
binding.shelly.config.autoCoIoT.description = Bei aktiviertem Auto-CoIoT wird das Protokoll aktiviert, sobald das Gerät eine Firmwareversion 1.6 oder neuer verwendet. Andernfalls wird dies über die Thing-Konfiguration gesteuert.
# Config status messages
-config-status.error.missing-deviceip=Die IP-Adresse des Shelly Gerätes ist nicht konfiguriert.
+config-status.error.missing-device-ip = Die IP-Adresse des Shelly Gerätes ist nicht konfiguriert.
# Thing status descriptions
offline.conf-error-no-credentials = Gerät ist passwortgeschützt, aber es sind keine Anmeldedaten konfiguriert.
offline.status-error-unexpected-api-result = Es trat ein unerwartetes Problem beim API-Zugriff auf. Überprüfen Sie die Logdatei für genauere Informationen.
offline.status-error-watchdog = Das Gerät antwortet nicht und ist vermutlich nicht mehr verfügbar.
offline.status-error-restarted = Das Gerät wurde neu gestartet und wird erneut initialisiert.
+offline.status-error-fwupgrade = Gerätesoftware wird aktualisiert
# Status error messages
config-status.error.missing-userid = Keine Benutzerkennung in der Thing Konfiguration
# General messages
message.versioncheck.failed = Firmware-Version konnte nicht geprüft werden: {0}
message.versioncheck.beta = Es wurde eine Betaversion erkannt: {0}/{1} ({2}), bitte sicherstellen, dass diese neuer ist als Version {3} (Release Build).
-message.versioncheck.tooold = ACHTUNG: Eine alter Firmware wurde erkannt: {0}/{1} ({2}), minimal erforderlich {3}.
+message.versioncheck.tooold = ACHTUNG: Eine alte Firmware wurde erkannt: {0}/{1} ({2}), minimal erforderlich {3}.
message.versioncheck.update = INFO: Eine neue Firmwareversion ist verfügbar, aktuell: {0}, neu: {1}
message.versioncheck.autocoiot = INFO: Die Firmware unterstützt die Anforderung, Auto-CoIoT wurde aktiviert.
message.init.noipaddress = Es konnte keine lokale IP-Adresse ermittelt werden. Bitte sicherstellen, dass IPv4 aktiviert ist und das richtige Interface in der openHAB Netzwerk-Konfiguration ausgewählt ist.
channel-type.shelly.whiteBrightness.description = Helligkeit (0-100%, 0=aus)
channel-type.shelly.meterWatts.label = Leistung
channel-type.shelly.meterWatts.description = Aktueller Stromverbrauch in Watt
-channel-type.shelly.meterAccuWatts.label = Kumulierte Verbrauch
-channel-type.shelly.meterAccuWatts.description = Kumulierterr Verbrauch in Watt
+channel-type.shelly.meterAccuWatts.label = Kumulierter Verbrauch
+channel-type.shelly.meterAccuWatts.description = Kumulierter Verbrauch in Watt
channel-type.shelly.meterAccuTotal.label = Kumulierter Gesamtverbrauch
channel-type.shelly.meterAccuTotal.description = Kumulierter Gesamtverbrauch in kW/h
channel-type.shelly.meterAccuReturned.label = Kumulierte Einspeisung
channel-type.shelly.sensorIllumination.description = Angabe zum erkannten Tageslichtwert
channel-type.shelly.sensorPPM.label = Gas-Konzentration
channel-type.shelly.sensorPPM.description = Gemessene Konzentration in PPM
-channel-type.shelly.sensorADC.label = Spannung (ADC)
+channel-type.shelly.sensorADC.label = Voltage (ADC)
channel-type.shelly.sensorADC.description = Gemessene Spannung
channel-type.shelly.sensorTilt.label = Öffnungswinkel
channel-type.shelly.sensorTilt.description = Öffnungswinkel in Grad (erfordert Kalibrierung in der App)
<state readOnly="true">
</state>
</channel-type>
- <channel-type id="batVoltage" advanced="true">
- <item-type>Number:ElectricPotential</item-type>
- <label>Battery Voltage</label>
- <description>Battery voltage in V</description>
- <state readOnly="true" pattern="%.1f %unit%">
+ <channel-type id="externalPower" advanced="true">
+ <item-type>Switch</item-type>
+ <label>External Power</label>
+ <description>ON: External power is connected</description>
+ <state readOnly="true">
</state>
</channel-type>
<channel-type id="uptime" advanced="true">
</thing-type>
<thing-type id="shellymotion">
- <label>Shelly Motion</label>
+ <label>Shelly Motion (SHMOS-1)</label>
<description>Shelly Motion Sensor (battery powered)</description>
<channel-groups>