added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.shelly-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-shelly" description="Shelly Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-coap</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.shelly/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ShellyBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyBindingConstants {
|
||||
|
||||
public static final String VENDOR = "Shelly";
|
||||
public static final String BINDING_ID = "shelly";
|
||||
public static final String SYSTEM_ID = "system";
|
||||
|
||||
// Type names
|
||||
public static final String THING_TYPE_SHELLY1_STR = "shelly1";
|
||||
public static final String THING_TYPE_SHELLY1PM_STR = "shelly1pm";
|
||||
public static final String THING_TYPE_SHELLYEM_STR = "shellyem";
|
||||
public static final String THING_TYPE_SHELLY3EM_STR = "shellyem3"; // bad: misspelled product name, it's 3EM
|
||||
public static final String THING_TYPE_SHELLY2_PREFIX = "shellyswitch";
|
||||
public static final String THING_TYPE_SHELLY2_RELAY_STR = "shelly2-relay";
|
||||
public static final String THING_TYPE_SHELLY2_ROLLER_STR = "shelly2-roller";
|
||||
public static final String THING_TYPE_SHELLY25_PREFIX = "shellyswitch25";
|
||||
public static final String THING_TYPE_SHELLY25_RELAY_STR = "shelly25-relay";
|
||||
public static final String THING_TYPE_SHELLY25_ROLLER_STR = "shelly25-roller";
|
||||
public static final String THING_TYPE_SHELLY4PRO_STR = "shelly4pro";
|
||||
public static final String THING_TYPE_SHELLYPLUG_STR = "shellyplug";
|
||||
public static final String THING_TYPE_SHELLYPLUGS_STR = "shellyplugs";
|
||||
public static final String THING_TYPE_SHELLYDIMMER_STR = "shellydimmer";
|
||||
public static final String THING_TYPE_SHELLYDIMMER2_STR = "shellydimmer2";
|
||||
public static final String THING_TYPE_SHELLYIX3_STR = "shellyix3";
|
||||
public static final String THING_TYPE_SHELLYBULB_STR = "shellybulb";
|
||||
public static final String THING_TYPE_SHELLYDUO_STR = "shellybulbduo";
|
||||
public static final String THING_TYPE_SHELLYVINTAGE_STR = "shellyvintage";
|
||||
public static final String THING_TYPE_SHELLYRGBW2_PREFIX = "shellyrgbw2";
|
||||
public static final String THING_TYPE_SHELLYRGBW2_COLOR_STR = "shellyrgbw2-color";
|
||||
public static final String THING_TYPE_SHELLYRGBW2_WHITE_STR = "shellyrgbw2-white";
|
||||
public static final String THING_TYPE_SHELLYHT_STR = "shellyht";
|
||||
public static final String THING_TYPE_SHELLYSMOKE_STR = "shellysmoke";
|
||||
public static final String THING_TYPE_SHELLYGAS_STR = "shellygas";
|
||||
public static final String THING_TYPE_SHELLYFLOOD_STR = "shellyflood";
|
||||
public static final String THING_TYPE_SHELLYDOORWIN_STR = "shellydw";
|
||||
public static final String THING_TYPE_SHELLYDOORWIN2_STR = "shellydw2";
|
||||
public static final String THING_TYPE_SHELLYEYE_STR = "shellyseye";
|
||||
public static final String THING_TYPE_SHELLYSENSE_STR = "shellysense";
|
||||
public static final String THING_TYPE_SHELLYBUTTON1_STR = "shellybutton1";
|
||||
public static final String THING_TYPE_SHELLYPROTECTED_STR = "shellydevice";
|
||||
public static final String THING_TYPE_SHELLYUNKNOWN_STR = "shellyunknown";
|
||||
|
||||
// Device Types
|
||||
public static final String SHELLYDT_1 = "SHSW-1";
|
||||
public static final String SHELLYDT_1PM = "SHSW-PM";
|
||||
public static final String SHELLYDT_SHPLG = "SHPLG-1";
|
||||
public static final String SHELLYDT_SHPLG_S = "SHPLG-S";
|
||||
public static final String SHELLYDT_SHELLY2 = "SHSW-21";
|
||||
public static final String SHELLYDT_SHELLY25 = "SHSW-25";
|
||||
public static final String SHELLYDT_SHPRO = "SHSW-44";
|
||||
public static final String SHELLYDT_EM = "SHEM";
|
||||
public static final String SHELLYDT_3EM = "SHEM-3";
|
||||
public static final String SHELLYDT_HT = "SHHT-1";
|
||||
public static final String SHELLYDT_DW = "SHDW-1";
|
||||
public static final String SHELLYDT_DW2 = "SHDW-2";
|
||||
public static final String SHELLYDT_SENSE = "SHSEN-1";
|
||||
public static final String SHELLYDT_GAS = "SHGS-1";
|
||||
public static final String SHELLYDT_DIMMER = "SHDM-1";
|
||||
public static final String SHELLYDT_DIMMER2 = "SHDM-2";
|
||||
public static final String SHELLYDT_IX3 = "SHIX3-1";
|
||||
public static final String SHELLYDT_BULB = "SHBLB-1";
|
||||
public static final String SHELLYDT_DUO = "SHBDUO-1";
|
||||
public static final String SHELLYDT_VINTAGE = "SHVIN-1";
|
||||
public static final String SHELLYDT_RGBW2 = "SHRGBW2";
|
||||
public static final String SHELLYDT_BUTTON1 = "SHBTN-1";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY1 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY1PM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY1PM_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYEM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEM_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY3EM = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY3EM_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY2_RELAY = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLY2_RELAY_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY2_ROLLER = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLY2_ROLLER_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY25_RELAY = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLY25_RELAY_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY25_ROLLER = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLY25_ROLLER_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLY4PRO = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLY4PRO_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPLUG = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUG_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPLUGS = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPLUGS_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDIMMER = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYDIMMER_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDIMMER2 = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYDIMMER2_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYIX3 = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYIX3_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYBULB = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYBULB_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDUO = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYDUO_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYVINTAGE = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYVINTAGE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYHT = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYHT_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYSENSE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSENSE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYSMOKE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYSMOKE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYGAS = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYGAS_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYFLOOD = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYFLOOD_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDOORWIN = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYDOORWIN_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYDOORWIN2 = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYDOORWIN2_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYBUTTON1 = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYBUTTON1_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYEYE = new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYEYE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_COLOR = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYRGBW2_COLOR_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYRGBW2_WHITE = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYRGBW2_WHITE_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYPROTECTED = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYPROTECTED_STR);
|
||||
public static final ThingTypeUID THING_TYPE_SHELLYUNKNOWN = new ThingTypeUID(BINDING_ID,
|
||||
THING_TYPE_SHELLYUNKNOWN_STR);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream
|
||||
.of(THING_TYPE_SHELLY1, THING_TYPE_SHELLY1PM, THING_TYPE_SHELLYEM, THING_TYPE_SHELLY3EM,
|
||||
THING_TYPE_SHELLY2_RELAY, THING_TYPE_SHELLY2_ROLLER, THING_TYPE_SHELLY25_RELAY,
|
||||
THING_TYPE_SHELLY25_ROLLER, THING_TYPE_SHELLY4PRO, THING_TYPE_SHELLYPLUG,
|
||||
THING_TYPE_SHELLYPLUGS, THING_TYPE_SHELLYDIMMER, THING_TYPE_SHELLYDIMMER2,
|
||||
THING_TYPE_SHELLYIX3, THING_TYPE_SHELLYBULB, THING_TYPE_SHELLYDUO, THING_TYPE_SHELLYVINTAGE,
|
||||
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_SHELLYPROTECTED, THING_TYPE_SHELLYUNKNOWN)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
// Thing Configuration Properties
|
||||
public static final String CONFIG_DEVICEIP = "deviceIp";
|
||||
public static final String CONFIG_HTTP_USERID = "userId";
|
||||
public static final String CONFIG_HTTP_PASSWORD = "password";
|
||||
public static final String CONFIG_UPDATE_INTERVAL = "updateInterval";
|
||||
|
||||
public static final String PROPERTY_SERVICE_NAME = "serviceName";
|
||||
public static final String PROPERTY_DEV_NAME = "deviceName";
|
||||
public static final String PROPERTY_DEV_TYPE = "deviceType";
|
||||
public static final String PROPERTY_DEV_MODE = "deviceMode";
|
||||
public static final String PROPERTY_HWREV = "deviceHwRev";
|
||||
public static final String PROPERTY_HWBATCH = "deviceHwBatch";
|
||||
public static final String PROPERTY_UPDATE_PERIOD = "devUpdatePeriod";
|
||||
public static final String PROPERTY_NUM_RELAYS = "numberRelays";
|
||||
public static final String PROPERTY_NUM_ROLLERS = "numberRollers";
|
||||
public static final String PROPERTY_NUM_METER = "numberMeters";
|
||||
public static final String PROPERTY_LAST_ACTIVE = "lastActive";
|
||||
public static final String PROPERTY_WIFI_NETW = "wifiNetwork";
|
||||
public static final String PROPERTY_UPDATE_STATUS = "updateStatus";
|
||||
public static final String PROPERTY_UPDATE_AVAILABLE = "updateAvailable";
|
||||
public static final String PROPERTY_UPDATE_CURR_VERS = "updateCurrentVersion";
|
||||
public static final String PROPERTY_UPDATE_NEW_VERS = "updateNewVersion";
|
||||
public static final String PROPERTY_COAP_DESCR = "coapDeviceDescr";
|
||||
public static final String PROPERTY_COAP_VERSION = "coapVersion";
|
||||
public static final String PROPERTY_STATS_TIMEOUTS = "statsTimeoutErrors";
|
||||
public static final String PROPERTY_STATS_TRECOVERED = "statsTimeoutsRecovered";
|
||||
public static final String PROPERTY_COIOTAUTO = "coiotAutoEnable";
|
||||
public static final String PROPERTY_COIOTREFRESH = "coiotAutoRefresh";
|
||||
|
||||
// Relay
|
||||
public static final String CHANNEL_GROUP_RELAY_CONTROL = "relay";
|
||||
public static final String CHANNEL_OUTPUT_NAME = "outputName";
|
||||
public static final String CHANNEL_OUTPUT = "output";
|
||||
public static final String CHANNEL_INPUT = "input";
|
||||
public static final String CHANNEL_INPUT1 = "input1";
|
||||
public static final String CHANNEL_INPUT2 = "input2";
|
||||
public static final String CHANNEL_BRIGHTNESS = "brightness";
|
||||
|
||||
public static final String CHANNEL_TIMER_AUTOON = "autoOn";
|
||||
public static final String CHANNEL_TIMER_AUTOOFF = "autoOff";
|
||||
public static final String CHANNEL_TIMER_ACTIVE = "timerActive";
|
||||
|
||||
// Roller
|
||||
public static final String CHANNEL_GROUP_ROL_CONTROL = "roller";
|
||||
public static final String CHANNEL_ROL_CONTROL_CONTROL = "control";
|
||||
public static final String CHANNEL_ROL_CONTROL_POS = "rollerpos";
|
||||
public static final String CHANNEL_ROL_CONTROL_TIMER = "timer";
|
||||
public static final String CHANNEL_ROL_CONTROL_STATE = "state";
|
||||
public static final String CHANNEL_ROL_CONTROL_STOPR = "stopReason";
|
||||
|
||||
// Dimmer
|
||||
public static final String CHANNEL_GROUP_DIMMER_CONTROL = CHANNEL_GROUP_RELAY_CONTROL;
|
||||
|
||||
// Power meter
|
||||
public static final String CHANNEL_GROUP_METER = "meter";
|
||||
public static final String CHANNEL_METER_CURRENTWATTS = "currentWatts";
|
||||
public static final String CHANNEL_METER_LASTMIN = "lastPower";
|
||||
public static final String CHANNEL_METER_LASTMIN1 = CHANNEL_METER_LASTMIN + "1";
|
||||
public static final String CHANNEL_METER_TOTALKWH = "totalKWH";
|
||||
public static final String CHANNEL_EMETER_TOTALRET = "returnedKWH";
|
||||
public static final String CHANNEL_EMETER_REACTWATTS = "reactiveWatts";
|
||||
public static final String CHANNEL_EMETER_VOLTAGE = "voltage";
|
||||
public static final String CHANNEL_EMETER_CURRENT = "current";
|
||||
public static final String CHANNEL_EMETER_PFACTOR = "powerFactor";
|
||||
|
||||
public static final String CHANNEL_GROUP_SENSOR = "sensors";
|
||||
public static final String CHANNEL_SENSOR_TEMP = "temperature";
|
||||
public static final String CHANNEL_SENSOR_HUM = "humidity";
|
||||
public static final String CHANNEL_SENSOR_LUX = "lux";
|
||||
public static final String CHANNEL_SENSOR_PPM = "ppm";
|
||||
public static final String CHANNEL_SENSOR_ILLUM = "illumination";
|
||||
public static final String CHANNEL_SENSOR_VIBRATION = "vibration";
|
||||
public static final String CHANNEL_SENSOR_TILT = "tilt";
|
||||
public static final String CHANNEL_SENSOR_FLOOD = "flood";
|
||||
public static final String CHANNEL_SENSOR_SMOKE = "smoke";
|
||||
public static final String CHANNEL_SENSOR_CONTACT = "state";
|
||||
public static final String CHANNEL_SENSOR_VALVE = "valve";
|
||||
public static final String CHANNEL_SENSOR_SSTATE = "status"; // Shelly Gas
|
||||
public static final String CHANNEL_SENSOR_ALARM_STATE = "alarmState";
|
||||
public static final String CHANNEL_SENSOR_MOTION = "motion";
|
||||
public static final String CHANNEL_SENSOR_ERROR = "lastError";
|
||||
|
||||
// External sensors for Shelly1/1PM
|
||||
public static final String CHANNEL_ESENDOR_TEMP1 = CHANNEL_SENSOR_TEMP + "1";
|
||||
public static final String CHANNEL_ESENDOR_TEMP2 = CHANNEL_SENSOR_TEMP + "2";
|
||||
public static final String CHANNEL_ESENDOR_TEMP3 = CHANNEL_SENSOR_TEMP + "3";
|
||||
public static final String CHANNEL_ESENDOR_HUMIDITY = CHANNEL_SENSOR_HUM;
|
||||
|
||||
public static final String CHANNEL_GROUP_SENSE_CONTROL = "control";
|
||||
public static final String CHANNEL_SENSE_KEY = "key";
|
||||
|
||||
public static final String CHANNEL_GROUP_BATTERY = "battery";
|
||||
public static final String CHANNEL_SENSOR_BAT_LEVEL = "batteryLevel";
|
||||
public static final String CHANNEL_SENSOR_BAT_LOW = "lowBattery";
|
||||
|
||||
public static final String CHANNEL_GROUP_LIGHT_CONTROL = "control";
|
||||
public static final String CHANNEL_LIGHT_COLOR_MODE = "mode";
|
||||
public static final String CHANNEL_LIGHT_POWER = "power";
|
||||
public static final String CHANNEL_LIGHT_DEFSTATE = "defaultState";
|
||||
public static final String CHANNEL_GROUP_LIGHT_CHANNEL = "channel";
|
||||
|
||||
// Bulb/RGBW2 in color mode
|
||||
public static final String CHANNEL_GROUP_COLOR_CONTROL = "color";
|
||||
public static final String CHANNEL_COLOR_PICKER = "hsb";
|
||||
public static final String CHANNEL_COLOR_FULL = "full";
|
||||
public static final String CHANNEL_COLOR_RED = "red";
|
||||
public static final String CHANNEL_COLOR_GREEN = "green";
|
||||
public static final String CHANNEL_COLOR_BLUE = "blue";
|
||||
public static final String CHANNEL_COLOR_WHITE = "white";
|
||||
public static final String CHANNEL_COLOR_GAIN = "gain";
|
||||
public static final String CHANNEL_COLOR_EFFECT = "effect";
|
||||
|
||||
// Bulb/RGBW2/Dup in White Mode
|
||||
public static final String CHANNEL_GROUP_WHITE_CONTROL = "white";
|
||||
public static final String CHANNEL_COLOR_TEMP = "temperature";
|
||||
|
||||
// Device Status
|
||||
public static final String CHANNEL_GROUP_DEV_STATUS = "device";
|
||||
public static final String CHANNEL_DEVST_NAME = "deviceName";
|
||||
public static final String CHANNEL_DEVST_UPTIME = "uptime";
|
||||
public static final String CHANNEL_DEVST_HEARTBEAT = "heartBeat";
|
||||
public static final String CHANNEL_DEVST_RSSI = "wifiSignal";
|
||||
public static final String CHANNEL_DEVST_ITEMP = "internalTemp";
|
||||
public static final String CHANNEL_DEVST_WAKEUP = "wakeupReason";
|
||||
public static final String CHANNEL_DEVST_ALARM = "alarm";
|
||||
public static final String CHANNEL_DEVST_ACCUWATTS = "accumulatedWatts";
|
||||
public static final String CHANNEL_DEVST_ACCUTOTAL = "accumulatedWTotal";
|
||||
public static final String CHANNEL_DEVST_ACCURETURNED = "accumulatedReturned";
|
||||
public static final String CHANNEL_DEVST_CHARGER = "charger";
|
||||
public static final String CHANNEL_DEVST_UPDATE = "updateAvailable";
|
||||
public static final String CHANNEL_DEVST_SELFTTEST = "selfTest";
|
||||
|
||||
public static final String CHANNEL_LED_STATUS_DISABLE = "statusLed";
|
||||
public static final String CHANNEL_LED_POWER_DISABLE = "powerLed";
|
||||
// Button/xi3
|
||||
public static final String CHANNEL_GROUP_STATUS = "status";
|
||||
public static final String CHANNEL_STATUS_EVENTTYPE = "lastEvent";
|
||||
public static final String CHANNEL_STATUS_EVENTCOUNT = "eventCount";
|
||||
|
||||
// General
|
||||
public static final String CHANNEL_LAST_UPDATE = "lastUpdate";
|
||||
public static final String CHANNEL_EVENT_TRIGGER = "event";
|
||||
public static final String CHANNEL_BUTTON_TRIGGER = "button";
|
||||
|
||||
public static final String SERVICE_TYPE = "_http._tcp.local.";
|
||||
public static final String SHELLY_API_MIN_FWVERSION = "v1.5.7";// v1.5.7+
|
||||
public static final String SHELLY_API_MIN_FWCOIOT = "v1.6";// v1.6.0+
|
||||
public static final String SHELLY_API_FWCOIOT2 = "v1.8";// CoAP 2 with FW 1.8+
|
||||
|
||||
// Alarm types/messages
|
||||
public static final String ALARM_TYPE_NONE = "NONE";
|
||||
public static final String ALARM_TYPE_RESTARTED = "RESTARTED";
|
||||
public static final String ALARM_TYPE_OVERTEMP = "OVERTEMP";
|
||||
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_LOW_BATTERY = "LOW_BATTERY";
|
||||
|
||||
// Event types
|
||||
public static final String EVENT_TYPE_RELAY = "relay";
|
||||
public static final String EVENT_TYPE_ROLLER = "roller";
|
||||
public static final String EVENT_TYPE_LIGHT = "light";
|
||||
public static final String EVENT_TYPE_SENSORDATA = "report";
|
||||
|
||||
// URI for the EventServlet
|
||||
public static final String SHELLY_CALLBACK_URI = "/shelly/event";
|
||||
|
||||
public static final int DIM_STEPSIZE = 5;
|
||||
|
||||
// Formatting: Number of scaling digits
|
||||
public static final int DIGITS_NONE = 0;
|
||||
public static final int DIGITS_WATT = 1;
|
||||
public static final int DIGITS_KWH = 3;
|
||||
public static final int DIGITS_VOLT = 1;
|
||||
public static final int DIGITS_TEMP = 1;
|
||||
public static final int DIGITS_LUX = 1;
|
||||
public static final int DIGITS_PERCENT = 1;
|
||||
|
||||
public static final int SHELLY_API_TIMEOUT_MS = 5000;
|
||||
public static final int UPDATE_STATUS_INTERVAL_SECONDS = 3; // check for updates every x sec
|
||||
public static final int UPDATE_SKIP_COUNT = 20; // update every x triggers or when a key was pressed
|
||||
public static final int UPDATE_MIN_DELAY = 15;// update every x triggers or when a key was pressed
|
||||
public static final int UPDATE_SETTINGS_INTERVAL_SECONDS = 60; // check for updates every x sec
|
||||
public static final int HEALTH_CHECK_INTERVAL_SEC = 300; // Health check interval, 5min
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyLightHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyProtectedHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyRelayHandler;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyUtils;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.net.HttpServiceUtil;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.reactivex.annotations.NonNull;
|
||||
|
||||
/**
|
||||
* The {@link ShellyHandlerFactory} is responsible for creating things and thing handlers.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { ThingHandlerFactory.class, ShellyHandlerFactory.class }, configurationPid = "binding.shelly")
|
||||
public class ShellyHandlerFactory extends BaseThingHandlerFactory {
|
||||
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 = new ConcurrentHashSet<>();
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = ShellyBindingConstants.SUPPORTED_THING_TYPES_UIDS;
|
||||
private ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
|
||||
private String localIP = "";
|
||||
private int httpPort = -1;
|
||||
|
||||
/**
|
||||
* Activate the bundle: save properties
|
||||
*
|
||||
* @param componentContext
|
||||
* @param configProperties set of properties from cfg (use same names as in
|
||||
* thing config)
|
||||
*/
|
||||
@Activate
|
||||
public ShellyHandlerFactory(@Reference NetworkAddressService networkAddressService,
|
||||
@Reference LocaleProvider localeProvider, @Reference TranslationProvider i18nProvider,
|
||||
@Reference HttpClientFactory httpClientFactory, ComponentContext componentContext,
|
||||
Map<String, Object> configProperties) {
|
||||
logger.debug("Activate Shelly HandlerFactory");
|
||||
super.activate(componentContext);
|
||||
|
||||
messages = new ShellyTranslationProvider(bundleContext.getBundle(), i18nProvider, localeProvider);
|
||||
localIP = ShellyUtils.getString(networkAddressService.getPrimaryIpv4HostAddress());
|
||||
if (localIP.isEmpty()) {
|
||||
logger.warn("{}", messages.get("message.init.noipaddress"));
|
||||
}
|
||||
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
httpPort = HttpServiceUtil.getHttpServicePort(componentContext.getBundleContext());
|
||||
if (httpPort == -1) {
|
||||
httpPort = 8080;
|
||||
}
|
||||
logger.debug("Using OH HTTP port {}", httpPort);
|
||||
|
||||
this.coapServer = new ShellyCoapServer();
|
||||
|
||||
// Save bindingConfig & pass it to all registered listeners
|
||||
bindingConfig.updateFromProperties(configProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
String thingType = thingTypeUID.getId();
|
||||
ShellyBaseHandler handler = null;
|
||||
|
||||
if (thingType.equals(THING_TYPE_SHELLYPROTECTED_STR)) {
|
||||
logger.debug("{}: Create new thing of type {} using ShellyProtectedHandler", thing.getLabel(),
|
||||
thingTypeUID.toString());
|
||||
handler = new ShellyProtectedHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort,
|
||||
httpClient);
|
||||
} else if (thingType.equals(THING_TYPE_SHELLYBULB.getId()) || thingType.equals(THING_TYPE_SHELLYDUO.getId())
|
||||
|| thingType.equals(THING_TYPE_SHELLYRGBW2_COLOR.getId())
|
||||
|| thingType.equals(THING_TYPE_SHELLYRGBW2_WHITE.getId())) {
|
||||
logger.debug("{}: Create new thing of type {} using ShellyLightHandler", thing.getLabel(),
|
||||
thingTypeUID.toString());
|
||||
handler = new ShellyLightHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
} else if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
logger.debug("{}: Create new thing of type {} using ShellyRelayHandler", thing.getLabel(),
|
||||
thingTypeUID.toString());
|
||||
handler = new ShellyRelayHandler(thing, messages, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
}
|
||||
|
||||
if (handler != null) {
|
||||
deviceListeners.add(handler);
|
||||
return handler;
|
||||
}
|
||||
|
||||
logger.debug("Unable to create Thing Handler instance!");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove handler of things.
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void removeHandler(@NonNull ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof ShellyBaseHandler) {
|
||||
deviceListeners.remove(thingHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch event to registered devices.
|
||||
*
|
||||
* @param deviceName
|
||||
* @param componentIndex Index of component, e.g. 2 for relay2
|
||||
* @param eventType Type of event, e.g. light
|
||||
* @param parameters Input parameters from URL, e.g. on sensor reports
|
||||
*/
|
||||
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)) {
|
||||
// event processed
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ShellyBindingConfiguration getBindingConfig() {
|
||||
return bindingConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.api;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link CarNetException} implements an extension to the standard Exception class. This allows to keep also the
|
||||
* result of the last API call (e.g. including the http status code in the message).
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyApiException extends Exception {
|
||||
private static final long serialVersionUID = -5809459454769761821L;
|
||||
|
||||
private ShellyApiResult apiResult = new ShellyApiResult();
|
||||
private static String NONE = "none";
|
||||
|
||||
public ShellyApiException(Exception exception) {
|
||||
super(exception);
|
||||
}
|
||||
|
||||
public ShellyApiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ShellyApiException(ShellyApiResult res) {
|
||||
super(NONE);
|
||||
apiResult = res;
|
||||
}
|
||||
|
||||
public ShellyApiException(String message, Exception exception) {
|
||||
super(message, exception);
|
||||
}
|
||||
|
||||
public ShellyApiException(ShellyApiResult result, Exception exception) {
|
||||
super(exception);
|
||||
apiResult = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return isEmpty() ? "" : nonNullString(super.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String message = nonNullString(super.getMessage());
|
||||
String cause = getCauseClass().toString();
|
||||
if (!isEmpty()) {
|
||||
if (isUnknownHost()) {
|
||||
String[] string = message.split(": "); // java.net.UnknownHostException: api.rach.io
|
||||
message = MessageFormat.format("Unable to connect to {0} (Unknown host / Network down / Low signal)",
|
||||
string[1]);
|
||||
} else if (isMalformedURL()) {
|
||||
message = MessageFormat.format("Invalid URL: {0}", apiResult.getUrl());
|
||||
} else if (isTimeout()) {
|
||||
message = MessageFormat.format("Device unreachable or API Timeout ({0})", apiResult.getUrl());
|
||||
} else {
|
||||
message = MessageFormat.format("{0} ({1})", message, cause);
|
||||
}
|
||||
} else {
|
||||
message = apiResult.toString();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public boolean isApiException() {
|
||||
return getCauseClass() == ShellyApiException.class;
|
||||
}
|
||||
|
||||
public boolean isTimeout() {
|
||||
Class<?> extype = !isEmpty() ? getCauseClass() : null;
|
||||
return (extype != null) && ((extype == TimeoutException.class) || (extype == ExecutionException.class)
|
||||
|| (extype == InterruptedException.class) || getMessage().toLowerCase().contains("timeout"));
|
||||
}
|
||||
|
||||
public boolean isHttpAccessUnauthorized() {
|
||||
return apiResult.isHttpAccessUnauthorized();
|
||||
}
|
||||
|
||||
public boolean isUnknownHost() {
|
||||
return getCauseClass() == MalformedURLException.class;
|
||||
}
|
||||
|
||||
public boolean isMalformedURL() {
|
||||
return getCauseClass() == UnknownHostException.class;
|
||||
}
|
||||
|
||||
public boolean isJSONException() {
|
||||
return getCauseClass() == JsonSyntaxException.class;
|
||||
}
|
||||
|
||||
public ShellyApiResult getApiResult() {
|
||||
return apiResult;
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
return nonNullString(super.getMessage()).equals(NONE);
|
||||
}
|
||||
|
||||
private static String nonNullString(@Nullable String s) {
|
||||
return s != null ? s : "";
|
||||
}
|
||||
|
||||
private Class<?> getCauseClass() {
|
||||
Throwable cause = getCause();
|
||||
if (getCause() != null) {
|
||||
return cause.getClass();
|
||||
}
|
||||
return ShellyApiException.class;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.api;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpStatus.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
|
||||
/**
|
||||
* The {@link ShellyApiResult} wraps up the API result and provides some more information like url, http code, received
|
||||
* response etc.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyApiResult {
|
||||
public String url = "";
|
||||
public String method = "";
|
||||
public String response = "";
|
||||
public int httpCode = -1;
|
||||
public String httpReason = "";
|
||||
|
||||
public ShellyApiResult() {
|
||||
}
|
||||
|
||||
public ShellyApiResult(String method, String url) {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public ShellyApiResult(ContentResponse contentResponse) {
|
||||
fillFromResponse(contentResponse);
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return !url.isEmpty() ? method + " " + url : "";
|
||||
}
|
||||
|
||||
public String getHttpResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getUrl() + " > " + getHttpResponse();
|
||||
}
|
||||
|
||||
public boolean isHttpOk() {
|
||||
return httpCode == OK_200;
|
||||
}
|
||||
|
||||
public boolean isHttpAccessUnauthorized() {
|
||||
return (httpCode == UNAUTHORIZED_401 || response.contains(SHELLY_APIERR_UNAUTHORIZED));
|
||||
}
|
||||
|
||||
public boolean isHttpTimeout() {
|
||||
return httpCode == -1 || response.toUpperCase().contains(SHELLY_APIERR_TIMEOUT.toLowerCase());
|
||||
}
|
||||
|
||||
public boolean isHttpServerError() {
|
||||
return httpCode == INTERNAL_SERVER_ERROR_500;
|
||||
}
|
||||
|
||||
public boolean isNotCalibrtated() {
|
||||
return getHttpResponse().contains(SHELLY_APIERR_NOT_CALIBRATED);
|
||||
}
|
||||
|
||||
private void fillFromResponse(@Nullable ContentResponse contentResponse) {
|
||||
if (contentResponse != null) {
|
||||
String r = contentResponse.getContentAsString();
|
||||
response = r != null ? r : "";
|
||||
httpCode = contentResponse.getStatus();
|
||||
httpReason = contentResponse.getReason();
|
||||
|
||||
Request request = contentResponse.getRequest();
|
||||
if (request != null) {
|
||||
url = request.getURI().toString();
|
||||
method = request.getMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.api;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsGlobal;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsInput;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link ShellyDeviceProfile} creates a device profile based on the settings returned from the API's /settings
|
||||
* call. This is used to be more dynamic in controlling the device, but also to overcome some issues in the API (e.g.
|
||||
* RGBW2 returns "no meter" even it has one)
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyDeviceProfile {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyDeviceProfile.class);
|
||||
|
||||
public boolean initialized = false; // true when initialized
|
||||
|
||||
public String thingName = "";
|
||||
public String deviceType = "";
|
||||
|
||||
public String settingsJson = "";
|
||||
public ShellySettingsGlobal settings = new ShellySettingsGlobal();
|
||||
public ShellySettingsStatus status = new ShellySettingsStatus();
|
||||
|
||||
public String hostname = "";
|
||||
public String mode = "";
|
||||
public boolean discoverable = true;
|
||||
|
||||
public String hwRev = "";
|
||||
public String hwBatchId = "";
|
||||
public String mac = "";
|
||||
public String fwId = "";
|
||||
public String fwVersion = "";
|
||||
public String fwDate = "";
|
||||
|
||||
public boolean hasRelays = false; // true if it has at least 1 power meter
|
||||
public int numRelays = 0; // number of relays/outputs
|
||||
public int numRollers = 0; // number of Rollers, usually 1
|
||||
public boolean isRoller = false; // true for Shelly2 in roller mode
|
||||
public boolean isDimmer = false; // true for a Shelly Dimmer (SHDM-1)
|
||||
|
||||
public int numMeters = 0;
|
||||
public boolean isEMeter = false; // true for ShellyEM/3EM
|
||||
|
||||
public boolean isLight = false; // true if it is a Shelly Bulb/RGBW2
|
||||
public boolean isBulb = false; // true only if it is a Bulb
|
||||
public boolean isDuo = false; // true only if it is a Duo
|
||||
public boolean isRGBW2 = false; // true only if it a a RGBW2
|
||||
public boolean inColor = false; // true if bulb/rgbw2 is in color mode
|
||||
|
||||
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 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 boolean isIX3 = false; // true for a Shelly IX
|
||||
|
||||
public int minTemp = 0; // Bulb/Duo: Min Light Temp
|
||||
public int maxTemp = 0; // Bulb/Duo: Max Light Temp
|
||||
|
||||
public int updatePeriod = 2 * UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
|
||||
|
||||
public Map<String, String> irCodes = new HashMap<>(); // Sense: list of stored IR codes
|
||||
|
||||
public ShellyDeviceProfile() {
|
||||
}
|
||||
|
||||
public ShellyDeviceProfile initialize(String thingType, String json) throws ShellyApiException {
|
||||
Gson gson = new Gson();
|
||||
|
||||
initialized = false;
|
||||
|
||||
try {
|
||||
initFromThingType(thingType);
|
||||
settingsJson = json;
|
||||
settings = gson.fromJson(json, ShellySettingsGlobal.class);
|
||||
} catch (IllegalArgumentException | JsonSyntaxException e) {
|
||||
throw new ShellyApiException(
|
||||
thingName + ": Unable to transform settings JSON " + e.toString() + ", json='" + json + "'", e);
|
||||
}
|
||||
|
||||
// General settings
|
||||
deviceType = getString(settings.device.type);
|
||||
mac = getString(settings.device.mac);
|
||||
hostname = settings.device.hostname != null && !settings.device.hostname.isEmpty()
|
||||
? settings.device.hostname.toLowerCase()
|
||||
: "shelly-" + mac.toUpperCase().substring(6, 11);
|
||||
mode = !getString(settings.mode).isEmpty() ? getString(settings.mode).toLowerCase() : "";
|
||||
hwRev = settings.hwinfo != null ? getString(settings.hwinfo.hwRevision) : "";
|
||||
hwBatchId = settings.hwinfo != null ? getString(settings.hwinfo.batchId.toString()) : "";
|
||||
fwDate = substringBefore(settings.fw, "/");
|
||||
fwVersion = substringBetween(settings.fw, "/", "@");
|
||||
fwId = substringAfter(settings.fw, "@");
|
||||
discoverable = (settings.discoverable == null) || settings.discoverable;
|
||||
|
||||
inColor = isLight && mode.equalsIgnoreCase(SHELLY_MODE_COLOR);
|
||||
|
||||
numRelays = !isLight ? getInteger(settings.device.numOutputs) : 0;
|
||||
if ((numRelays > 0) && (settings.relays == null)) {
|
||||
numRelays = 0;
|
||||
}
|
||||
isDimmer = deviceType.equalsIgnoreCase(SHELLYDT_DIMMER) || deviceType.equalsIgnoreCase(SHELLYDT_DIMMER2);
|
||||
hasRelays = (numRelays > 0) || isDimmer;
|
||||
numRollers = getInteger(settings.device.numRollers);
|
||||
|
||||
isEMeter = settings.emeters != null;
|
||||
numMeters = !isEMeter ? getInteger(settings.device.numMeters) : getInteger(settings.device.numEMeters);
|
||||
if ((numMeters == 0) && isLight) {
|
||||
// RGBW2 doesn't report, but has one
|
||||
numMeters = inColor ? 1 : getInteger(settings.device.numOutputs);
|
||||
}
|
||||
isRoller = mode.equalsIgnoreCase(SHELLY_MODE_ROLLER);
|
||||
|
||||
if (settings.sleepMode != null) {
|
||||
// Sensor, usally 12h
|
||||
updatePeriod = getString(settings.sleepMode.unit).equalsIgnoreCase("m") ? settings.sleepMode.period * 60 // minutes
|
||||
: settings.sleepMode.period * 3600; // hours
|
||||
updatePeriod += 600; // give 10min extra
|
||||
} else if ((settings.coiot != null) && (settings.coiot.updatePeriod != null)) {
|
||||
// Derive from CoAP update interval, usually 2*15+5s=50sec -> 70sec
|
||||
updatePeriod = Math.max(UPDATE_SETTINGS_INTERVAL_SECONDS, 3 * getInteger(settings.coiot.updatePeriod)) + 10;
|
||||
} else {
|
||||
updatePeriod = 2 * UPDATE_SETTINGS_INTERVAL_SECONDS + 10;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean containsEventUrl(String eventType) {
|
||||
return containsEventUrl(settingsJson, eventType);
|
||||
}
|
||||
|
||||
public boolean containsEventUrl(String json, String eventType) {
|
||||
String settings = json.toLowerCase();
|
||||
return settings.contains((eventType + SHELLY_EVENTURL_SUFFIX).toLowerCase());
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
public void initFromThingType(String name) {
|
||||
String thingType = (name.contains("-") ? substringBefore(name, "-") : name).toLowerCase().trim();
|
||||
if (thingType.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
isBulb = thingType.equals(THING_TYPE_SHELLYBULB_STR);
|
||||
isDuo = thingType.equals(THING_TYPE_SHELLYDUO_STR) || thingType.equals(THING_TYPE_SHELLYVINTAGE_STR);
|
||||
isRGBW2 = thingType.startsWith(THING_TYPE_SHELLYRGBW2_PREFIX);
|
||||
isLight = isBulb || isDuo || isRGBW2;
|
||||
if (isLight) {
|
||||
minTemp = isBulb ? MIN_COLOR_TEMP_BULB : MIN_COLOR_TEMP_DUO;
|
||||
maxTemp = isBulb ? MAX_COLOR_TEMP_BULB : MAX_COLOR_TEMP_DUO;
|
||||
}
|
||||
|
||||
boolean isFlood = thingType.equals(THING_TYPE_SHELLYFLOOD_STR);
|
||||
boolean isSmoke = thingType.equals(THING_TYPE_SHELLYSMOKE_STR);
|
||||
boolean isGas = thingType.equals(THING_TYPE_SHELLYGAS_STR);
|
||||
isHT = thingType.equals(THING_TYPE_SHELLYHT_STR);
|
||||
isDW = thingType.equals(THING_TYPE_SHELLYDOORWIN_STR) || thingType.equals(THING_TYPE_SHELLYDOORWIN2_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 || isSense;
|
||||
hasBattery = isHT || isFlood || isDW || isSmoke || isButton; // we assume that Sense is connected to // the
|
||||
// charger
|
||||
}
|
||||
|
||||
public String getControlGroup(int i) {
|
||||
if (i < 0) {
|
||||
logger.debug("{}: Invalid index {} for getControlGroup()", thingName, i);
|
||||
return "";
|
||||
}
|
||||
int idx = i + 1;
|
||||
if (isDimmer) {
|
||||
return CHANNEL_GROUP_DIMMER_CONTROL;
|
||||
} else if (isRoller) {
|
||||
return numRollers == 1 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
|
||||
} else if (hasRelays) {
|
||||
return numRelays == 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
|
||||
} else if (isLight) {
|
||||
return numRelays == 1 ? CHANNEL_GROUP_LIGHT_CONTROL : CHANNEL_GROUP_LIGHT_CONTROL + idx;
|
||||
} else if (isButton) {
|
||||
return CHANNEL_GROUP_STATUS;
|
||||
} else if (isSensor) {
|
||||
return CHANNEL_GROUP_SENSOR;
|
||||
}
|
||||
|
||||
// e.g. ix3
|
||||
return numRelays == 1 ? CHANNEL_GROUP_STATUS : CHANNEL_GROUP_STATUS + idx;
|
||||
}
|
||||
|
||||
public String getInputGroup(int i) {
|
||||
int idx = i + 1; // group names are 1-based
|
||||
if (isRGBW2) {
|
||||
return CHANNEL_GROUP_LIGHT_CONTROL;
|
||||
} else if (isIX3) {
|
||||
return CHANNEL_GROUP_STATUS + idx;
|
||||
} else if (isButton) {
|
||||
return CHANNEL_GROUP_STATUS;
|
||||
} else if (isRoller) {
|
||||
return numRelays <= 2 ? CHANNEL_GROUP_ROL_CONTROL : CHANNEL_GROUP_ROL_CONTROL + idx;
|
||||
} else {
|
||||
// Device has 1 input per relay: 0=off, 1+2 depend on switch mode
|
||||
return numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + idx;
|
||||
}
|
||||
}
|
||||
|
||||
public String getInputChannel(int i) {
|
||||
int idx = i + 1; // channel names are 1-based
|
||||
if (isRGBW2 || isIX3) {
|
||||
return CHANNEL_INPUT; // RGBW2 has only 1 channel
|
||||
} else if (hasRelays) {
|
||||
return CHANNEL_INPUT + idx;
|
||||
}
|
||||
return CHANNEL_INPUT;
|
||||
}
|
||||
|
||||
public boolean inButtonMode(int idx) {
|
||||
if (idx < 0) {
|
||||
logger.debug("{}: Invalid index {} for inButtonMode()", thingName, idx);
|
||||
return false;
|
||||
}
|
||||
String btnType = "";
|
||||
if (isButton) {
|
||||
return true;
|
||||
} else if (isIX3) {
|
||||
if ((settings.inputs != null) && (idx >= 0) && (idx < settings.inputs.size())) {
|
||||
ShellySettingsInput input = settings.inputs.get(idx);
|
||||
btnType = input.btnType;
|
||||
}
|
||||
} else if (isDimmer) {
|
||||
if ((settings.dimmers != null) && (idx >= 0) && (idx < settings.dimmers.size())) {
|
||||
ShellySettingsDimmer dimmer = settings.dimmers.get(idx);
|
||||
btnType = dimmer.btnType;
|
||||
}
|
||||
} else if ((settings.relays != null) && (idx >= 0) && (idx < settings.relays.size())) {
|
||||
ShellySettingsRelay relay = settings.relays.get(idx);
|
||||
btnType = relay.btnType;
|
||||
}
|
||||
|
||||
if (btnType.equals(SHELLY_BTNT_MOMENTARY) || btnType.equals(SHELLY_BTNT_MOM_ON_RELEASE)
|
||||
|| btnType.equals(SHELLY_BTNT_DETACHED) || btnType.equals(SHELLY_BTNT_ONE_BUTTON)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.api;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.ShellyHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.ConfigurationPolicy;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link ShellyEventServlet} implements a servlet. which is called by the Shelly device to signnal events (button,
|
||||
* relay output, sensor data). The binding automatically sets those vent urls on startup (when not disabled in the thing
|
||||
* config).
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = HttpServlet.class, configurationPolicy = ConfigurationPolicy.OPTIONAL, immediate = true)
|
||||
public class ShellyEventServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 549582869577534569L;
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyEventServlet.class);
|
||||
|
||||
private final HttpService httpService;
|
||||
private final ShellyHandlerFactory handlerFactory;
|
||||
|
||||
@Activate
|
||||
public ShellyEventServlet(@Reference HttpService httpService, @Reference ShellyHandlerFactory handlerFactory,
|
||||
Map<String, Object> config) {
|
||||
this.httpService = httpService;
|
||||
this.handlerFactory = handlerFactory;
|
||||
try {
|
||||
httpService.registerServlet(SHELLY_CALLBACK_URI, this, null, httpService.createDefaultHttpContext());
|
||||
logger.debug("ShellyEventServlet started at '{}'", SHELLY_CALLBACK_URI);
|
||||
} catch (NamespaceException | ServletException | IllegalArgumentException e) {
|
||||
logger.warn("Could not start CallbackServlet", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
httpService.unregister(SHELLY_CALLBACK_URI);
|
||||
logger.debug("ShellyEventServlet stopped");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(@Nullable HttpServletRequest request, @Nullable HttpServletResponse resp)
|
||||
throws ServletException, IOException, IllegalArgumentException {
|
||||
String path = "";
|
||||
String deviceName = "";
|
||||
String index = "";
|
||||
String type = "";
|
||||
|
||||
if ((request == null) || (resp == null)) {
|
||||
logger.debug("request or resp must not be null!");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
path = request.getRequestURI().toLowerCase();
|
||||
String ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
if (ipAddress == null) {
|
||||
ipAddress = request.getRemoteAddr();
|
||||
}
|
||||
Map<String, String[]> parameters = request.getParameterMap();
|
||||
logger.debug("CallbackServlet: {} Request from {}:{}{}?{}", request.getProtocol(), ipAddress,
|
||||
request.getRemotePort(), path, parameters.toString());
|
||||
if (!path.toLowerCase().startsWith(SHELLY_CALLBACK_URI) || !path.contains("/event/shelly")) {
|
||||
logger.warn("CallbackServlet received unknown request: path = {}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
// URL looks like
|
||||
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/relay/n?xxxxx or
|
||||
// <ip address>:<remote port>/shelly/event/shellyrelay-XXXXXX/roller/n?xxxxx or
|
||||
// <ip address>:<remote port>/shelly/event/shellyht-XXXXXX/sensordata?hum=53,temp=26.50
|
||||
deviceName = substringBetween(path, "/event/", "/").toLowerCase();
|
||||
if (path.contains("/" + EVENT_TYPE_RELAY + "/") || path.contains("/" + EVENT_TYPE_ROLLER + "/")
|
||||
|| path.contains("/" + EVENT_TYPE_LIGHT + "/")) {
|
||||
index = substringAfterLast(path, "/").toLowerCase();
|
||||
type = substringBetween(path, deviceName + "/", "/" + index);
|
||||
} else {
|
||||
index = "";
|
||||
type = substringAfterLast(path, "/").toLowerCase();
|
||||
}
|
||||
logger.trace("{}: Process event of type type={}, index={}", deviceName, type, index);
|
||||
Map<String, String> parms = new TreeMap<>();
|
||||
|
||||
for (Map.Entry<String, String[]> p : parameters.entrySet()) {
|
||||
parms.put(p.getKey(), p.getValue()[0]);
|
||||
|
||||
}
|
||||
handlerFactory.onEvent(ipAddress, deviceName, index, type, parms);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("{}: Exception processing callback: path={}; index={}, type={}, parameters={}", deviceName,
|
||||
path, index, type, request.getParameterMap().toString());
|
||||
} finally {
|
||||
resp.setCharacterEncoding(StandardCharsets.UTF_8.toString());
|
||||
resp.getWriter().write("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.api;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySendKeyList;
|
||||
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.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLight;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import tec.uom.se.unit.Units;
|
||||
|
||||
/**
|
||||
* {@link ShellyHttpApi} wraps the Shelly REST API and provides various low level function to access the device api (not
|
||||
* cloud api).
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyHttpApi {
|
||||
public static final String HTTP_HEADER_AUTH = "Authorization";
|
||||
public static final String HTTP_AUTH_TYPE_BASIC = "Basic";
|
||||
public static final String CONTENT_TYPE_JSON = "application/json; charset=UTF-8";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyHttpApi.class);
|
||||
private final HttpClient httpClient;
|
||||
private ShellyThingConfiguration config = new ShellyThingConfiguration();
|
||||
private String thingName;
|
||||
private final Gson gson = new Gson();
|
||||
private int timeoutErrors = 0;
|
||||
private int timeoutsRecovered = 0;
|
||||
|
||||
private ShellyDeviceProfile profile = new ShellyDeviceProfile();
|
||||
|
||||
public ShellyHttpApi(String thingName, ShellyThingConfiguration config, HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
this.thingName = thingName;
|
||||
setConfig(thingName, config);
|
||||
profile.initFromThingType(thingName);
|
||||
}
|
||||
|
||||
public void setConfig(String thingName, ShellyThingConfiguration config) {
|
||||
this.thingName = thingName;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public ShellySettingsDevice getDevInfo() throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_DEVINFO, ShellySettingsDevice.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the device profile
|
||||
*
|
||||
* @param thingType Type of DEVICE as returned from the thing properties (based on discovery)
|
||||
* @return Initialized ShellyDeviceProfile
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public ShellyDeviceProfile getDeviceProfile(String thingType) throws ShellyApiException {
|
||||
String json = request(SHELLY_URL_SETTINGS);
|
||||
if (json.contains("\"type\":\"SHDM-")) {
|
||||
logger.trace("{}: Detected a Shelly Dimmer: fix Json (replace lights[] tag with dimmers[]", thingName);
|
||||
json = fixDimmerJson(json);
|
||||
}
|
||||
|
||||
// Map settings to device profile for Light and Sense
|
||||
profile.initialize(thingType, json);
|
||||
|
||||
// 2nd level initialization
|
||||
profile.thingName = profile.hostname;
|
||||
if (profile.isLight && (profile.numMeters == 0)) {
|
||||
logger.debug("{}: Get number of meters from light status", thingName);
|
||||
ShellyStatusLight status = getLightStatus();
|
||||
profile.numMeters = status.meters != null ? status.meters.size() : 0;
|
||||
}
|
||||
if (profile.isSense) {
|
||||
profile.irCodes = getIRCodeList();
|
||||
logger.debug("{}: Sense stored key list loaded, {} entries.", thingName, profile.irCodes.size());
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return profile.initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get generic device settings/status. Json returned from API will be mapped to a Gson object
|
||||
*
|
||||
* @return Device settings/status as ShellySettingsStatus object
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public ShellySettingsStatus getStatus() throws ShellyApiException {
|
||||
String json = "";
|
||||
try {
|
||||
json = request(SHELLY_URL_STATUS);
|
||||
// Dimmer2 returns invalid json type for loaderror :-(
|
||||
json = json.replace("\"loaderror\":0,", "\"loaderror\":false,");
|
||||
json = json.replace("\"loaderror\":1,", "\"loaderror\":true,");
|
||||
ShellySettingsStatus status = gson.fromJson(json, ShellySettingsStatus.class);
|
||||
status.json = json;
|
||||
return status;
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new ShellyApiException("Unable to parse JSON: " + json, e);
|
||||
}
|
||||
}
|
||||
|
||||
public ShellyStatusRelay getRelayStatus(Integer relayIndex) throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_STATUS_RELEAY + "/" + relayIndex.toString(), ShellyStatusRelay.class);
|
||||
}
|
||||
|
||||
public ShellyShortLightStatus setRelayTurn(Integer id, String turnMode) throws ShellyApiException {
|
||||
return callApi(getControlUriPrefix(id) + "?" + SHELLY_LIGHT_TURN + "=" + turnMode.toLowerCase(),
|
||||
ShellyShortLightStatus.class);
|
||||
}
|
||||
|
||||
public void setBrightness(Integer id, Integer brightness, boolean autoOn) throws ShellyApiException {
|
||||
String turn = autoOn ? SHELLY_LIGHT_TURN + "=" + SHELLY_API_ON + "&" : "";
|
||||
request(getControlUriPrefix(id) + "?" + turn + "brightness=" + brightness.toString());
|
||||
}
|
||||
|
||||
public ShellyControlRoller getRollerStatus(Integer rollerIndex) throws ShellyApiException {
|
||||
String uri = SHELLY_URL_CONTROL_ROLLER + "/" + rollerIndex.toString() + "/pos";
|
||||
return callApi(uri, ShellyControlRoller.class);
|
||||
}
|
||||
|
||||
public void setRollerTurn(Integer relayIndex, String turnMode) throws ShellyApiException {
|
||||
request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?go=" + turnMode);
|
||||
}
|
||||
|
||||
public void setRollerPos(Integer relayIndex, Integer position) throws ShellyApiException {
|
||||
request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?go=to_pos&roller_pos="
|
||||
+ position.toString());
|
||||
}
|
||||
|
||||
public void setRollerTimer(Integer relayIndex, Integer timer) throws ShellyApiException {
|
||||
request(SHELLY_URL_CONTROL_ROLLER + "/" + relayIndex.toString() + "?timer=" + timer.toString());
|
||||
}
|
||||
|
||||
public ShellyShortLightStatus getLightStatus(Integer index) throws ShellyApiException {
|
||||
return callApi(getControlUriPrefix(index), ShellyShortLightStatus.class);
|
||||
}
|
||||
|
||||
public ShellyStatusSensor getSensorStatus() throws ShellyApiException {
|
||||
ShellyStatusSensor status = callApi(SHELLY_URL_STATUS, ShellyStatusSensor.class);
|
||||
if (profile.isSense) {
|
||||
// complete reported data, map C to F or vice versa: C=(F - 32) * 0.5556;
|
||||
status.tmp.tC = status.tmp.units.equals(SHELLY_TEMP_CELSIUS) ? status.tmp.value
|
||||
: ImperialUnits.FAHRENHEIT.getConverterTo(Units.CELSIUS).convert(getDouble(status.tmp.value))
|
||||
.doubleValue();
|
||||
status.tmp.tF = status.tmp.units.equals(SHELLY_TEMP_FAHRENHEIT) ? status.tmp.value
|
||||
: Units.CELSIUS.getConverterTo(ImperialUnits.FAHRENHEIT).convert(getDouble(status.tmp.value))
|
||||
.doubleValue();
|
||||
}
|
||||
if ((status.charger == null) && (status.externalPower != null)) {
|
||||
// SHelly H&T uses external_power, Sense uses charger
|
||||
status.charger = status.externalPower != 0;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setTimer(Integer index, String timerName, Double value) throws ShellyApiException {
|
||||
String type = SHELLY_CLASS_RELAY;
|
||||
if (profile.isRoller) {
|
||||
type = SHELLY_CLASS_ROLLER;
|
||||
} else if (profile.isLight) {
|
||||
type = SHELLY_CLASS_LIGHT;
|
||||
}
|
||||
String uri = SHELLY_URL_SETTINGS + "/" + type + "/" + index + "?" + timerName + "="
|
||||
+ ((Integer) value.intValue()).toString();
|
||||
request(uri);
|
||||
}
|
||||
|
||||
public void setLedStatus(String ledName, Boolean value) throws ShellyApiException {
|
||||
request(SHELLY_URL_SETTINGS + "?" + ledName + "=" + (value ? SHELLY_API_TRUE : SHELLY_API_FALSE));
|
||||
}
|
||||
|
||||
public ShellySettingsLight getLightSettings() throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_SETTINGS_LIGHT, ShellySettingsLight.class);
|
||||
}
|
||||
|
||||
public ShellyStatusLight getLightStatus() throws ShellyApiException {
|
||||
return callApi(SHELLY_URL_STATUS, ShellyStatusLight.class);
|
||||
}
|
||||
|
||||
public void setLightSetting(String parm, String value) throws ShellyApiException {
|
||||
request(SHELLY_URL_SETTINGS + "?" + parm + "=" + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change between White and Color Mode
|
||||
*
|
||||
* @param mode
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public void setLightMode(String mode) throws ShellyApiException {
|
||||
if (!mode.isEmpty() && !profile.mode.equals(mode)) {
|
||||
setLightSetting(SHELLY_API_MODE, mode);
|
||||
profile.mode = mode;
|
||||
profile.inColor = profile.isLight && profile.mode.equalsIgnoreCase(SHELLY_MODE_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single light parameter
|
||||
*
|
||||
* @param lightIndex Index of the light, usually 0 for Bulb and 0..3 for RGBW2.
|
||||
* @param parm Name of the parameter (see API spec)
|
||||
* @param value The value
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public void setLightParm(Integer lightIndex, String parm, String value) throws ShellyApiException {
|
||||
// Bulb, RGW2: /<color mode>/<light id>?parm?value
|
||||
// Dimmer: /light/<light id>?parm=value
|
||||
request(getControlUriPrefix(lightIndex) + "?" + parm + "=" + value);
|
||||
}
|
||||
|
||||
public void setLightParms(Integer lightIndex, Map<String, String> parameters) throws ShellyApiException {
|
||||
String url = getControlUriPrefix(lightIndex) + "?";
|
||||
int i = 0;
|
||||
for (String key : parameters.keySet()) {
|
||||
if (i > 0) {
|
||||
url = url + "&";
|
||||
}
|
||||
url = url + key + "=" + parameters.get(key);
|
||||
i++;
|
||||
}
|
||||
request(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the IR Code list from the Shelly Sense device. The list could be customized by the user. It defines the
|
||||
* symbolic key code, which gets
|
||||
* map into a PRONTO code
|
||||
*
|
||||
* @return Map of key codes
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public Map<String, String> getIRCodeList() throws ShellyApiException {
|
||||
String result = request(SHELLY_URL_LIST_IR);
|
||||
// take pragmatic approach to make the returned JSon into named arrays for Gson parsing
|
||||
String keyList = substringAfter(result, "[");
|
||||
keyList = substringBeforeLast(keyList, "]");
|
||||
keyList = keyList.replaceAll(java.util.regex.Pattern.quote("\",\""), "\", \"name\": \"");
|
||||
keyList = keyList.replaceAll(java.util.regex.Pattern.quote("["), "{ \"id\":");
|
||||
keyList = keyList.replaceAll(java.util.regex.Pattern.quote("]"), "} ");
|
||||
String json = "{\"key_codes\" : [" + keyList + "] }";
|
||||
|
||||
ShellySendKeyList codes = gson.fromJson(json, ShellySendKeyList.class);
|
||||
Map<String, String> list = new HashMap<>();
|
||||
for (ShellySenseKeyCode key : codes.keyCodes) {
|
||||
list.put(key.id, key.name);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a IR key code to the Shelly Sense.
|
||||
*
|
||||
* @param keyCode A keyCoud could be a symbolic name (as defined in the key map on the device) or a PRONTO Code in
|
||||
* plain or hex64 format
|
||||
*
|
||||
* @throws ShellyApiException
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
public void sendIRKey(String keyCode) throws ShellyApiException, IllegalArgumentException {
|
||||
String type = "";
|
||||
if (profile.irCodes.containsKey(keyCode)) {
|
||||
type = SHELLY_IR_CODET_STORED;
|
||||
} else if ((keyCode.length() > 4) && keyCode.contains(" ")) {
|
||||
type = SHELLY_IR_CODET_PRONTO;
|
||||
} else {
|
||||
type = SHELLY_IR_CODET_PRONTO_HEX;
|
||||
}
|
||||
String url = SHELLY_URL_SEND_IR + "?type=" + type;
|
||||
if (type.equals(SHELLY_IR_CODET_STORED)) {
|
||||
url = url + "&" + "id=" + keyCode;
|
||||
} else if (type.equals(SHELLY_IR_CODET_PRONTO)) {
|
||||
String code = Base64.getEncoder().encodeToString(keyCode.getBytes(StandardCharsets.UTF_8));
|
||||
if (code == null) {
|
||||
throw new IllegalArgumentException("Unable to BASE64 encode the pronto code: " + keyCode);
|
||||
}
|
||||
url = url + "&" + SHELLY_IR_CODET_PRONTO + "=" + code;
|
||||
} else if (type.equals(SHELLY_IR_CODET_PRONTO_HEX)) {
|
||||
url = url + "&" + SHELLY_IR_CODET_PRONTO_HEX + "=" + keyCode;
|
||||
}
|
||||
request(url);
|
||||
}
|
||||
|
||||
public void setSenseSetting(String setting, String value) throws ShellyApiException {
|
||||
request(SHELLY_URL_SETTINGS + "?" + setting + "=" + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set event callback URLs. Depending on the device different event types are supported. In fact all of them will be
|
||||
* redirected to the binding's servlet and act as a trigger to schedule a status update
|
||||
*
|
||||
* @param ShellyApiException
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public void setActionURLs() throws ShellyApiException {
|
||||
setRelayEvents();
|
||||
setDimmerEvents();
|
||||
setSensorEventUrls();
|
||||
}
|
||||
|
||||
private void setRelayEvents() throws ShellyApiException {
|
||||
if (profile.settings.relays != null) {
|
||||
int num = profile.isRoller ? profile.numRollers : profile.numRelays;
|
||||
for (int i = 0; i < num; i++) {
|
||||
setEventUrls(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setDimmerEvents() throws ShellyApiException {
|
||||
if (profile.settings.dimmers != null) {
|
||||
for (int i = 0; i < profile.settings.dimmers.size(); i++) {
|
||||
setEventUrls(i);
|
||||
}
|
||||
} else if (profile.isLight) {
|
||||
setEventUrls(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sensor Action URLs
|
||||
*
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
private void setSensorEventUrls() throws ShellyApiException, ShellyApiException {
|
||||
if (profile.isSensor) {
|
||||
logger.debug("{}: Set Sensor Reporting URL", thingName);
|
||||
setEventUrl(config.eventsSensorReport, SHELLY_EVENT_SENSORREPORT, SHELLY_EVENT_DARK, SHELLY_EVENT_TWILIGHT,
|
||||
SHELLY_EVENT_FLOOD_DETECTED, SHELLY_EVENT_FLOOD_GONE, SHELLY_EVENT_OPEN, SHELLY_EVENT_CLOSE,
|
||||
SHELLY_EVENT_VIBRATION, SHELLY_EVENT_ALARM_MILD, SHELLY_EVENT_ALARM_HEAVY, SHELLY_EVENT_ALARM_OFF,
|
||||
SHELLY_EVENT_TEMP_OVER, SHELLY_EVENT_TEMP_UNDER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set/delete Relay/Roller/Dimmer Action URLs
|
||||
*
|
||||
* @param index Device Index (0-based)
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
private void setEventUrls(Integer index) throws ShellyApiException {
|
||||
if (profile.isRoller) {
|
||||
setEventUrl(EVENT_TYPE_ROLLER, 0, config.eventsRoller, SHELLY_EVENT_ROLLER_OPEN, SHELLY_EVENT_ROLLER_CLOSE,
|
||||
SHELLY_EVENT_ROLLER_STOP);
|
||||
} else if (profile.isDimmer) {
|
||||
// 2 set of URLs
|
||||
setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsButton, SHELLY_EVENT_BTN1_ON, SHELLY_EVENT_BTN1_OFF,
|
||||
SHELLY_EVENT_BTN2_ON, SHELLY_EVENT_BTN2_OFF);
|
||||
setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsPush, SHELLY_EVENT_SHORTPUSH1, SHELLY_EVENT_LONGPUSH1,
|
||||
SHELLY_EVENT_SHORTPUSH2, SHELLY_EVENT_LONGPUSH2);
|
||||
|
||||
// Relay output
|
||||
setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF);
|
||||
} else if (profile.hasRelays) {
|
||||
// Standard relays: btn_xxx, out_xxx, short/longpush URLs
|
||||
setEventUrl(EVENT_TYPE_RELAY, index, config.eventsButton, SHELLY_EVENT_BTN_ON, SHELLY_EVENT_BTN_OFF);
|
||||
setEventUrl(EVENT_TYPE_RELAY, index, config.eventsPush, SHELLY_EVENT_SHORTPUSH, SHELLY_EVENT_LONGPUSH);
|
||||
setEventUrl(EVENT_TYPE_RELAY, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF);
|
||||
} else if (profile.isLight) {
|
||||
// Duo, Bulb
|
||||
setEventUrl(EVENT_TYPE_LIGHT, index, config.eventsSwitch, SHELLY_EVENT_OUT_ON, SHELLY_EVENT_OUT_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
private void setEventUrl(boolean enabled, String... eventTypes) throws ShellyApiException {
|
||||
if (config.localIp.isEmpty()) {
|
||||
throw new ShellyApiException(thingName + ": Local IP address was not detected, can't build Callback URL");
|
||||
}
|
||||
for (String eventType : eventTypes) {
|
||||
if (profile.containsEventUrl(eventType)) {
|
||||
// H&T adds the type=xx to report_url itself, so we need to ommit here
|
||||
String eclass = profile.isSensor ? EVENT_TYPE_SENSORDATA : eventType;
|
||||
String urlParm = eventType.contains("temp") || profile.isHT ? "" : "?type=" + eventType;
|
||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/"
|
||||
+ profile.thingName + "/" + eclass + urlParm;
|
||||
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
||||
String testUrl = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\"";
|
||||
if (!enabled && !profile.settingsJson.contains(testUrl)) {
|
||||
// Don't set URL to null when the current one doesn't point to this OH
|
||||
// Don't interfere with a 3rd party App
|
||||
continue;
|
||||
}
|
||||
if (!profile.settingsJson.contains(testUrl)) {
|
||||
// Current Action URL is != new URL
|
||||
logger.debug("{}: Set new url for event type {}: {}", thingName, eventType, newUrl);
|
||||
request(SHELLY_URL_SETTINGS + "?" + mkEventUrl(eventType) + "=" + urlEncode(newUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setEventUrl(String deviceClass, Integer index, boolean enabled, String... eventTypes)
|
||||
throws ShellyApiException {
|
||||
for (String eventType : eventTypes) {
|
||||
if (profile.containsEventUrl(eventType)) {
|
||||
String callBackUrl = "http://" + config.localIp + ":" + config.localPort + SHELLY_CALLBACK_URI + "/"
|
||||
+ profile.thingName + "/" + deviceClass + "/" + index + "?type=" + eventType;
|
||||
String newUrl = enabled ? callBackUrl : SHELLY_NULL_URL;
|
||||
String test = "\"" + mkEventUrl(eventType) + "\":\"" + callBackUrl + "\"";
|
||||
if (!enabled && !profile.settingsJson.contains(test)) {
|
||||
// Don't set URL to null when the current one doesn't point to this OH
|
||||
// Don't interfere with a 3rd party App
|
||||
continue;
|
||||
}
|
||||
test = "\"" + mkEventUrl(eventType) + "\":\"" + newUrl + "\"";
|
||||
if (!profile.settingsJson.contains(test)) {
|
||||
// Current Action URL is != new URL
|
||||
logger.debug("{}: Set URL for type {} to {}", thingName, eventType, newUrl);
|
||||
request(SHELLY_URL_SETTINGS + "/" + deviceClass + "/" + index + "?" + mkEventUrl(eventType) + "="
|
||||
+ urlEncode(newUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String mkEventUrl(String eventType) {
|
||||
return eventType + SHELLY_EVENTURL_SUFFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit GET request and return response, check for invalid responses
|
||||
*
|
||||
* @param uri: URI (e.g. "/settings")
|
||||
*/
|
||||
public <T> T callApi(String uri, Class<T> classOfT) throws ShellyApiException {
|
||||
try {
|
||||
String json = request(uri);
|
||||
return gson.fromJson(json, classOfT);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new ShellyApiException("Unable to convert JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String request(String uri) throws ShellyApiException {
|
||||
ShellyApiResult apiResult = new ShellyApiResult();
|
||||
int retries = 3;
|
||||
boolean timeout = false;
|
||||
while (retries > 0) {
|
||||
try {
|
||||
apiResult = innerRequest(HttpMethod.GET, uri);
|
||||
if (timeout) {
|
||||
logger.debug("{}: API timeout #{}/{} recovered ({})", thingName, timeoutErrors, timeoutsRecovered,
|
||||
apiResult.getUrl());
|
||||
timeoutsRecovered++;
|
||||
}
|
||||
return apiResult.response; // successful
|
||||
} catch (ShellyApiException e) {
|
||||
if ((!e.isTimeout() && !apiResult.isHttpServerError()) || profile.hasBattery || (retries == 0)) {
|
||||
// Sensor in sleep mode or API exception for non-battery device or retry counter expired
|
||||
throw e; // non-timeout exception
|
||||
}
|
||||
|
||||
timeout = true;
|
||||
retries--;
|
||||
timeoutErrors++; // count the retries
|
||||
logger.debug("{}: API Timeout, retry #{} ({})", thingName, timeoutErrors, e.toString());
|
||||
}
|
||||
}
|
||||
throw new ShellyApiException("Inconsistent API result or Timeout"); // successful
|
||||
}
|
||||
|
||||
private ShellyApiResult innerRequest(HttpMethod method, String uri) throws ShellyApiException {
|
||||
Request request = null;
|
||||
String url = "http://" + config.deviceIp + uri;
|
||||
ShellyApiResult apiResult = new ShellyApiResult(method.toString(), url);
|
||||
|
||||
try {
|
||||
request = httpClient.newRequest(url).method(method.toString()).timeout(SHELLY_API_TIMEOUT_MS,
|
||||
TimeUnit.MILLISECONDS);
|
||||
|
||||
if (!config.userId.isEmpty()) {
|
||||
String value = config.userId + ":" + config.password;
|
||||
request.header(HTTP_HEADER_AUTH,
|
||||
HTTP_AUTH_TYPE_BASIC + " " + Base64.getEncoder().encodeToString(value.getBytes()));
|
||||
}
|
||||
request.header(HttpHeader.ACCEPT, CONTENT_TYPE_JSON);
|
||||
logger.trace("{}: HTTP {} for {}", thingName, method, url);
|
||||
|
||||
// Do request and get response
|
||||
ContentResponse contentResponse = request.send();
|
||||
apiResult = new ShellyApiResult(contentResponse);
|
||||
String response = contentResponse.getContentAsString().replace("\t", "").replace("\r\n", "").trim();
|
||||
logger.trace("{}: HTTP Response {}: {}", thingName, contentResponse.getStatus(), response);
|
||||
|
||||
// validate response, API errors are reported as Json
|
||||
if (contentResponse.getStatus() != HttpStatus.OK_200) {
|
||||
throw new ShellyApiException(apiResult);
|
||||
}
|
||||
if (response == null || response.isEmpty() || !response.startsWith("{") && !response.startsWith("[")) {
|
||||
throw new ShellyApiException("Unexpected response: " + response);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException | TimeoutException | IllegalArgumentException e) {
|
||||
ShellyApiException ex = new ShellyApiException(apiResult, e);
|
||||
if (!ex.isTimeout()) { // will be handled by the caller
|
||||
logger.trace("{}: API call returned exception", thingName, ex);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
return apiResult;
|
||||
}
|
||||
|
||||
public String getControlUriPrefix(Integer id) {
|
||||
String uri = "";
|
||||
if (profile.isLight || profile.isDimmer) {
|
||||
if (profile.isDuo || profile.isDimmer) {
|
||||
// Duo + Dimmer
|
||||
uri = SHELLY_URL_CONTROL_LIGHT;
|
||||
} else {
|
||||
// Bulb + RGBW2
|
||||
uri = "/" + (profile.inColor ? SHELLY_MODE_COLOR : SHELLY_MODE_WHITE);
|
||||
}
|
||||
} else {
|
||||
// Roller, Relay
|
||||
uri = SHELLY_URL_CONTROL_RELEAY;
|
||||
}
|
||||
uri = uri + "/" + id;
|
||||
return uri;
|
||||
}
|
||||
|
||||
public int getTimeoutErrors() {
|
||||
return timeoutErrors;
|
||||
}
|
||||
|
||||
public int getTimeoutsRecovered() {
|
||||
return timeoutsRecovered;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapListener} describes the listening interface to process Coap responses
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ShellyCoIoTInterface {
|
||||
public int getVersion();
|
||||
|
||||
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap);
|
||||
|
||||
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
|
||||
Map<String, State> updates);
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoIoTProtocol} implements common functions for the CoIoT implementations
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoIoTProtocol {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTProtocol.class);
|
||||
protected final String thingName;
|
||||
protected final ShellyBaseHandler thingHandler;
|
||||
protected final ShellyDeviceProfile profile;
|
||||
protected final Map<String, CoIotDescrBlk> blkMap;
|
||||
protected final Map<String, CoIotDescrSen> sensorMap;
|
||||
|
||||
// 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
|
||||
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 String[] inputEvent = { "", "", "", "", "", "", "", "" };
|
||||
|
||||
public ShellyCoIoTProtocol(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap,
|
||||
Map<String, CoIotDescrSen> sensorMap) {
|
||||
this.thingName = thingName;
|
||||
this.thingHandler = thingHandler;
|
||||
this.blkMap = blkMap;
|
||||
this.sensorMap = sensorMap;
|
||||
this.profile = thingHandler.getProfile();
|
||||
}
|
||||
|
||||
protected boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
|
||||
Map<String, State> updates) {
|
||||
// Process status information and convert into channel updates
|
||||
// Integer rIndex = Integer.parseInt(sen.links) + 1;
|
||||
// String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
|
||||
// : CHANNEL_GROUP_RELAY_CONTROL + rIndex;
|
||||
int rIndex = getIdFromBlk(sen);
|
||||
String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
|
||||
: CHANNEL_GROUP_RELAY_CONTROL + rIndex;
|
||||
switch (sen.type.toLowerCase()) {
|
||||
case "b": // BatteryLevel +
|
||||
updateChannel(updates, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL,
|
||||
toQuantityType(s.value, DIGITS_PERCENT, SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "h" /* Humidity */:
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM,
|
||||
toQuantityType(s.value, DIGITS_PERCENT, SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "m" /* Motion */:
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "l": // Luminosity +
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX,
|
||||
toQuantityType(s.value, DIGITS_LUX, SmartHomeUnits.LUX));
|
||||
break;
|
||||
case "s": // CatchAll
|
||||
switch (sen.desc.toLowerCase()) {
|
||||
case "state": // Relay status +
|
||||
case "output":
|
||||
updatePower(profile, updates, rIndex, sen, s, sensorUpdates);
|
||||
break;
|
||||
case "input":
|
||||
handleInput(sen, s, rGroup, updates);
|
||||
break;
|
||||
case "brightness":
|
||||
// already handled by state/output
|
||||
break;
|
||||
case "overtemp": // ++
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true);
|
||||
}
|
||||
break;
|
||||
case "position":
|
||||
// work around: Roller reports 101% instead max 100
|
||||
double pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(s.value, SHELLY_MAX_ROLLER_POS));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType(SHELLY_MAX_ROLLER_POS - pos, SmartHomeUnits.PERCENT));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_POS,
|
||||
toQuantityType(pos, SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "flood":
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "vibration": // DW with FW1.6.5+
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "luminositylevel": // +
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ILLUM, getStringType(s.valueStr));
|
||||
break;
|
||||
case "charger": // Sense
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
// RGBW2/Bulb
|
||||
case "red":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_RED,
|
||||
ShellyColorUtils.toPercent((int) s.value));
|
||||
break;
|
||||
case "green":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GREEN,
|
||||
ShellyColorUtils.toPercent((int) s.value));
|
||||
break;
|
||||
case "blue":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_BLUE,
|
||||
ShellyColorUtils.toPercent((int) s.value));
|
||||
break;
|
||||
case "white":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_WHITE,
|
||||
ShellyColorUtils.toPercent((int) s.value));
|
||||
break;
|
||||
case "gain":
|
||||
updateChannel(updates, CHANNEL_GROUP_COLOR_CONTROL, CHANNEL_COLOR_GAIN,
|
||||
ShellyColorUtils.toPercent((int) s.value, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN));
|
||||
break;
|
||||
case "sensorerror": // +
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, getStringType(s.valueStr));
|
||||
break;
|
||||
default:
|
||||
// Unknown
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown type
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean updateChannel(Map<String, State> updates, String group, String channel, State value) {
|
||||
updates.put(mkChannelId(group, channel), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleInput(CoIotDescrSen sen, CoIotSensor s, String rGroup, Map<String, State> updates) {
|
||||
int idx = getSensorNumber(sen.desc, sen.id) - 1;
|
||||
String iGroup = profile.getInputGroup(idx);
|
||||
String iChannel = profile.getInputChannel(idx);
|
||||
updateChannel(updates, iGroup, iChannel, s.value == 0 ? OnOffType.OFF : OnOffType.ON);
|
||||
}
|
||||
|
||||
protected void handleInputEvent(CoIotDescrSen sen, String type, Integer count, Map<String, State> updates) {
|
||||
int idx = getSensorNumber(sen.desc, sen.id) - 1;
|
||||
String group = profile.getInputGroup(idx);
|
||||
if (count == -1) {
|
||||
// event type
|
||||
updateChannel(updates, group, CHANNEL_STATUS_EVENTTYPE, new StringType(type));
|
||||
inputEvent[idx] = type;
|
||||
} else {
|
||||
// event count
|
||||
updateChannel(updates, group, CHANNEL_STATUS_EVENTCOUNT, getDecimal(count));
|
||||
if (profile.inButtonMode(idx) && ((profile.hasBattery && (count == 1)) || (count != lastEventCount[idx]))) {
|
||||
if (profile.isButton || (lastEventCount[idx] != -1)) { // skip the first one if binding was restarted
|
||||
thingHandler.triggerButton(group, inputEvent[idx]);
|
||||
}
|
||||
lastEventCount[idx] = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Handles the combined updated of the brightness channel:
|
||||
* brightness$Switch is the OnOffType (power state)
|
||||
* brightness&Value is the brightness value
|
||||
*
|
||||
* @param profile Device profile, required to select the channel group and name
|
||||
* @param updates List of updates. updatePower will add brightness$Switch and brightness&Value if changed
|
||||
* @param id Sensor id from the update
|
||||
* @param sen Sensor description from the update
|
||||
* @param s New sensor value
|
||||
* @param allUpdatesList of updates. This is required, because we need to update both values at the same time
|
||||
*/
|
||||
protected void updatePower(ShellyDeviceProfile profile, Map<String, State> updates, int id, CoIotDescrSen sen,
|
||||
CoIotSensor s, List<CoIotSensor> allUpdates) {
|
||||
String group = "";
|
||||
String channel = CHANNEL_BRIGHTNESS;
|
||||
String checkL = ""; // RGBW-white uses 4 different Power, Brightness, VSwitch values
|
||||
if (profile.isLight || profile.isDimmer) {
|
||||
if (profile.isBulb || profile.inColor) {
|
||||
group = CHANNEL_GROUP_LIGHT_CONTROL;
|
||||
channel = CHANNEL_LIGHT_POWER;
|
||||
} else if (profile.isDuo) {
|
||||
group = CHANNEL_GROUP_WHITE_CONTROL;
|
||||
} else if (profile.isDimmer) {
|
||||
group = CHANNEL_GROUP_RELAY_CONTROL;
|
||||
} else if (profile.isRGBW2) {
|
||||
group = CHANNEL_GROUP_LIGHT_CHANNEL + id;
|
||||
checkL = String.valueOf(id - 1); // id is 1-based, L is 0-based
|
||||
logger.trace("{}: updatePower() for L={}", thingName, checkL);
|
||||
}
|
||||
|
||||
// We need to update brigthtess and on/off state at the same time to avoid "flipping brightness slider" in
|
||||
// the UI
|
||||
Double brightness = -1.0;
|
||||
Double power = -1.0;
|
||||
for (CoIotSensor update : allUpdates) {
|
||||
CoIotDescrSen d = fixDescription(sensorMap.get(update.id), blkMap);
|
||||
if (!checkL.isEmpty() && !d.links.equals(checkL)) {
|
||||
// continue until we find the correct one
|
||||
continue;
|
||||
}
|
||||
if (d.desc.equalsIgnoreCase("brightness")) {
|
||||
brightness = new Double(update.value);
|
||||
} else if (d.desc.equalsIgnoreCase("output") || d.desc.equalsIgnoreCase("state")) {
|
||||
power = new Double(update.value);
|
||||
}
|
||||
}
|
||||
if (power != -1) {
|
||||
updateChannel(updates, group, channel + "$Switch", power == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
if (brightness != -1) {
|
||||
updateChannel(updates, group, channel + "$Value",
|
||||
toQuantityType(power == 1 ? brightness : 0, DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
} else if (profile.hasRelays) {
|
||||
group = profile.numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL : CHANNEL_GROUP_RELAY_CONTROL + id;
|
||||
updateChannel(updates, group, CHANNEL_OUTPUT, s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
} else if (profile.isSensor) {
|
||||
// Sensor state
|
||||
if (profile.isDW) { // Door Window has item type Contact
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
|
||||
s.value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
|
||||
} else {
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find index of Input id, which is required to map to channel name
|
||||
*
|
||||
* @parm sensorDesc D field from sensor update
|
||||
* @param sensorId The id from the sensor update
|
||||
* @return Index of found entry (+1 will be the suffix for the channel name) or null if sensorId is not found
|
||||
*/
|
||||
protected int getSensorNumber(String sensorDesc, String sensorId) {
|
||||
int idx = 0;
|
||||
for (Map.Entry<String, CoIotDescrSen> se : sensorMap.entrySet()) {
|
||||
CoIotDescrSen sen = se.getValue();
|
||||
if (sen.desc.equalsIgnoreCase(sensorDesc)) {
|
||||
idx++; // iterate from input1..2..n
|
||||
}
|
||||
if (sen.id.equalsIgnoreCase(sensorId) && blkMap.containsKey(sen.links)) {
|
||||
int id = getIdFromBlk(sen);
|
||||
if (id != -1) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
if (sen.id.equalsIgnoreCase(sensorId)) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
logger.debug("{}: sensorId {} not found in sensorMap!", thingName, sensorId);
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected int getIdFromBlk(CoIotDescrSen sen) {
|
||||
int idx = -1;
|
||||
if (blkMap.containsKey(sen.links)) {
|
||||
CoIotDescrBlk blk = blkMap.get(sen.links);
|
||||
String desc = blk.desc.toLowerCase();
|
||||
if (desc.startsWith(SHELLY_CLASS_RELAY) || desc.startsWith(SHELLY_CLASS_ROLLER)
|
||||
|| desc.startsWith(SHELLY_CLASS_EMETER)) {
|
||||
if (desc.contains("_")) { // CoAP v2
|
||||
idx = Integer.parseInt(substringAfter(desc, "_"));
|
||||
} else { // CoAP v1
|
||||
if (desc.substring(0, 5).equalsIgnoreCase(SHELLY_CLASS_RELAY)) {
|
||||
idx = Integer.parseInt(substringAfter(desc, SHELLY_CLASS_RELAY));
|
||||
}
|
||||
if (desc.substring(0, 6).equalsIgnoreCase(SHELLY_CLASS_ROLLER)) {
|
||||
idx = Integer.parseInt(substringAfter(desc, SHELLY_CLASS_ROLLER));
|
||||
}
|
||||
if (desc.substring(0, SHELLY_CLASS_EMETER.length()).equalsIgnoreCase(SHELLY_CLASS_EMETER)) {
|
||||
idx = Integer.parseInt(substringAfter(desc, SHELLY_CLASS_EMETER));
|
||||
}
|
||||
}
|
||||
idx = idx + 1; // make it 1-based (sen.L is 0-based)
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get matching sensorId for updates on "External Temperature" - there might be more than 1 sensor.
|
||||
*
|
||||
* @param sensorId sensorId to map into a channel index
|
||||
* @return Index of the corresponding channel (e.g. 0 build temperature1, 1->temperagture2...)
|
||||
*/
|
||||
protected int getExtTempId(String sensorId) {
|
||||
int idx = 0;
|
||||
for (Map.Entry<String, CoIotDescrSen> se : sensorMap.entrySet()) {
|
||||
CoIotDescrSen sen = se.getValue();
|
||||
if (sen.desc.equalsIgnoreCase("external_temperature") || sen.desc.equalsIgnoreCase("external temperature c")
|
||||
|| (sen.desc.equalsIgnoreCase("extTemp") && !sen.unit.equalsIgnoreCase(SHELLY_TEMP_FAHRENHEIT))) {
|
||||
idx++; // iterate from temperature1..2..n
|
||||
}
|
||||
if (sen.id.equalsIgnoreCase(sensorId)) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
logger.debug("{}: sensorId {} not found in sensorMap!", thingName, sensorId);
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected ShellyDeviceProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
|
||||
return sen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyColorUtils;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import tec.uom.se.unit.Units;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoIoTVersion1} implements the parsing for CoIoT version 1
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoIoTVersion1 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion1.class);
|
||||
|
||||
public ShellyCoIoTVersion1(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap,
|
||||
Map<String, CoIotDescrSen> sensorMap) {
|
||||
super(thingName, thingHandler, blkMap, sensorMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return ShellyCoapJSonDTO.COIOT_VERSION_1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process CoIoT status update message. If a status update is received, but the device description has not been
|
||||
* received yet a GET is send to query device description.
|
||||
*
|
||||
* @param devId device id included in the status packet
|
||||
* @param payload CoAP payload (Json format), example: {"G":[[0,112,0]]}
|
||||
* @param serial Serial for this request. If this the the same as last serial
|
||||
* the update was already sent and processed so this one gets
|
||||
* ignored.
|
||||
*/
|
||||
@Override
|
||||
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
|
||||
Map<String, State> updates) {
|
||||
// first check the base implementation
|
||||
if (super.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
|
||||
// process by the base class
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process status information and convert into channel updates
|
||||
Integer rIndex = Integer.parseInt(sen.links) + 1;
|
||||
String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
|
||||
: CHANNEL_GROUP_RELAY_CONTROL + rIndex;
|
||||
switch (sen.type.toLowerCase()) {
|
||||
case "t": // Temperature +
|
||||
Double value = getDouble(s.value);
|
||||
switch (sen.desc.toLowerCase()) {
|
||||
case "temperature": // Sensor Temp
|
||||
if (getString(getProfile().settings.temperatureUnits)
|
||||
.equalsIgnoreCase(SHELLY_TEMP_FAHRENHEIT)) {
|
||||
value = ImperialUnits.FAHRENHEIT.getConverterTo(Units.CELSIUS).convert(getDouble(s.value))
|
||||
.doubleValue();
|
||||
}
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
|
||||
toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
break;
|
||||
case "temperature f": // Device Temp -> ignore (we use C only)
|
||||
break;
|
||||
case "temperature c": // Device Temp in C
|
||||
// Device temperature
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(value, DIGITS_NONE, SIUnits.CELSIUS));
|
||||
break;
|
||||
case "external temperature f": // Shelly 1/1PM external temp sensors
|
||||
// ignore F, we use C only
|
||||
break;
|
||||
case "external temperature c": // Shelly 1/1PM external temp sensors
|
||||
case "external_temperature":
|
||||
int idx = getExtTempId(sen.id);
|
||||
if (idx > 0) {
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP + idx,
|
||||
toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
} else {
|
||||
logger.debug("{}: Unable to get extSensorId {} from {}/{}", thingName, sen.id, sen.type,
|
||||
sen.desc);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("{}: Unknown temperatur type: {}", thingName, sen.desc);
|
||||
}
|
||||
break;
|
||||
case "p": // Power/Watt
|
||||
// 3EM uses 1-based meter IDs, other 0-based
|
||||
String mGroup = profile.numMeters == 1 ? CHANNEL_GROUP_METER
|
||||
: CHANNEL_GROUP_METER + (profile.isEMeter ? sen.links : rIndex);
|
||||
updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(s.value, DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
break;
|
||||
case "s" /* CatchAll */:
|
||||
switch (sen.desc.toLowerCase()) {
|
||||
case "overtemp":
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true);
|
||||
}
|
||||
break;
|
||||
case "energy counter 0 [w-min]":
|
||||
updateChannel(updates, rGroup, CHANNEL_METER_LASTMIN1,
|
||||
toQuantityType(s.value, DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
break;
|
||||
case "energy counter 1 [w-min]":
|
||||
case "energy counter 2 [w-min]":
|
||||
// we don't use them
|
||||
break;
|
||||
case "energy counter total [w-h]": // 3EM reports W/h
|
||||
case "energy counter total [w-min]":
|
||||
Double total = profile.isEMeter ? s.value / 1000 : s.value / 60 / 1000;
|
||||
updateChannel(updates, rGroup, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(total, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
break;
|
||||
case "voltage":
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_VOLTAGE,
|
||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, SmartHomeUnits.VOLT));
|
||||
break;
|
||||
case "current":
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_CURRENT,
|
||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, SmartHomeUnits.AMPERE));
|
||||
break;
|
||||
case "pf":
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value));
|
||||
break;
|
||||
case "position":
|
||||
// work around: Roller reports 101% instead max 100
|
||||
double pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(s.value, SHELLY_MAX_ROLLER_POS));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType(SHELLY_MAX_ROLLER_POS - pos, SmartHomeUnits.PERCENT));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_POS,
|
||||
toQuantityType(pos, SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "input event": // Shelly Button 1
|
||||
handleInputEvent(sen, getString(s.valueStr), -1, updates);
|
||||
break;
|
||||
case "input event counter": // Shelly Button 1/ix3
|
||||
handleInputEvent(sen, "", getInteger((int) s.value), updates);
|
||||
break;
|
||||
case "flood":
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "tilt": // DW with FW1.6.5+ //+
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT,
|
||||
toQuantityType(s.value, DIGITS_NONE, SmartHomeUnits.DEGREE_ANGLE));
|
||||
break;
|
||||
case "vibration": // DW with FW1.6.5+
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
|
||||
s.value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "temp": // Shelly Bulb
|
||||
case "colortemperature": // Shelly Duo
|
||||
updateChannel(updates,
|
||||
profile.inColor ? CHANNEL_GROUP_COLOR_CONTROL : CHANNEL_GROUP_WHITE_CONTROL,
|
||||
CHANNEL_COLOR_TEMP,
|
||||
ShellyColorUtils.toPercent((int) s.value, profile.minTemp, profile.maxTemp));
|
||||
break;
|
||||
case "sensor state": // Shelly Gas
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE, getStringType(s.valueStr));
|
||||
break;
|
||||
case "alarm state": // Shelly Gas
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE,
|
||||
getStringType(s.valueStr));
|
||||
break;
|
||||
case "self-test state":// Shelly Gas
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST,
|
||||
getStringType(s.valueStr));
|
||||
break;
|
||||
case "concentration":// Shelly Gas
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, getDecimal(s.value));
|
||||
break;
|
||||
case "sensorerror":
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR, getStringType(s.valueStr));
|
||||
break;
|
||||
default:
|
||||
// Unknown
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown type
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Depending on the device type and firmware release there are significant bugs or incosistencies in the CoIoT
|
||||
* Device Description returned by the discovery request. Shelly is even not following it's own speicifcation. All of
|
||||
* that has been reported to Shelly and acknowledged. Firmware 1.6 brought significant improvements. However, the
|
||||
* old mapping stays in to support older firmware releases.
|
||||
*
|
||||
* @param sen Sensor description received from device
|
||||
* @return fixed Sensor description (sen)
|
||||
*/
|
||||
@Override
|
||||
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
|
||||
// Shelly1: reports null descr+type "Switch" -> map to S
|
||||
// Shelly1PM: reports null descr+type "Overtemp" -> map to O
|
||||
// Shelly1PM: reports null descr+type "W" -> add description
|
||||
// Shelly1PM: reports temp senmsors without desc -> add description
|
||||
// Shelly Dimmer: sensors are reported without descriptions -> map to S
|
||||
// SHelly Sense: multiple issues: Description should not be lower case, invalid type for Motion and Battery
|
||||
// Shelly Sense: Battery is reported with Desc "battery", but type "H" instead of "B"
|
||||
// Shelly Sense: Motion is reported with Desc "battery", but type "H" instead of "B"
|
||||
// Shelly Bulb: Colors are coded with Type="Red" etc. rather than Type="S" and color as Descr
|
||||
// Shelly RGBW2 is reporting Brightness, Power, VSwitch for each channel, but all with L=0
|
||||
if (sen.desc == null) {
|
||||
sen.desc = "";
|
||||
}
|
||||
String desc = sen.desc.toLowerCase();
|
||||
|
||||
// RGBW2 reports Power_0, Power_1, Power_2, Power_3; same for VSwitch and Brightness, all of them linkted to L:0
|
||||
// we break it up to Power with L:0, Power with L:1...
|
||||
if (desc.contains("_") && (desc.contains("power") || desc.contains("vswitch") || desc.contains("brightness"))) {
|
||||
String newDesc = substringBefore(sen.desc, "_");
|
||||
String newLink = substringAfter(sen.desc, "_");
|
||||
sen.desc = newDesc;
|
||||
sen.links = newLink;
|
||||
if (!blkMap.containsKey(sen.links)) {
|
||||
// auto-insert a matching blk entry
|
||||
CoIotDescrBlk blk = new CoIotDescrBlk();
|
||||
CoIotDescrBlk blk0 = blkMap.get("0"); // blk 0 is always there
|
||||
blk.id = sen.links;
|
||||
blk.desc = blk0.desc + "_" + blk.id;
|
||||
blkMap.put(blk.id, blk);
|
||||
}
|
||||
}
|
||||
|
||||
switch (sen.type.toLowerCase()) {
|
||||
case "w": // old devices/firmware releases use "W", new ones "P"
|
||||
sen.type = "P";
|
||||
sen.desc = "Power";
|
||||
break;
|
||||
case "tc":
|
||||
sen.type = "T";
|
||||
sen.desc = "Temperature C";
|
||||
break;
|
||||
case "tf":
|
||||
sen.type = "T";
|
||||
sen.desc = "Temperature F";
|
||||
break;
|
||||
case "overtemp":
|
||||
sen.type = "S";
|
||||
sen.desc = "Overtemp";
|
||||
break;
|
||||
case "relay0":
|
||||
case "switch":
|
||||
case "vswitch":
|
||||
sen.type = "S";
|
||||
sen.desc = "State";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sen.desc.toLowerCase()) {
|
||||
case "motion": // fix acc to spec it's T=M
|
||||
sen.type = "M";
|
||||
sen.desc = "Motion";
|
||||
break;
|
||||
case "battery": // fix: type is B not H
|
||||
sen.type = "B";
|
||||
sen.desc = "Battery";
|
||||
break;
|
||||
case "overtemp":
|
||||
sen.type = "S";
|
||||
sen.desc = "Overtemp";
|
||||
break;
|
||||
case "relay0":
|
||||
case "switch":
|
||||
case "vswitch":
|
||||
sen.type = "S";
|
||||
sen.desc = "State";
|
||||
break;
|
||||
case "e cnt 0 [w-min]": // 4 Pro
|
||||
case "e cnt 1 [w-min]":
|
||||
case "e cnt 2 [w-min]":
|
||||
case "e cnt total [w-min]": // 4 Pro
|
||||
sen.desc = sen.desc.toLowerCase().replace("e cnt", "energy counter");
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (sen.desc.isEmpty()) {
|
||||
switch (sen.type.toLowerCase()) {
|
||||
case "p":
|
||||
sen.desc = "Power";
|
||||
break;
|
||||
case "T":
|
||||
sen.desc = "Temperature";
|
||||
break;
|
||||
case "input":
|
||||
sen.type = "S";
|
||||
sen.desc = "Input";
|
||||
break;
|
||||
case "output":
|
||||
sen.type = "S";
|
||||
sen.desc = "Output";
|
||||
break;
|
||||
case "brightness":
|
||||
sen.type = "S";
|
||||
sen.desc = "Brightness";
|
||||
break;
|
||||
case "red":
|
||||
case "green":
|
||||
case "blue":
|
||||
case "white":
|
||||
case "gain":
|
||||
case "temp": // Bulb: Color temperature
|
||||
sen.desc = sen.type;
|
||||
sen.type = "S";
|
||||
break;
|
||||
case "vswitch":
|
||||
// it seems that Shelly tends to break their own spec: T is the description and D is no longer
|
||||
// included -> map D to sen.T and set CatchAll for T
|
||||
sen.desc = sen.type;
|
||||
sen.type = "S";
|
||||
break;
|
||||
// Default: set no description
|
||||
// (there are no T values defined in the CoIoT spec)
|
||||
case "tostate":
|
||||
default:
|
||||
sen.desc = "";
|
||||
}
|
||||
}
|
||||
return sen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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
|
||||
*/
|
||||
/**
|
||||
* The {@link ShellyCoIoTVersion1} implements the parsing for CoIoT version 1
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoIoTVersion1} implements the parsing for CoIoT version 2
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoIoTVersion2 extends ShellyCoIoTProtocol implements ShellyCoIoTInterface {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoIoTVersion2.class);
|
||||
|
||||
public ShellyCoIoTVersion2(String thingName, ShellyBaseHandler thingHandler, Map<String, CoIotDescrBlk> blkMap,
|
||||
Map<String, CoIotDescrSen> sensorMap) {
|
||||
super(thingName, thingHandler, blkMap, sensorMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return ShellyCoapJSonDTO.COIOT_VERSION_2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process CoIoT status update message. If a status update is received, but the device description has not been
|
||||
* received yet a GET is send to query device description.
|
||||
*
|
||||
* @param devId device id included in the status packet
|
||||
* @param payload CoAP payload (Json format), example: {"G":[[0,112,0]]}
|
||||
* @param serial Serial for this request. If this the the same as last serial
|
||||
* the update was already sent and processed so this one gets
|
||||
* ignored.
|
||||
*/
|
||||
@Override
|
||||
public boolean handleStatusUpdate(List<CoIotSensor> sensorUpdates, CoIotDescrSen sen, CoIotSensor s,
|
||||
Map<String, State> updates) {
|
||||
// first check the base implementation
|
||||
if (super.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
|
||||
// process by the base class
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process status information and convert into channel updates
|
||||
// Integer rIndex = Integer.parseInt(sen.links) + 1;
|
||||
int rIndex = getIdFromBlk(sen);
|
||||
String rGroup = getProfile().numRelays <= 1 ? CHANNEL_GROUP_RELAY_CONTROL
|
||||
: CHANNEL_GROUP_RELAY_CONTROL + rIndex;
|
||||
String mGroup = profile.numMeters == 1 ? CHANNEL_GROUP_METER
|
||||
: CHANNEL_GROUP_METER + (profile.isEMeter ? getIdFromBlk(sen) : rIndex);
|
||||
|
||||
boolean processed = true;
|
||||
double value = getDouble(s.value);
|
||||
String reason = "";
|
||||
switch (sen.id) {
|
||||
case "3103": // H, humidity, 0-100 percent, unknown 999
|
||||
case "3106": // L, luminosity, lux, U32, -1
|
||||
case "3109": // S, tilt, 0-180deg, -1
|
||||
case "3110": // S, luminosityLevel, dark/twilight/bright, "unknown"=unknown
|
||||
case "3111": // B, battery, 0-100%, unknown -1
|
||||
case "3112": // S, charger, 0/1
|
||||
case "3115": // S, sensorError, 0/1
|
||||
case "5101": // S, brightness, 1-100%
|
||||
// processed by base handler
|
||||
break;
|
||||
case "6109": // P, overpowerValue, W, U32
|
||||
case "9101":
|
||||
// Relay: S, mode, relay/roller or
|
||||
// Dimmer: S, mode, color/white
|
||||
// skip, could check against thing mode...
|
||||
break;
|
||||
|
||||
case "1101": // S, output, 0/1
|
||||
updatePower(profile, updates, rIndex, sen, s, sensorUpdates);
|
||||
break;
|
||||
case "1102": // roler_0: S, roller, open/close/stop -> roller state
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_STATE, getStringType(s.valueStr));
|
||||
break;
|
||||
case "1103": // roller_0: S, rollerPos, 0-100, unknown -1
|
||||
int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min((int) value, SHELLY_MAX_ROLLER_POS));
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType(new Double(SHELLY_MAX_ROLLER_POS - pos), SmartHomeUnits.PERCENT));
|
||||
break;
|
||||
case "1105": // S, valvle, closed/opened/not_connected/failure/closing/opening/checking or unbknown
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE, getStringType(s.valueStr));
|
||||
break;
|
||||
|
||||
case "2101": // Input_0: S, input, 0/1
|
||||
case "2201": // Input_1: S, input, 0/1
|
||||
case "2301": // Input_2: S, input, 0/1
|
||||
case "2401": // Input_3: S, input, 0/1
|
||||
handleInput(sen, s, rGroup, updates);
|
||||
break;
|
||||
case "2102": // Input_0: EV, inputEvent, S/SS/SSS/L
|
||||
case "2202": // Input_1: EV, inputEvent
|
||||
case "2302": // Input_2: EV, inputEvent
|
||||
case "2402": // Input_3: EV, inputEvent
|
||||
handleInputEvent(sen, getString(s.valueStr), -1, updates);
|
||||
break;
|
||||
case "2103": // EVC, inputEventCnt, U16
|
||||
case "2203": // EVC, inputEventCnt, U16
|
||||
case "2303": // EVC, inputEventCnt, U16
|
||||
case "2403": // EVC, inputEventCnt, U16
|
||||
handleInputEvent(sen, "", getInteger((int) s.value), updates);
|
||||
break;
|
||||
case "3101": // sensor_0: T, extTemp, C, -55/125; unknown 999
|
||||
case "3201": // sensor_1: T, extTemp, C, -55/125; unknown 999
|
||||
case "3301": // sensor_2: T, extTemp, C, -55/125; unknown 999
|
||||
int idx = getExtTempId(sen.id);
|
||||
if (idx >= 0) {
|
||||
// H&T, Fllod, DW only have 1 channel, 1/1PM with Addon have up to to 3 sensors
|
||||
String channel = profile.isSensor ? CHANNEL_SENSOR_TEMP : CHANNEL_SENSOR_TEMP + idx;
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, channel,
|
||||
toQuantityType(value, DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
} else {
|
||||
logger.debug("{}: Unable to get extSensorId {} from {}/{}", thingName, sen.id, sen.type, sen.desc);
|
||||
}
|
||||
break;
|
||||
case "3104": // T, deviceTemp, Celsius -40/300; 999=unknown
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(value, DIGITS_NONE, SIUnits.CELSIUS));
|
||||
break;
|
||||
case "3102": // sensor_0: T, extTemp, F, -67/257, unknown 999
|
||||
case "3202": // sensor_1: T, extTemp, F, -67/257, unknown 999
|
||||
case "3302": // sensor_2: T, extTemp, F, -67/257, unknown 999
|
||||
case "3105": // T, deviceTemp, Fahrenheit -40/572
|
||||
// skip, we use only C
|
||||
break;
|
||||
|
||||
case "3107": // C, Gas concentration, U16
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM, getDecimal(s.value));
|
||||
break;
|
||||
case "3108": // DW: S, dwIsOpened, 0/1, -1=unknown
|
||||
if (value != -1) {
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
|
||||
value != 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
|
||||
} else {
|
||||
logger.debug("{}: Sensor error reported, check device, battery and installation", thingName);
|
||||
}
|
||||
break;
|
||||
case "3113": // S, sensorOp, warmup/normal/fault
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE, getStringType(s.valueStr));
|
||||
break;
|
||||
case "3114": // S, selfTest, not_completed/completed/running/pending
|
||||
updateChannel(updates, CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST, getStringType(s.valueStr));
|
||||
break;
|
||||
case "3117": // S, extInput, 0/1
|
||||
handleInput(sen, s, rGroup, updates);
|
||||
break;
|
||||
|
||||
case "4101": // relay_0: P, power, W
|
||||
case "4201": // relay_1: P, power, W
|
||||
case "4301": // relay_2: P, power, W
|
||||
case "4401": // relay_3: P, power, W
|
||||
case "4105": // emeter_0: P, power, W
|
||||
case "4205": // emeter_1: P, power, W
|
||||
case "4305": // emeter_2: P, power, W
|
||||
case "4102": // roller_0: P, rollerPower, W, 0-2300, unknown -1
|
||||
case "4202": // roller_1: P, rollerPower, W, 0-2300, unknown -1
|
||||
updateChannel(updates, mGroup, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(s.value, DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updateChannel(updates, mGroup, CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
break;
|
||||
|
||||
case "4103": // relay_0: E, energy, Wmin, U32
|
||||
case "4203": // relay_1: E, energy, Wmin, U32
|
||||
case "4303": // relay_2: E, energy, Wmin, U32
|
||||
case "4403": // relay_3: E, energy, Wmin, U32
|
||||
case "4104": // roller_0: E, rollerEnergy, Wmin, U32, -1
|
||||
case "4204": // roller_0: E, rollerEnergy, Wmin, U32, -1
|
||||
case "4106": // emeter_0: E, energy, Wh, U32
|
||||
case "4206": // emeter_1: E, energy, Wh, U32
|
||||
case "4306": // emeter_2: E, energy, Wh, U32
|
||||
double total = profile.isEMeter ? s.value / 1000 : s.value / 60 / 1000;
|
||||
updateChannel(updates, mGroup, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(total, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
break;
|
||||
|
||||
case "4107": // emeter_0: E, energyReturned, Wh, U32, -1
|
||||
case "4207": // emeter_1: E, energyReturned, Wh, U32, -1
|
||||
case "4307": // emeter_2: E, energyReturned, Wh, U32, -1
|
||||
updateChannel(updates, mGroup, CHANNEL_EMETER_TOTALRET,
|
||||
toQuantityType(getDouble(s.value) / 1000, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
break;
|
||||
|
||||
case "4108": // emeter_0: V, voltage, 0-265V, U32, -1
|
||||
case "4208": // emeter_1: V, voltage, 0-265V, U32, -1
|
||||
case "4308": // emeter_2: V, voltage, 0-265V, U32, -1
|
||||
updateChannel(updates, mGroup, CHANNEL_EMETER_VOLTAGE,
|
||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, SmartHomeUnits.VOLT));
|
||||
break;
|
||||
|
||||
case "4109": // emeter_0: A, current, 0/120A, -1
|
||||
case "4209": // emeter_1: A, current, 0/120A, -1
|
||||
case "4309": // emeter_2: A, current, 0/120A, -1
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_CURRENT,
|
||||
toQuantityType(getDouble(s.value), DIGITS_VOLT, SmartHomeUnits.AMPERE));
|
||||
break;
|
||||
|
||||
case "4110": // emeter_0: S, powerFactor, 0/1, -1
|
||||
case "4210": // emeter_1: S, powerFactor, 0/1, -1
|
||||
case "4310": // emeter_2: S, powerFactor, 0/1, -1
|
||||
updateChannel(updates, rGroup, CHANNEL_EMETER_PFACTOR, getDecimal(s.value));
|
||||
break;
|
||||
|
||||
case "6101": // A, overtemp, 0/1
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_OVERTEMP, true);
|
||||
}
|
||||
break;
|
||||
case "6102": // relay_0: A, overpower, 0/1
|
||||
case "6202": // relay_1: A, overpower, 0/1
|
||||
case "6302": // relay_2: A, overpower, 0/1
|
||||
case "6402": // relay_3: A, overpower, 0/1
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_OVERPOWER, true);
|
||||
}
|
||||
break;
|
||||
case "6104": // relay_0: A, loadError, 0/1
|
||||
case "6204": // relay_1: A, loadError, 0/1
|
||||
case "6304": // relay_2: A, loadError, 0/1
|
||||
case "6404": // relay_3: A, loadError, 0/1
|
||||
if (s.value == 1) {
|
||||
thingHandler.postEvent(ALARM_TYPE_LOADERR, true);
|
||||
}
|
||||
break;
|
||||
case "6103": // roller_0: A, rollerStopReason, normal/safety_switch/obstacle/overpower
|
||||
reason = getString(s.valueStr);
|
||||
updateChannel(updates, CHANNEL_GROUP_ROL_CONTROL, CHANNEL_ROL_CONTROL_STOPR, getStringType(reason));
|
||||
if (!reason.isEmpty() && !reason.equalsIgnoreCase(SHELLY_API_STOPR_NORMAL)) {
|
||||
thingHandler.postEvent("ROLLER_" + reason.toUpperCase(), true);
|
||||
}
|
||||
case "6106": // A, flood, 0/1, -1
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
|
||||
value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "6108": // A, gas, none/mild/heavy/test or unknown
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE, getStringType(s.valueStr));
|
||||
break;
|
||||
case "6110": // A, vibration, 0/1, -1=unknown
|
||||
updateChannel(updates, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
|
||||
value == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case "9102": // EV, wakeupEvent, battery/button/periodic/poweron/sensor/ext_power, "unknown"=unknown
|
||||
thingHandler.updateWakeupReason(s.valueArray);
|
||||
break;
|
||||
case "9103": // EVC, cfgChanged, U16
|
||||
if ((lastCfgCount != -1) && (lastCfgCount != s.value)) {
|
||||
thingHandler.requestUpdates(1, true); // refresh config
|
||||
}
|
||||
lastCfgCount = (int) s.value;
|
||||
break;
|
||||
|
||||
default:
|
||||
processed = false;
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoIotDescrSen fixDescription(CoIotDescrSen sen, Map<String, CoIotDescrBlk> blkMap) {
|
||||
return sen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.californium.core.CoapClient;
|
||||
import org.eclipse.californium.core.coap.CoAP.Code;
|
||||
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
|
||||
import org.eclipse.californium.core.coap.CoAP.Type;
|
||||
import org.eclipse.californium.core.coap.MessageObserverAdapter;
|
||||
import org.eclipse.californium.core.coap.Option;
|
||||
import org.eclipse.californium.core.coap.OptionNumberRegistry;
|
||||
import org.eclipse.californium.core.coap.Request;
|
||||
import org.eclipse.californium.core.coap.Response;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrBlk;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDescrSen;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDevDescrTypeAdapter;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotDevDescription;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotGenericSensorList;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensor;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.CoIotSensorTypeAdapter;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyThingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapHandler} handles the CoIoT/CoAP registration and events.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoapHandler implements ShellyCoapListener {
|
||||
private static final byte[] EMPTY_BYTE = new byte[0];
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoapHandler.class);
|
||||
private final ShellyBaseHandler thingHandler;
|
||||
private ShellyThingConfiguration config = new ShellyThingConfiguration();
|
||||
private final GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
private final Gson gson;
|
||||
private String thingName;
|
||||
|
||||
private boolean coiotBound = false;
|
||||
private ShellyCoIoTInterface coiot;
|
||||
private int coiotVers = -1;
|
||||
|
||||
private final ShellyCoapServer coapServer;
|
||||
private @Nullable CoapClient statusClient;
|
||||
private Request reqDescription = new Request(Code.GET, Type.CON);
|
||||
private Request reqStatus = new Request(Code.GET, Type.CON);
|
||||
private boolean discovering = false;
|
||||
|
||||
private int lastSerial = -1;
|
||||
private String lastPayload = "";
|
||||
private Map<String, CoIotDescrBlk> blkMap = new LinkedHashMap<>();
|
||||
private Map<String, CoIotDescrSen> sensorMap = new LinkedHashMap<>();
|
||||
private final ShellyDeviceProfile profile;
|
||||
|
||||
public ShellyCoapHandler(ShellyBaseHandler thingHandler, ShellyCoapServer coapServer) {
|
||||
this.thingHandler = thingHandler;
|
||||
this.thingName = thingHandler.thingName;
|
||||
this.coapServer = coapServer;
|
||||
this.coiot = new ShellyCoIoTVersion1(thingName, thingHandler, blkMap, sensorMap); // Default
|
||||
|
||||
gsonBuilder.registerTypeAdapter(CoIotDevDescription.class, new CoIotDevDescrTypeAdapter());
|
||||
gsonBuilder.registerTypeAdapter(CoIotGenericSensorList.class, new CoIotSensorTypeAdapter());
|
||||
gson = gsonBuilder.create();
|
||||
profile = thingHandler.getProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize CoAP access, send discovery packet and start Status server
|
||||
*
|
||||
* @parm thingName Thing name derived from Thing Type/hostname
|
||||
* @parm config ShellyThingConfiguration
|
||||
* @thows ShellyApiException
|
||||
*/
|
||||
public synchronized void start(String thingName, ShellyThingConfiguration config) throws ShellyApiException {
|
||||
try {
|
||||
this.thingName = thingName;
|
||||
this.config = config;
|
||||
if (isStarted()) {
|
||||
logger.trace("{}: CoAP Listener was already started", thingName);
|
||||
stop();
|
||||
}
|
||||
|
||||
logger.debug("{}: Starting CoAP Listener", thingName);
|
||||
coapServer.start(config.localIp, this);
|
||||
statusClient = new CoapClient(completeUrl(config.deviceIp, COLOIT_URI_DEVSTATUS))
|
||||
.setTimeout((long) SHELLY_API_TIMEOUT_MS).useNONs().setEndpoint(coapServer.getEndpoint());
|
||||
discover();
|
||||
} catch (UnknownHostException e) {
|
||||
logger.debug("{}: CoAP Exception", thingName, e);
|
||||
throw new ShellyApiException("Unknown Host: " + config.deviceIp, e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStarted() {
|
||||
return statusClient != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an inbound Response (or mapped Request): decode CoAP options. handle discovery result or status updates
|
||||
*
|
||||
* @param response The Response packet
|
||||
*/
|
||||
@Override
|
||||
public void processResponse(@Nullable Response response) {
|
||||
if (response == null) {
|
||||
return; // other device instance
|
||||
}
|
||||
String ip = response.getSourceContext().getPeerAddress().toString();
|
||||
if (!ip.contains(config.deviceIp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String payload = "";
|
||||
String devId = "";
|
||||
String uri = "";
|
||||
// int validity = 0;
|
||||
int serial = -1;
|
||||
try {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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++;
|
||||
}
|
||||
|
||||
// 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 || ((serial & 0xFF) != 0))) {
|
||||
logger.debug("{}: Serial {} was already processed, ignore update", thingName, serial);
|
||||
return;
|
||||
}
|
||||
|
||||
// fixed malformed JSON :-(
|
||||
payload = fixJSON(payload);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
if (!discovering) {
|
||||
// Observe Status Updates
|
||||
reqStatus = sendRequest(reqStatus, config.deviceIp, COLOIT_URI_DEVSTATUS, Type.NON);
|
||||
discovering = true;
|
||||
}
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
logger.debug("{}: Unable to process CoIoT Message for payload={}", thingName, payload, e);
|
||||
resetSerial();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a CoIoT device description message. This includes definitions on device units (Relay0, Relay1, Sensors
|
||||
* etc.) as well as a definition of sensors and actors. This information needs to be stored allowing to map ids from
|
||||
* status updates to the device units and matching the correct thing channel.
|
||||
*
|
||||
* @param devId The device id reported in the CoIoT message.
|
||||
* @param payload Device desciption in JSon format, example:
|
||||
* {"blk":[{"I":0,"D":"Relay0"}],"sen":[{"I":112,"T":"Switch","R":"0/1","L":0}],"act":[{"I":211,"D":"Switch","L":0,"P":[{"I":2011,"D":"ToState","R":"0/1"}]}]}
|
||||
*/
|
||||
private void handleDeviceDescription(String devId, String payload) {
|
||||
logger.debug("{}: CoIoT Device Description for {}: {}", thingName, devId, payload);
|
||||
|
||||
try {
|
||||
boolean valid = true;
|
||||
|
||||
// Decode Json
|
||||
CoIotDevDescription descr = gson.fromJson(payload, CoIotDevDescription.class);
|
||||
for (int i = 0; i < descr.blk.size(); i++) {
|
||||
CoIotDescrBlk blk = descr.blk.get(i);
|
||||
logger.debug("{}: id={}: {}", thingName, blk.id, blk.desc);
|
||||
if (!blkMap.containsKey(blk.id)) {
|
||||
blkMap.put(blk.id, blk);
|
||||
} else {
|
||||
blkMap.replace(blk.id, blk);
|
||||
}
|
||||
if ((blk.type != null) && !blk.type.isEmpty()) {
|
||||
// in fact it is a sen entry - that's vioaling the Spec
|
||||
logger.trace("{}: fix: auto-create sensor definition for id {}/{}!", thingName, blk.id,
|
||||
blk.desc);
|
||||
CoIotDescrSen sen = new CoIotDescrSen();
|
||||
sen.id = blk.id;
|
||||
sen.desc = blk.desc;
|
||||
sen.type = blk.type;
|
||||
sen.range = blk.range;
|
||||
sen.links = blk.links;
|
||||
valid &= addSensor(sen);
|
||||
}
|
||||
}
|
||||
|
||||
// Save to thing properties
|
||||
thingHandler.updateProperties(PROPERTY_COAP_DESCR, payload);
|
||||
|
||||
logger.debug("{}: Adding {} sensor definitions", thingName, descr.sen.size());
|
||||
if (descr.sen != null) {
|
||||
for (int i = 0; i < descr.sen.size(); i++) {
|
||||
valid &= addSensor(descr.sen.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
logger.debug(
|
||||
"{}: Incompatible device description detected for CoIoT version {} (id length mismatch), discarding!",
|
||||
thingName, coiot.getVersion());
|
||||
thingHandler.updateProperties(PROPERTY_COAP_DESCR, "");
|
||||
discover();
|
||||
return;
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.warn("{}: Unable to parse CoAP Device Description! JSON={}", thingName, payload);
|
||||
} catch (NullPointerException | IllegalArgumentException e) {
|
||||
logger.warn("{}: Unable to parse CoAP Device Description! JSON={}", thingName, payload, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new sensor to the sensor table
|
||||
*
|
||||
* @param sen CoIotDescrSen of the sensor
|
||||
*/
|
||||
private synchronized boolean addSensor(CoIotDescrSen sen) {
|
||||
logger.debug("{}: id {}: {}, Type={}, Range={}, Links={}", thingName, sen.id, sen.desc, sen.type, sen.range,
|
||||
sen.links);
|
||||
// CoIoT version 2 changes from 3 digit IDs to 4 digit IDs
|
||||
// We need to make sure that the persisted device description matches,
|
||||
// otherwise the stored one is discarded and a new discovery is triggered
|
||||
// This happens on firmware up/downgrades (version 1.8 brings CoIoT v2 with 4 digit IDs)
|
||||
int vers = coiot.getVersion();
|
||||
if (((vers == COIOT_VERSION_1) && (sen.id.length() > 3))
|
||||
|| ((vers >= COIOT_VERSION_2) && (sen.id.length() < 4))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
CoIotDescrSen fixed = coiot.fixDescription(sen, blkMap);
|
||||
if (!sensorMap.containsKey(fixed.id)) {
|
||||
sensorMap.put(sen.id, fixed);
|
||||
} else {
|
||||
sensorMap.replace(sen.id, fixed);
|
||||
}
|
||||
} catch (NullPointerException | IllegalArgumentException e) { // depending on firmware release the CoAP device
|
||||
// description is buggy
|
||||
logger.debug("{}: Unable to decode sensor definition -> skip", thingName, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process CoIoT status update message. If a status update is received, but the device description has not been
|
||||
* received yet a GET is send to query device description.
|
||||
*
|
||||
* @param devId device id included in the status packet
|
||||
* @param payload CoAP payload (Json format), example: {"G":[[0,112,0]]}
|
||||
* @param serial Serial for this request. If this the the same as last serial
|
||||
* the update was already sent and processed so this one gets
|
||||
* ignored.
|
||||
*/
|
||||
private void handleStatusUpdate(String devId, String payload, int serial) {
|
||||
logger.debug("{}: CoIoT Sensor data {} (serial={})", thingName, payload, serial);
|
||||
if (blkMap.isEmpty()) {
|
||||
// send discovery packet
|
||||
resetSerial();
|
||||
discover();
|
||||
|
||||
// try to uses description from last initialization
|
||||
String savedDescr = thingHandler.getProperty(PROPERTY_COAP_DESCR);
|
||||
if (savedDescr.isEmpty()) {
|
||||
logger.debug("{}: Device description not yet received, trigger auto-initialization", thingName);
|
||||
return;
|
||||
}
|
||||
|
||||
// simulate received device description to create element table
|
||||
logger.debug("{}: Device description for {} restored: {}", thingName, devId, savedDescr);
|
||||
handleDeviceDescription(devId, savedDescr);
|
||||
}
|
||||
|
||||
// Parse Json,
|
||||
CoIotGenericSensorList list = gson.fromJson(fixJSON(payload), CoIotGenericSensorList.class);
|
||||
if (list.generic == null) {
|
||||
logger.debug("{}: Sensor list has invalid format! Payload: {}", devId, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
List<CoIotSensor> sensorUpdates = list.generic;
|
||||
Map<String, State> updates = new TreeMap<String, State>();
|
||||
logger.debug("{}: {} CoAP sensor updates received", thingName, sensorUpdates.size());
|
||||
int failed = 0;
|
||||
for (int i = 0; i < sensorUpdates.size(); i++) {
|
||||
try {
|
||||
CoIotSensor s = sensorUpdates.get(i);
|
||||
if (!sensorMap.containsKey(s.id)) {
|
||||
logger.debug("{}: Invalid id in sensor description: {}, index {}", thingName, s.id, i);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
CoIotDescrSen sen = sensorMap.get(s.id);
|
||||
// find matching sensor definition from device description, use the Link ID as index
|
||||
sen = coiot.fixDescription(sen, blkMap);
|
||||
if (!blkMap.containsKey(sen.links)) {
|
||||
logger.debug("{}: Invalid CoAP description: sen.links({}", thingName, getString(sen.links));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!blkMap.containsKey(sen.links)) {
|
||||
logger.debug("{}: Unable to find BLK for link {} from sen.id={}", thingName, sen.links, sen.id);
|
||||
continue;
|
||||
}
|
||||
CoIotDescrBlk element = blkMap.get(sen.links);
|
||||
logger.trace("{}: Sensor value[{}]: id={}, Value={} ({}, Type={}, Range={}, Link={}: {})", thingName,
|
||||
i, s.id, getString(s.valueStr).isEmpty() ? s.value : s.valueStr, sen.desc, sen.type, sen.range,
|
||||
sen.links, element.desc);
|
||||
|
||||
if (!coiot.handleStatusUpdate(sensorUpdates, sen, s, updates)) {
|
||||
logger.debug("{}: CoIoT data for id {}, type {}/{} not processed, value={}; payload={}", thingName,
|
||||
sen.id, sen.type, sen.desc, s.value, payload);
|
||||
}
|
||||
} catch (NullPointerException | IllegalArgumentException e) {
|
||||
// even the processing of one value failed we continue with the next one (sometimes this is caused by
|
||||
// buggy formats provided by the device
|
||||
logger.debug("{}: Unable to process data from sensor[{}], devId={}, payload={}", thingName, i, devId,
|
||||
payload, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!updates.isEmpty()) {
|
||||
int updated = 0;
|
||||
for (Map.Entry<String, State> u : updates.entrySet()) {
|
||||
updated += thingHandler.updateChannel(u.getKey(), u.getValue(), false) ? 1 : 0;
|
||||
}
|
||||
if (updated > 0) {
|
||||
logger.debug("{}: {} channels updated from CoIoT status, serial={}", thingName, updated, serial);
|
||||
if (profile.isSensor || profile.isRoller) {
|
||||
// CoAP is currently lacking the lastUpdate info, so we use host timestamp
|
||||
thingHandler.updateChannel(profile.getControlGroup(0), CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
}
|
||||
}
|
||||
|
||||
// Old firmware release are lacking various status values, which are not updated using CoIoT.
|
||||
// In this case we keep a refresh so it gets polled using REST. Beginning with Firmware 1.6 most
|
||||
// of the values are available
|
||||
if ((!thingHandler.autoCoIoT && (thingHandler.scheduledUpdates <= 1))
|
||||
|| (thingHandler.autoCoIoT && !profile.isLight && !profile.hasBattery)) {
|
||||
thingHandler.requestUpdates(1, false);
|
||||
}
|
||||
} else {
|
||||
if (failed == sensorUpdates.size()) {
|
||||
logger.debug("{}: Device description problem detected, re-discover", thingName);
|
||||
coiotBound = false;
|
||||
discover();
|
||||
}
|
||||
}
|
||||
|
||||
// Remember serial, new packets with same serial will be ignored
|
||||
lastSerial = serial;
|
||||
lastPayload = payload;
|
||||
}
|
||||
|
||||
private void discover() {
|
||||
reqDescription = sendRequest(reqDescription, config.deviceIp, COLOIT_URI_DEVDESC, Type.CON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix malformed JSON - stupid, but the devices sometimes return malformed JSON with then causes a
|
||||
* JsonSyntaxException
|
||||
*
|
||||
* @param json to be checked/fixed
|
||||
*/
|
||||
private static String fixJSON(String payload) {
|
||||
String json = payload;
|
||||
json = json.replace("}{", "},{");
|
||||
json = json.replace("][", "],[");
|
||||
json = json.replace("],,[", "],[");
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a new request (Discovery to get Device Description). Before a pending
|
||||
* request will be canceled.
|
||||
*
|
||||
* @param request The current request (this will be canceled an a new one will
|
||||
* be created)
|
||||
* @param ipAddress Device's IP address
|
||||
* @param uri The URI we are calling (CoIoT = /cit/d or /cit/s)
|
||||
* @param con true: send as CON, false: send as NON
|
||||
* @return new packet
|
||||
*/
|
||||
private Request sendRequest(@Nullable Request request, String ipAddress, String uri, Type con) {
|
||||
if ((request != null) && !request.isCanceled()) {
|
||||
request.cancel();
|
||||
}
|
||||
|
||||
resetSerial();
|
||||
return newRequest(ipAddress, uri, con).send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new Request structure. A message observer will be added to get the
|
||||
* callback when a response has been received.
|
||||
*
|
||||
* @param ipAddress IP address of the device
|
||||
* @param uri URI to be addressed
|
||||
* @param uri The URI we are calling (CoIoT = /cit/d or /cit/s)
|
||||
* @param con true: send as CON, false: send as NON
|
||||
* @return new packet
|
||||
*/
|
||||
|
||||
private Request newRequest(String ipAddress, 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.setToken(EMPTY_BYTE);
|
||||
request.addMessageObserver(new MessageObserverAdapter() {
|
||||
@Override
|
||||
public void onResponse(@Nullable Response response) {
|
||||
processResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
logger.debug("{}: CoAP Request was canceled", thingName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout() {
|
||||
logger.debug("{}: CoAP Request timed out", thingName);
|
||||
}
|
||||
});
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset serial and payload used to detect duplicate messages, which have to be ignored.
|
||||
* We can't rely that the device manages serials correctly all the time. There are firmware releases sending updated
|
||||
* sensor information with the serial from the last packet, which is wrong. We bypass this problem by comparing also
|
||||
* the payload.
|
||||
*/
|
||||
private void resetSerial() {
|
||||
lastSerial = -1;
|
||||
lastPayload = "";
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return coiotVers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel pending requests and shutdown the client
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
if (isStarted()) {
|
||||
logger.debug("{}: Stopping CoAP Listener", thingName);
|
||||
coapServer.stop(this);
|
||||
if (statusClient != null) {
|
||||
statusClient.shutdown();
|
||||
statusClient = null;
|
||||
}
|
||||
if (!reqDescription.isCanceled()) {
|
||||
reqDescription.cancel();
|
||||
}
|
||||
if (!reqStatus.isCanceled()) {
|
||||
reqStatus.cancel();
|
||||
}
|
||||
}
|
||||
resetSerial();
|
||||
coiotBound = false;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
stop();
|
||||
}
|
||||
|
||||
private static String completeUrl(String ipAddress, String uri) {
|
||||
return "coap://" + ipAddress + ":" + COIOT_PORT + uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapJSonDTO} helps the CoIoT Json into Java objects
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
public class ShellyCoapJSonDTO {
|
||||
// Coap
|
||||
public static final int COIOT_VERSION_1 = 1;
|
||||
public static final int COIOT_VERSION_2 = 2;
|
||||
|
||||
public static final int COIOT_PORT = 5683;
|
||||
public static final String COAP_MULTICAST_ADDRESS = "224.0.1.187";
|
||||
|
||||
public static final String COLOIT_URI_BASE = "/cit/";
|
||||
public static final String COLOIT_URI_DEVDESC = COLOIT_URI_BASE + "d";
|
||||
public static final String COLOIT_URI_DEVSTATUS = COLOIT_URI_BASE + "s";
|
||||
|
||||
public static final int COIOT_OPTION_GLOBAL_DEVID = 3332;
|
||||
public static final int COIOT_OPTION_STATUS_VALIDITY = 3412;
|
||||
public static final int COIOT_OPTION_STATUS_SERIAL = 3420;
|
||||
|
||||
public static final String COIOT_TAG_BLK = "blk";
|
||||
public static final String COIOT_TAG_SEN = "sen";
|
||||
public static final String COIOT_TAG_ACT = "act";
|
||||
public static final String COIOT_TAG_GENERIC = "G";
|
||||
|
||||
public static class CoIotDescrBlk {
|
||||
@SerializedName("I")
|
||||
String id; // ID
|
||||
@SerializedName("D")
|
||||
String desc; // Description
|
||||
|
||||
// Sometimes sen entries are part of the blk array - not conforming the Spec!
|
||||
@SerializedName("T")
|
||||
public String type; // Type
|
||||
@SerializedName("R")
|
||||
public String range; // Range
|
||||
@SerializedName("L")
|
||||
public String links; // Links
|
||||
}
|
||||
|
||||
public static class CoIotDescrSen {
|
||||
@SerializedName("I")
|
||||
String id; // ID
|
||||
@SerializedName("D")
|
||||
String desc; // Description
|
||||
@SerializedName("T")
|
||||
public String type; // Type
|
||||
@SerializedName("R")
|
||||
public String range; // Range
|
||||
@SerializedName("L")
|
||||
public String links; // Links
|
||||
@SerializedName("U")
|
||||
public String unit; // Unit
|
||||
}
|
||||
|
||||
public static class CoIotDescrP {
|
||||
@SerializedName("I")
|
||||
String id; // ID
|
||||
@SerializedName("D")
|
||||
String desc; // Description
|
||||
@SerializedName("R")
|
||||
public String range; // Range
|
||||
}
|
||||
|
||||
public static class CoIotDescrAct {
|
||||
@SerializedName("I")
|
||||
String id; // ID
|
||||
@SerializedName("D")
|
||||
String desc; // Description
|
||||
@SerializedName("L")
|
||||
public String links; // Links
|
||||
@SerializedName("P")
|
||||
public List<CoIotDescrP> pTag; // ?
|
||||
}
|
||||
|
||||
public static class CoIotDevDescription {
|
||||
public List<CoIotDescrBlk> blk;
|
||||
public List<CoIotDescrSen> sen;
|
||||
// public List<CoIotDescrAct> act;
|
||||
|
||||
public CoIotDevDescription() {
|
||||
blk = new ArrayList<>();
|
||||
sen = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static class CoIotSensor {
|
||||
@SerializedName("index")
|
||||
public String id; // id
|
||||
public double value; // value
|
||||
public String valueStr; // value
|
||||
public List<Object> valueArray;
|
||||
}
|
||||
|
||||
public static class CoIotGenericSensorList {
|
||||
@SerializedName("G")
|
||||
public List<CoIotSensor> generic;
|
||||
|
||||
public CoIotGenericSensorList() {
|
||||
generic = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class CoIotDevDescrTypeAdapter extends TypeAdapter<CoIotDevDescription> {
|
||||
@Override
|
||||
public CoIotDevDescription read(final JsonReader in) throws IOException {
|
||||
CoIotDevDescription descr = new CoIotDevDescription();
|
||||
|
||||
/*
|
||||
* parse JSON like
|
||||
* "blk": [
|
||||
* { "I": 0, "D": "Relay0"},
|
||||
* { "I": 1, "D": "Sensors"} ],
|
||||
* "sen": [
|
||||
* { "I": 111, "T": "P", "D": "Power","R": "0/3500","L": 0},
|
||||
* { "I": 112,"T": "S","D": "Switch","R": "0/1","L": 0}
|
||||
* ]
|
||||
*/
|
||||
in.beginObject();
|
||||
String name = in.nextName();
|
||||
if (name.equalsIgnoreCase(COIOT_TAG_BLK)) {
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
CoIotDescrBlk blk = new CoIotDescrBlk();
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
switch (in.nextName().toUpperCase()) {
|
||||
case "I":
|
||||
blk.id = in.nextString();
|
||||
break;
|
||||
case "D":
|
||||
blk.desc = in.nextString();
|
||||
break;
|
||||
default:
|
||||
// skip data
|
||||
in.nextNull();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
descr.blk.add(blk);
|
||||
}
|
||||
in.endArray();
|
||||
name = in.nextName();
|
||||
}
|
||||
|
||||
if (name.equalsIgnoreCase(COIOT_TAG_SEN)) {
|
||||
/*
|
||||
* parse sensor list, e.g.
|
||||
* "sen":[
|
||||
* { "I":111,"T":"Red","R":"0/255","L":0},
|
||||
* { "I":121,"T":"Green","R":"0/255","L":0},
|
||||
* ]
|
||||
*/
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
CoIotDescrSen sen = new CoIotDescrSen();
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String tag = in.nextName();
|
||||
switch (tag.toUpperCase()) {
|
||||
case "I":
|
||||
sen.id = in.nextString();
|
||||
break;
|
||||
case "D":
|
||||
sen.desc = in.nextString();
|
||||
break;
|
||||
case "T":
|
||||
sen.type = in.nextString();
|
||||
break;
|
||||
case "R":
|
||||
JsonToken token = in.peek();
|
||||
if (token == JsonToken.BEGIN_ARRAY) {
|
||||
// must be v2: an array
|
||||
in.beginArray();
|
||||
sen.range = "";
|
||||
while (in.hasNext()) {
|
||||
String value = in.nextString();
|
||||
sen.range += sen.range.isEmpty() ? value : ";" + value;
|
||||
}
|
||||
in.endArray();
|
||||
} else {
|
||||
sen.range = in.nextString();
|
||||
}
|
||||
break;
|
||||
case "L":
|
||||
sen.links = String.valueOf(in.nextInt());
|
||||
break;
|
||||
case "U": // New in CoAPv2: unit"
|
||||
sen.unit = in.nextString();
|
||||
break;
|
||||
default:
|
||||
// skip data
|
||||
in.nextNull();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
descr.sen.add(sen);
|
||||
}
|
||||
|
||||
in.endArray();
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
return descr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final JsonWriter out, final CoIotDevDescription descr) throws IOException {
|
||||
out.beginObject();
|
||||
if (descr != null) {
|
||||
out.name(COIOT_TAG_BLK).beginArray();
|
||||
for (int i = 0; i < descr.blk.size(); i++) {
|
||||
CoIotDescrBlk blk = descr.blk.get(i);
|
||||
out.beginArray();
|
||||
out.value(blk.id);
|
||||
out.value(blk.desc);
|
||||
out.endArray();
|
||||
}
|
||||
out.endArray();
|
||||
|
||||
out.name(COIOT_TAG_SEN).beginArray();
|
||||
for (int i = 0; i < descr.sen.size(); i++) {
|
||||
// Create element, e.g. {“I”:66, “D”:“lux”, “T”:“L”, “R”:“0/100000”, “L”:1},
|
||||
CoIotDescrSen sen = descr.sen.get(i);
|
||||
out.beginArray();
|
||||
out.value(sen.id);
|
||||
out.value(sen.desc);
|
||||
out.value(sen.type);
|
||||
out.value(sen.range);
|
||||
out.value(sen.links);
|
||||
if (sen.unit != null) {
|
||||
out.value(sen.unit);
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
out.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class CoIotSensorTypeAdapter extends TypeAdapter<CoIotGenericSensorList> {
|
||||
@Override
|
||||
public CoIotGenericSensorList read(final JsonReader in) throws IOException {
|
||||
CoIotGenericSensorList list = new CoIotGenericSensorList();
|
||||
|
||||
in.beginObject();
|
||||
String generic = in.nextName();
|
||||
if (generic.equals(COIOT_TAG_GENERIC)) {
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
CoIotSensor sensor = new CoIotSensor();
|
||||
in.beginArray();
|
||||
in.nextInt(); // alway 0
|
||||
sensor.id = Integer.toString(in.nextInt());
|
||||
JsonToken token = in.peek();
|
||||
if (token == JsonToken.STRING) {
|
||||
// handle as string
|
||||
sensor.valueStr = in.nextString();
|
||||
sensor.value = -1;
|
||||
} else if (token == JsonToken.NUMBER) {
|
||||
// handle as double
|
||||
sensor.value = in.nextDouble();
|
||||
sensor.valueStr = "";
|
||||
} else if (token == JsonToken.BEGIN_ARRAY) {
|
||||
sensor.valueArray = new ArrayList<>();
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
if (in.peek() == JsonToken.STRING) {
|
||||
sensor.valueArray.add(in.nextString());
|
||||
} else {
|
||||
// skip
|
||||
in.nextNull();
|
||||
}
|
||||
}
|
||||
in.endArray();
|
||||
}
|
||||
in.endArray();
|
||||
list.generic.add(sensor);
|
||||
}
|
||||
in.endArray();
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final JsonWriter out, final CoIotGenericSensorList o) throws IOException {
|
||||
CoIotGenericSensorList sensors = o;
|
||||
out.beginObject();
|
||||
if (sensors != null) {
|
||||
out.name(COIOT_TAG_GENERIC).beginArray();
|
||||
for (int i = 0; i < sensors.generic.size(); i++) {
|
||||
out.beginArray();
|
||||
out.value(0);
|
||||
out.value(sensors.generic.get(i).id);
|
||||
out.value(sensors.generic.get(i).value);
|
||||
out.endArray();
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
out.endObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import org.eclipse.californium.core.coap.Response;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapListener} describes the listening interface to process Coap responses
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ShellyCoapListener {
|
||||
public void processResponse(@Nullable Response response);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.coap;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.coap.ShellyCoapJSonDTO.COIOT_PORT;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.californium.core.CoapResource;
|
||||
import org.eclipse.californium.core.CoapServer;
|
||||
import org.eclipse.californium.core.coap.CoAP;
|
||||
import org.eclipse.californium.core.coap.CoAP.Code;
|
||||
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
|
||||
import org.eclipse.californium.core.coap.Request;
|
||||
import org.eclipse.californium.core.coap.Response;
|
||||
import org.eclipse.californium.core.network.CoapEndpoint;
|
||||
import org.eclipse.californium.core.network.Exchange;
|
||||
import org.eclipse.californium.core.network.config.NetworkConfig;
|
||||
import org.eclipse.californium.elements.UdpMulticastConnector;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.util.ConcurrentHashSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCoapServer} implements the UDP listener and status event processor (for /cit/s messages)
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyCoapServer {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyCoapServer.class);
|
||||
|
||||
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 final Set<ShellyCoapListener> coapListeners = new ConcurrentHashSet<>();
|
||||
|
||||
protected class ShellyStatusListener extends CoapResource {
|
||||
private ShellyCoapServer listener;
|
||||
|
||||
public ShellyStatusListener(String uri, ShellyCoapServer listener) {
|
||||
super(uri, true);
|
||||
getAttributes().setTitle("ShellyCoapListener");
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(@Nullable final Exchange exchange) {
|
||||
if (exchange != null) {
|
||||
Request request = exchange.getRequest();
|
||||
Code code = exchange.getRequest().getCode();
|
||||
switch (code) {
|
||||
case CUSTOM_30:
|
||||
listener.processResponse(createResponse(request));
|
||||
break;
|
||||
default:
|
||||
super.handleRequest(exchange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void start(String localIp, ShellyCoapListener listener) throws UnknownHostException {
|
||||
if (!started) {
|
||||
logger.debug("Initializing CoIoT listener (local IP={}:{})", localIp, COIOT_PORT);
|
||||
NetworkConfig nc = NetworkConfig.getStandard();
|
||||
InetAddress localAddr = InetAddress.getByName(localIp);
|
||||
InetSocketAddress localPort = new InetSocketAddress(COIOT_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.addEndpoint(statusEndpoint);
|
||||
CoapResource cit = new ShellyStatusListener("cit", this);
|
||||
CoapResource s = new ShellyStatusListener("s", this);
|
||||
cit.add(s);
|
||||
server.add(cit);
|
||||
started = true;
|
||||
}
|
||||
|
||||
if (!coapListeners.contains(listener)) {
|
||||
coapListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
protected void processResponse(Response response) {
|
||||
coapListeners.forEach(listener -> listener.processResponse(response));
|
||||
}
|
||||
|
||||
public static Response createResponse(Request request) {
|
||||
Response response = Response.createResponse(request, ResponseCode.CONTENT);
|
||||
response.setType(request.getType());
|
||||
response.setSourceContext(request.getSourceContext());
|
||||
response.setMID(request.getMID());
|
||||
response.setOptions(request.getOptions());
|
||||
response.setPayload(request.getPayload());
|
||||
return response;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CoapEndpoint getEndpoint() {
|
||||
return statusEndpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel pending requests and shutdown the client
|
||||
*/
|
||||
public void stop(ShellyCoapListener listener) {
|
||||
coapListeners.remove(listener);
|
||||
if (coapListeners.isEmpty()) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void stop() {
|
||||
if (started) {
|
||||
// Last listener
|
||||
server.stop();
|
||||
statusEndpoint.stop();
|
||||
coapListeners.clear();
|
||||
started = false;
|
||||
logger.debug("CoAP Listener stopped");
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.config;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Dictionary;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ShellyBindingConfiguration} class contains fields mapping binding configuration parameters.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyBindingConfiguration {
|
||||
// Binding Configuration Properties
|
||||
public static final String CONFIG_DEF_HTTP_USER = "defaultUserId";
|
||||
public static final String CONFIG_DEF_HTTP_PWD = "defaultPassword";
|
||||
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 boolean autoCoIoT = true;
|
||||
|
||||
public void updateFromProperties(Map<String, Object> properties) {
|
||||
for (Map.Entry<String, Object> e : properties.entrySet()) {
|
||||
if (e.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
switch (e.getKey()) {
|
||||
case CONFIG_DEF_HTTP_USER:
|
||||
defaultUserId = (String) e.getValue();
|
||||
break;
|
||||
case CONFIG_DEF_HTTP_PWD:
|
||||
defaultPassword = (String) e.getValue();
|
||||
break;
|
||||
case CONFIG_AUTOCOIOT:
|
||||
autoCoIoT = (boolean) e.getValue();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFromProperties(Dictionary<String, Object> properties) {
|
||||
List<String> keys = Collections.list(properties.keys());
|
||||
Map<String, Object> dictCopy = keys.stream().collect(Collectors.toMap(Function.identity(), properties::get));
|
||||
updateFromProperties(dictCopy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ShellyThingConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyThingConfiguration {
|
||||
public String deviceIp = ""; // ip address of thedevice
|
||||
public String userId = ""; // userid for http basic auth
|
||||
public String password = ""; // password for http basic auth
|
||||
|
||||
public int updateInterval = 60; // schedule interval for the update job
|
||||
public int lowBattery = 15; // threshold for battery value
|
||||
public boolean brightnessAutoOn = true; // true: turn on device if brightness > 0 is set
|
||||
|
||||
public boolean eventsButton = false; // true: register for Relay btn_xxx events
|
||||
public boolean eventsSwitch = true; // true: register for device out_xxx events
|
||||
public boolean eventsPush = true; // true: register for short/long push events
|
||||
public boolean eventsRoller = true; // true: register for short/long push events
|
||||
public boolean eventsSensorReport = true; // true: register for sensor events
|
||||
public boolean eventsCoIoT = false; // true: use CoIoT events (based on COAP)
|
||||
|
||||
public String localIp = ""; // local ip addresses used to create callback url
|
||||
public String localPort = "8080";
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.PROPERTY_MODEL_ID;
|
||||
|
||||
import java.io.IOException;
|
||||
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.ShellyApiResult;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyHttpApi;
|
||||
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.util.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.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.cm.Configuration;
|
||||
import org.osgi.service.cm.ConfigurationAdmin;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Modified;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class identifies Shelly devices by their mDNS service information.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = MDNSDiscoveryParticipant.class, immediate = true)
|
||||
public class ShellyDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyDiscoveryParticipant.class);
|
||||
private final ShellyBindingConfiguration bindingConfig = new ShellyBindingConfiguration();
|
||||
private final ShellyTranslationProvider messages;
|
||||
private final HttpClient httpClient;
|
||||
private final ConfigurationAdmin configurationAdmin;
|
||||
|
||||
/**
|
||||
* OSGI Service Activation
|
||||
*
|
||||
* @param componentContext
|
||||
* @param localeProvider
|
||||
*/
|
||||
@Activate
|
||||
public ShellyDiscoveryParticipant(@Reference ConfigurationAdmin configurationAdmin,
|
||||
@Reference HttpClientFactory httpClientFactory, @Reference LocaleProvider localeProvider,
|
||||
@Reference TranslationProvider i18nProvider, ComponentContext componentContext) {
|
||||
logger.debug("Activating ShellyDiscovery service");
|
||||
this.configurationAdmin = configurationAdmin;
|
||||
this.messages = new ShellyTranslationProvider(componentContext.getBundleContext().getBundle(), i18nProvider,
|
||||
localeProvider);
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
bindingConfig.updateFromProperties(componentContext.getProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return SERVICE_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process updates to Binding Config
|
||||
*
|
||||
* @param componentContext
|
||||
*/
|
||||
@Modified
|
||||
protected void modified(final ComponentContext componentContext) {
|
||||
logger.debug("Shelly Binding Configuration refreshed");
|
||||
bindingConfig.updateFromProperties(componentContext.getProperties());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DiscoveryResult createResult(final ServiceInfo service) {
|
||||
String name = service.getName().toLowerCase(); // Duao: Name starts with" Shelly" rather than "shelly"
|
||||
if (!name.startsWith("shelly")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String address = "";
|
||||
try {
|
||||
String mode = "";
|
||||
String model = "unknown";
|
||||
String deviceName = "";
|
||||
ThingUID thingUID = null;
|
||||
ShellyDeviceProfile profile = null;
|
||||
Map<String, Object> properties = new TreeMap<>();
|
||||
|
||||
name = service.getName().toLowerCase();
|
||||
address = service.getHostAddress();
|
||||
if ((address == null) || address.isEmpty()) {
|
||||
logger.trace("{}: Shelly device discovered with empty IP address (service-name={})", name, service);
|
||||
return null;
|
||||
}
|
||||
String thingType = service.getQualifiedName().contains(SERVICE_TYPE) && name.contains("-")
|
||||
? substringBeforeLast(name, "-")
|
||||
: name;
|
||||
logger.debug("{}: Shelly device discovered: IP-Adress={}, type={}", name, address, thingType);
|
||||
|
||||
// Get device settings
|
||||
Configuration serviceConfig = configurationAdmin.getConfiguration("binding.shelly");
|
||||
if (serviceConfig.getProperties() != null) {
|
||||
bindingConfig.updateFromProperties(serviceConfig.getProperties());
|
||||
}
|
||||
|
||||
ShellyThingConfiguration config = new ShellyThingConfiguration();
|
||||
config.deviceIp = address;
|
||||
config.userId = bindingConfig.defaultUserId;
|
||||
config.password = bindingConfig.defaultPassword;
|
||||
|
||||
try {
|
||||
ShellyHttpApi api = new ShellyHttpApi(name, config, httpClient);
|
||||
|
||||
profile = api.getDeviceProfile(thingType);
|
||||
logger.debug("{}: Shelly settings : {}", name, profile.settingsJson);
|
||||
deviceName = getString(profile.settings.name);
|
||||
model = getString(profile.settings.device.type);
|
||||
mode = profile.mode;
|
||||
|
||||
properties = ShellyBaseHandler.fillDeviceProperties(profile);
|
||||
logger.trace("{}: thingType={}, deviceType={}, mode={}, symbolic name={}", name, thingType,
|
||||
profile.deviceType, 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.info("{}: {}", name, messages.get("discovery.failed", address, e.toString()));
|
||||
logger.debug("{}: Discovery failed", name, e);
|
||||
}
|
||||
} catch (IllegalArgumentException e) { // maybe some format description was buggy
|
||||
logger.debug("{}: Discovery failed!", name, e);
|
||||
}
|
||||
|
||||
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_MODE, mode);
|
||||
|
||||
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(name).build();
|
||||
}
|
||||
} 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 {
|
||||
logger.debug("ServiceInfo {}", service);
|
||||
if (service == null) {
|
||||
throw new IllegalArgumentException("service must not be null!");
|
||||
}
|
||||
String serviceName = service.getName();
|
||||
if (serviceName == null) {
|
||||
throw new IllegalArgumentException("serviceName must not be null!");
|
||||
}
|
||||
serviceName = serviceName.toLowerCase();
|
||||
if (!serviceName.contains(VENDOR.toLowerCase())) {
|
||||
logger.debug("Not a " + VENDOR + " device!");
|
||||
return null;
|
||||
}
|
||||
return ShellyThingCreator.getThingUID(serviceName, "", "", false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* The {@link ShellyThingCreator} maps the device id into the thing type id
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyThingCreator {
|
||||
private static final Map<String, String> THING_TYPE_MAPPING = new LinkedHashMap<>();
|
||||
static {
|
||||
// mapping by thing type
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_1PM, THING_TYPE_SHELLY1PM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_1, THING_TYPE_SHELLY1_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_3EM, THING_TYPE_SHELLY3EM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_EM, THING_TYPE_SHELLYEM_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_GAS, THING_TYPE_SHELLYGAS_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DW, THING_TYPE_SHELLYDOORWIN_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DW2, THING_TYPE_SHELLYDOORWIN2_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DUO, THING_TYPE_SHELLYDUO_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_BULB, THING_TYPE_SHELLYBULB_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_VINTAGE, THING_TYPE_SHELLYVINTAGE_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DIMMER, THING_TYPE_SHELLYDIMMER_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_DIMMER2, THING_TYPE_SHELLYDIMMER2_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_IX3, THING_TYPE_SHELLYIX3_STR);
|
||||
THING_TYPE_MAPPING.put(SHELLYDT_BUTTON1, THING_TYPE_SHELLYBUTTON1_STR);
|
||||
|
||||
// mapping by thing type
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1_STR, THING_TYPE_SHELLY1_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY1PM_STR, THING_TYPE_SHELLY1PM_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY4PRO_STR, THING_TYPE_SHELLY4PRO_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDIMMER2_STR, THING_TYPE_SHELLYDIMMER2_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDIMMER_STR, THING_TYPE_SHELLYDIMMER_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYIX3_STR, THING_TYPE_SHELLYIX3_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLY3EM_STR, THING_TYPE_SHELLY3EM_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYEM_STR, THING_TYPE_SHELLYEM_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDUO_STR, THING_TYPE_SHELLYDUO_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYVINTAGE_STR, THING_TYPE_SHELLYVINTAGE_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBULB_STR, THING_TYPE_SHELLYBULB_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDUO_STR, THING_TYPE_SHELLYDUO_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYHT_STR, THING_TYPE_SHELLYHT_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYSMOKE_STR, THING_TYPE_SHELLYSMOKE_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYGAS_STR, THING_TYPE_SHELLYGAS_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYFLOOD_STR, THING_TYPE_SHELLYFLOOD_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDOORWIN_STR, THING_TYPE_SHELLYDOORWIN_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYDOORWIN2_STR, THING_TYPE_SHELLYDOORWIN2_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYSENSE_STR, THING_TYPE_SHELLYSENSE_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYEYE_STR, THING_TYPE_SHELLYEYE_STR);
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYBUTTON1_STR, THING_TYPE_SHELLYBUTTON1_STR);
|
||||
|
||||
THING_TYPE_MAPPING.put(THING_TYPE_SHELLYPROTECTED_STR, THING_TYPE_SHELLYPROTECTED_STR);
|
||||
}
|
||||
|
||||
public static ThingUID getThingUID(String serviceName, String deviceType, String mode, boolean unknown) {
|
||||
String devid = substringAfterLast(serviceName, "-");
|
||||
if (devid.isEmpty()) {
|
||||
throw new IllegalArgumentException("serviceName has improper format: " + serviceName);
|
||||
}
|
||||
return new ThingUID(!unknown ? getThingTypeUID(serviceName, deviceType, mode)
|
||||
: getThingTypeUID(THING_TYPE_SHELLYPROTECTED_STR + "-" + devid, deviceType, mode), devid);
|
||||
}
|
||||
|
||||
public static ThingTypeUID getThingTypeUID(String serviceName, String deviceType, String mode) {
|
||||
return new ThingTypeUID(BINDING_ID, getThingType(serviceName, deviceType, mode));
|
||||
}
|
||||
|
||||
public static ThingTypeUID getUnknownTTUID() {
|
||||
return new ThingTypeUID(BINDING_ID, THING_TYPE_SHELLYPROTECTED_STR);
|
||||
}
|
||||
|
||||
public static String getThingType(String hostname, String deviceType, String mode) {
|
||||
String name = hostname.toLowerCase();
|
||||
String type = substringBefore(name, "-").toLowerCase();
|
||||
String devid = substringAfterLast(name, "-");
|
||||
if ((devid == null) || (type == null)) {
|
||||
throw new IllegalArgumentException("Invalid device name format: " + hostname);
|
||||
}
|
||||
|
||||
// First check for special handling
|
||||
if (name.startsWith(THING_TYPE_SHELLY25_PREFIX)) { // Shelly v2.5
|
||||
return mode.equals(SHELLY_MODE_RELAY) ? THING_TYPE_SHELLY25_RELAY_STR : THING_TYPE_SHELLY25_ROLLER_STR;
|
||||
}
|
||||
if (name.startsWith(THING_TYPE_SHELLY2_PREFIX)) { // Shelly v2
|
||||
return mode.equals(SHELLY_MODE_RELAY) ? THING_TYPE_SHELLY2_RELAY_STR : THING_TYPE_SHELLY2_ROLLER_STR;
|
||||
}
|
||||
if (name.startsWith(THING_TYPE_SHELLYPLUG_STR)) {
|
||||
// shellyplug-s needs to be mapped to shellyplugs to follow the schema
|
||||
// for the thing types: <thing type>-<mode>
|
||||
if (name.startsWith(THING_TYPE_SHELLYPLUGS_STR) || name.contains("-s")) {
|
||||
return THING_TYPE_SHELLYPLUGS_STR;
|
||||
}
|
||||
return THING_TYPE_SHELLYPLUG_STR;
|
||||
}
|
||||
if (name.startsWith(THING_TYPE_SHELLYRGBW2_PREFIX)) {
|
||||
return mode.equals(SHELLY_MODE_COLOR) ? THING_TYPE_SHELLYRGBW2_COLOR_STR : THING_TYPE_SHELLYRGBW2_WHITE_STR;
|
||||
}
|
||||
|
||||
// Check general mapping
|
||||
if (!deviceType.isEmpty() && THING_TYPE_MAPPING.containsKey(deviceType)) {
|
||||
return THING_TYPE_MAPPING.get(deviceType);
|
||||
}
|
||||
if (THING_TYPE_MAPPING.containsKey(type)) {
|
||||
return THING_TYPE_MAPPING.get(type);
|
||||
}
|
||||
return THING_TYPE_SHELLYUNKNOWN_STR;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ShellyCHANNEL_DEFINITIONSDTO} defines channel information for dynamically created channels. Those will be
|
||||
* added on the first thing status update
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyChannelDefinitionsDTO {
|
||||
|
||||
private static final ChannelMap CHANNEL_DEFINITIONS = new ChannelMap();
|
||||
|
||||
// shortcuts to avoid line breaks (make code more readable)
|
||||
private static final String CHGR_DEVST = CHANNEL_GROUP_DEV_STATUS;
|
||||
private static final String CHGR_RELAY = CHANNEL_GROUP_RELAY_CONTROL;
|
||||
private static final String CHGR_ROLLER = CHANNEL_GROUP_ROL_CONTROL;
|
||||
private static final String CHGR_STATUS = CHANNEL_GROUP_STATUS;
|
||||
private static final String CHGR_METER = CHANNEL_GROUP_METER;
|
||||
private static final String CHGR_SENSOR = CHANNEL_GROUP_SENSOR;
|
||||
private static final String CHGR_BAT = CHANNEL_GROUP_BATTERY;
|
||||
|
||||
public static final String ITEM_TYPE_NUMBER = "Number";
|
||||
public static final String ITEM_TYPE_STRING = "String";
|
||||
public static final String ITEM_TYPE_SWITCH = "Switch";
|
||||
public static final String ITEM_TYPE_CONTACT = "Contact";
|
||||
public static final String ITEM_TYPE_DATETIME = "DateTime";
|
||||
public static final String ITEM_TYPE_TEMP = "Number:Temperature";
|
||||
public static final String ITEM_TYPE_LUX = "Number:Illuminance";
|
||||
public static final String ITEM_TYPE_POWER = "Number:Power";
|
||||
public static final String ITEM_TYPE_ENERGY = "Number:Energy";
|
||||
public static final String ITEM_TYPE_VOLT = "Number:ElectricPotential";
|
||||
public static final String ITEM_TYPE_AMP = "Number:ElectricPotential";
|
||||
public static final String ITEM_TYPE_PERCENT = "Number:Dimensionless";
|
||||
public static final String ITEM_TYPE_ANGLE = "Number:Angle";
|
||||
|
||||
public static final String PREFIX_GROUP = "definitions.shelly.group.";
|
||||
public static final String PREFIX_CHANNEL = "channel-type.shelly.";
|
||||
public static final String SUFFIX_LABEL = ".label";
|
||||
public static final String SUFFIX_DESCR = ".description";
|
||||
|
||||
public ShellyChannelDefinitionsDTO(ShellyTranslationProvider m) {
|
||||
// Device: Internal Temp
|
||||
CHANNEL_DEFINITIONS
|
||||
// Device
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_NAME, "deviceName", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ITEMP, "deviceTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_WAKEUP, "sensorWakeup", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS, "meterAccuWatts", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL, "meterAccuTotal", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED, "meterAccuReturned", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_CHARGER, "charger", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE, "ledStatusDisable", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE, "ledPowerDisable", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST, "selfTest", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPTIME, "uptime", ITEM_TYPE_NUMBER))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT, "heartBeat", ITEM_TYPE_DATETIME))
|
||||
.add(new ShellyChannel(m, CHGR_DEVST, CHANNEL_DEVST_UPDATE, "updateAvailable", ITEM_TYPE_SWITCH))
|
||||
|
||||
// Relay
|
||||
.add(new ShellyChannel(m, CHGR_RELAY, CHANNEL_OUTPUT_NAME, "outputName", ITEM_TYPE_STRING))
|
||||
|
||||
// Roller
|
||||
.add(new ShellyChannel(m, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE, "rollerState", ITEM_TYPE_STRING))
|
||||
|
||||
// RGBW2
|
||||
.add(new ShellyChannel(m, CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_INPUT, "inputState", ITEM_TYPE_SWITCH))
|
||||
|
||||
// Power Meter
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_CURRENTWATTS, "meterWatts", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_TOTALKWH, "meterTotal", ITEM_TYPE_ENERGY))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_METER_LASTMIN1, "lastPower1", ITEM_TYPE_ENERGY))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
|
||||
|
||||
// EMeter
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_TOTALRET, "meterReturned", ITEM_TYPE_ENERGY))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_REACTWATTS, "meterReactive", ITEM_TYPE_POWER))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_VOLTAGE, "meterVoltage", ITEM_TYPE_VOLT))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_CURRENT, "meterCurrent", ITEM_TYPE_AMP))
|
||||
.add(new ShellyChannel(m, CHGR_METER, CHANNEL_EMETER_PFACTOR, "meterPowerFactor", ITEM_TYPE_NUMBER))
|
||||
|
||||
// Sensors
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TEMP, "sensorTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_HUM, "sensorHumidity", ITEM_TYPE_PERCENT))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_LUX, "sensorLux", ITEM_TYPE_LUX))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ILLUM, "sensorIllumination", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_CONTACT, "sensorContact", ITEM_TYPE_CONTACT))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SSTATE, "sensorState", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VIBRATION, "sensorVibration", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_TILT, "sensorTilt", ITEM_TYPE_ANGLE))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_MOTION, "sensorMotion", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_FLOOD, "sensorFlood", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_SMOKE, "sensorSmoke", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_PPM, "sensorPPM", ITEM_TYPE_NUMBER))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_VALVE, "sensorValve", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ALARM_STATE, "alarmState", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_SENSOR_ERROR, "sensorError", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
|
||||
|
||||
// Button/ix3
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_INPUT, "inputState", ITEM_TYPE_SWITCH))
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTTYPE, "eventType", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_STATUS_EVENTCOUNT, "eventCount", ITEM_TYPE_NUMBER))
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_BUTTON_TRIGGER, "system.button", ITEM_TYPE_STRING))
|
||||
.add(new ShellyChannel(m, CHGR_STATUS, CHANNEL_LAST_UPDATE, "lastUpdate", ITEM_TYPE_DATETIME))
|
||||
|
||||
// Addon with external sensors
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1, "sensorExtTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2, "sensorExtTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3, "sensorExtTemp", ITEM_TYPE_TEMP))
|
||||
.add(new ShellyChannel(m, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY, "sensorExtHum", ITEM_TYPE_PERCENT))
|
||||
|
||||
// Battery
|
||||
.add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LEVEL, "system:battery-level",
|
||||
ITEM_TYPE_PERCENT))
|
||||
.add(new ShellyChannel(m, CHGR_BAT, CHANNEL_SENSOR_BAT_LOW, "system:low-battery", ITEM_TYPE_SWITCH));
|
||||
}
|
||||
|
||||
public static ShellyChannel getDefinition(String channelName) throws IllegalArgumentException {
|
||||
String group = substringBefore(channelName, "#");
|
||||
String channel = substringAfter(channelName, "#");
|
||||
if (group.contains(CHANNEL_GROUP_METER)) {
|
||||
group = CHANNEL_GROUP_METER; // map meter1..n to meter
|
||||
} else if (group.contains(CHANNEL_GROUP_RELAY_CONTROL)) {
|
||||
group = CHANNEL_GROUP_RELAY_CONTROL; // map meter1..n to meter
|
||||
} else if (group.contains(CHANNEL_GROUP_LIGHT_CHANNEL)) {
|
||||
group = CHANNEL_GROUP_LIGHT_CHANNEL;
|
||||
} else if (group.contains(CHANNEL_GROUP_STATUS)) {
|
||||
group = CHANNEL_GROUP_STATUS; // map status1..n to meter
|
||||
}
|
||||
String channelId = group + "#" + channel;
|
||||
return CHANNEL_DEFINITIONS.get(channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-create relay channels depending on relay type/mode
|
||||
*
|
||||
* @return ArrayList<Channel> of channels to be added to the thing
|
||||
*/
|
||||
public static Map<String, Channel> createDeviceChannels(final Thing thing, final ShellyDeviceProfile profile,
|
||||
final ShellySettingsStatus status) {
|
||||
Map<String, Channel> add = new LinkedHashMap<>();
|
||||
|
||||
addChannel(thing, add, profile.settings.name != null, CHGR_DEVST, CHANNEL_DEVST_NAME);
|
||||
|
||||
if (!profile.isSensor) {
|
||||
// Only some devices report the internal device temp
|
||||
addChannel(thing, add, (status.tmp != null) || (status.temperature != null), CHGR_DEVST,
|
||||
CHANNEL_DEVST_ITEMP);
|
||||
}
|
||||
|
||||
// RGBW2
|
||||
addChannel(thing, add, status.input != null, CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_INPUT);
|
||||
|
||||
// If device has more than 1 meter the channel accumulatedWatts receives the accumulated value
|
||||
boolean accuChannel = (((status.meters != null) && (status.meters.size() > 1) && !profile.isRoller
|
||||
&& !profile.isRGBW2) || ((status.emeters != null && status.emeters.size() > 1)));
|
||||
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUWATTS);
|
||||
addChannel(thing, add, accuChannel, CHGR_DEVST, CHANNEL_DEVST_ACCUTOTAL);
|
||||
addChannel(thing, add, accuChannel && (status.emeters != null), CHGR_DEVST, CHANNEL_DEVST_ACCURETURNED);
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPDATE);
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_UPTIME);
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_DEVST_HEARTBEAT);
|
||||
|
||||
if (profile.settings.ledPowerDisable != null) {
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_POWER_DISABLE);
|
||||
}
|
||||
if (profile.settings.ledStatusDisable != null) {
|
||||
addChannel(thing, add, true, CHGR_DEVST, CHANNEL_LED_STATUS_DISABLE); // WiFi status LED
|
||||
}
|
||||
return add;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-create relay channels depending on relay type/mode
|
||||
*
|
||||
* @return ArrayList<Channel> of channels to be added to the thing
|
||||
*/
|
||||
public static Map<String, Channel> createRelayChannels(final Thing thing, final ShellyDeviceProfile profile,
|
||||
final ShellyStatusRelay relay, int idx) {
|
||||
Map<String, Channel> add = new LinkedHashMap<>();
|
||||
String group = profile.getControlGroup(idx);
|
||||
|
||||
ShellySettingsRelay rs = profile.settings.relays.get(idx);
|
||||
addChannel(thing, add, rs.name != null, group, CHANNEL_OUTPUT_NAME);
|
||||
|
||||
// Shelly 1/1PM Addon
|
||||
if (relay.extTemperature != null) {
|
||||
addChannel(thing, add, relay.extTemperature.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP1);
|
||||
addChannel(thing, add, relay.extTemperature.sensor2 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP2);
|
||||
addChannel(thing, add, relay.extTemperature.sensor3 != null, CHGR_SENSOR, CHANNEL_ESENDOR_TEMP3);
|
||||
}
|
||||
if (relay.extHumidity != null) {
|
||||
addChannel(thing, add, relay.extHumidity.sensor1 != null, CHGR_SENSOR, CHANNEL_ESENDOR_HUMIDITY);
|
||||
}
|
||||
|
||||
return add;
|
||||
}
|
||||
|
||||
public static Map<String, Channel> createRollerChannels(Thing thing, final ShellyControlRoller roller) {
|
||||
Map<String, Channel> add = new LinkedHashMap<>();
|
||||
addChannel(thing, add, roller.state != null, CHGR_ROLLER, CHANNEL_ROL_CONTROL_STATE);
|
||||
return add;
|
||||
}
|
||||
|
||||
public static Map<String, Channel> createMeterChannels(Thing thing, final ShellySettingsMeter meter, String group) {
|
||||
Map<String, Channel> newChannels = new LinkedHashMap<>();
|
||||
addChannel(thing, newChannels, meter.power != null, group, CHANNEL_METER_CURRENTWATTS);
|
||||
addChannel(thing, newChannels, meter.total != null, group, CHANNEL_METER_TOTALKWH);
|
||||
if (meter.counters != null) {
|
||||
addChannel(thing, newChannels, meter.counters[0] != null, group, CHANNEL_METER_LASTMIN1);
|
||||
}
|
||||
addChannel(thing, newChannels, meter.timestamp != null, group, CHANNEL_LAST_UPDATE);
|
||||
return newChannels;
|
||||
}
|
||||
|
||||
public static Map<String, Channel> createEMeterChannels(final Thing thing, final ShellySettingsEMeter emeter,
|
||||
String group) {
|
||||
Map<String, Channel> newChannels = new LinkedHashMap<>();
|
||||
addChannel(thing, newChannels, emeter.power != null, group, CHANNEL_METER_CURRENTWATTS);
|
||||
addChannel(thing, newChannels, emeter.total != null, group, CHANNEL_METER_TOTALKWH);
|
||||
addChannel(thing, newChannels, emeter.totalReturned != null, group, CHANNEL_EMETER_TOTALRET);
|
||||
addChannel(thing, newChannels, emeter.reactive != null, group, CHANNEL_EMETER_REACTWATTS);
|
||||
addChannel(thing, newChannels, emeter.voltage != null, group, CHANNEL_EMETER_VOLTAGE);
|
||||
addChannel(thing, newChannels, emeter.current != null, group, CHANNEL_EMETER_CURRENT);
|
||||
addChannel(thing, newChannels, emeter.pf != null, group, CHANNEL_EMETER_PFACTOR);
|
||||
addChannel(thing, newChannels, true, group, CHANNEL_LAST_UPDATE);
|
||||
return newChannels;
|
||||
}
|
||||
|
||||
public static Map<String, Channel> createSensorChannels(final Thing thing, final ShellyDeviceProfile profile,
|
||||
final ShellyStatusSensor sdata) {
|
||||
Map<String, Channel> newChannels = new LinkedHashMap<>();
|
||||
|
||||
// Sensor data
|
||||
addChannel(thing, newChannels, sdata.tmp != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP);
|
||||
addChannel(thing, newChannels, sdata.hum != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM);
|
||||
addChannel(thing, newChannels, sdata.lux != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX);
|
||||
if (sdata.accel != null) {
|
||||
addChannel(thing, newChannels, sdata.accel.vibration != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_VIBRATION);
|
||||
addChannel(thing, newChannels, sdata.accel.tilt != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT);
|
||||
}
|
||||
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.lux != null && sdata.lux.illumination != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_ILLUM);
|
||||
addChannel(thing, newChannels, sdata.contact != null && sdata.contact.state != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_CONTACT);
|
||||
addChannel(thing, newChannels, sdata.motion != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION);
|
||||
addChannel(thing, newChannels, sdata.charger != null, CHGR_DEVST, CHANNEL_DEVST_CHARGER);
|
||||
addChannel(thing, newChannels, sdata.sensorError != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR);
|
||||
addChannel(thing, newChannels, sdata.actReasons != null, CHGR_DEVST, CHANNEL_DEVST_WAKEUP);
|
||||
|
||||
// Gas
|
||||
if (sdata.gasSensor != null) {
|
||||
addChannel(thing, newChannels, sdata.gasSensor.selfTestState != null, CHGR_DEVST, CHANNEL_DEVST_SELFTTEST);
|
||||
addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_SSTATE);
|
||||
addChannel(thing, newChannels, sdata.concentration != null && sdata.concentration.ppm != null,
|
||||
CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM);
|
||||
addChannel(thing, newChannels, sdata.valves != null, CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VALVE);
|
||||
addChannel(thing, newChannels, sdata.gasSensor.sensorState != null, CHANNEL_GROUP_SENSOR,
|
||||
CHANNEL_SENSOR_ALARM_STATE);
|
||||
}
|
||||
|
||||
// Battery
|
||||
if (sdata.bat != null) {
|
||||
addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LEVEL);
|
||||
addChannel(thing, newChannels, sdata.bat.value != null, CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW);
|
||||
}
|
||||
|
||||
addChannel(thing, newChannels, true, profile.getControlGroup(0), CHANNEL_LAST_UPDATE);
|
||||
return newChannels;
|
||||
}
|
||||
|
||||
private static void addChannel(Thing thing, Map<String, Channel> newChannels, boolean supported, String group,
|
||||
String channelName) throws IllegalArgumentException {
|
||||
if (supported) {
|
||||
final String channelId = group + "#" + channelName;
|
||||
final ShellyChannel channelDef = getDefinition(channelId);
|
||||
final ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
|
||||
final ChannelTypeUID channelTypeUID = channelDef.typeId.contains("system:")
|
||||
? new ChannelTypeUID(channelDef.typeId)
|
||||
: new ChannelTypeUID(BINDING_ID, channelDef.typeId);
|
||||
Channel channel = ChannelBuilder.create(channelUID, channelDef.itemType).withType(channelTypeUID).build();
|
||||
newChannels.put(channelId, channel);
|
||||
}
|
||||
}
|
||||
|
||||
public class ShellyChannel {
|
||||
private final ShellyTranslationProvider messages;
|
||||
public String group = "";
|
||||
public String groupLabel = "";
|
||||
public String groupDescription = "";
|
||||
|
||||
public String channel = "";
|
||||
public String label = "";
|
||||
public String description = "";
|
||||
public String itemType = "";
|
||||
public String typeId = "";
|
||||
public String category = "";
|
||||
public Set<String> tags = new HashSet<>();
|
||||
|
||||
public ShellyChannel(ShellyTranslationProvider messages, String group, String channel, String typeId,
|
||||
String itemType, String... category) {
|
||||
this.messages = messages;
|
||||
this.group = group;
|
||||
this.channel = channel;
|
||||
this.itemType = itemType;
|
||||
this.typeId = typeId;
|
||||
|
||||
groupLabel = getText(PREFIX_GROUP + group + SUFFIX_LABEL);
|
||||
groupDescription = getText(PREFIX_GROUP + group + SUFFIX_DESCR);
|
||||
label = getText(PREFIX_CHANNEL + channel + SUFFIX_LABEL);
|
||||
description = getText(PREFIX_CHANNEL + channel + SUFFIX_DESCR);
|
||||
}
|
||||
|
||||
public String getChanneId() {
|
||||
return group + "#" + channel;
|
||||
}
|
||||
|
||||
private String getText(String key) {
|
||||
String text = messages.get(key);
|
||||
return text != null ? text : "";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChannelMap {
|
||||
private final Map<String, ShellyChannel> map = new LinkedHashMap<>();
|
||||
|
||||
private ChannelMap add(ShellyChannel def) {
|
||||
map.put(def.getChanneId(), def);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShellyChannel get(String channelName) throws IllegalArgumentException {
|
||||
ShellyChannel def = null;
|
||||
if (channelName.contains("#")) {
|
||||
def = map.get(channelName);
|
||||
}
|
||||
for (HashMap.Entry<String, ShellyChannel> entry : map.entrySet()) {
|
||||
if (entry.getValue().channel.contains("#" + channelName)) {
|
||||
def = entry.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (def == null) {
|
||||
throw new IllegalArgumentException("Channel definition for " + channelName + " not found!");
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
|
||||
/**
|
||||
* The {@link ShellyColorUtils} provides some utility functions around RGBW handling.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyColorUtils {
|
||||
OnOffType power = OnOffType.OFF;
|
||||
String mode = "";
|
||||
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
int white = 0;
|
||||
PercentType percentRed = new PercentType(0);
|
||||
PercentType percentGreen = new PercentType(0);
|
||||
PercentType percentBlue = new PercentType(0);
|
||||
PercentType percentWhite = new PercentType(0);
|
||||
|
||||
int gain = 0;
|
||||
int brightness = 0;
|
||||
int temp = 0;
|
||||
int minTemp = 0;
|
||||
int maxTemp = 0;
|
||||
PercentType percentGain = new PercentType(0);
|
||||
PercentType percentBrightness = new PercentType(0);
|
||||
PercentType percentTemp = new PercentType(0);
|
||||
Integer effect = 0;
|
||||
|
||||
public ShellyColorUtils() {
|
||||
}
|
||||
|
||||
public ShellyColorUtils(ShellyColorUtils col) {
|
||||
minTemp = col.minTemp;
|
||||
maxTemp = col.maxTemp;
|
||||
setRed(col.red);
|
||||
setGreen(col.green);
|
||||
setBlue(col.blue);
|
||||
setWhite(col.white);
|
||||
setGain(col.gain);
|
||||
setBrightness(col.brightness);
|
||||
setTemp(col.temp);
|
||||
}
|
||||
|
||||
void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
void setMinMaxTemp(int min, int max) {
|
||||
minTemp = min;
|
||||
maxTemp = max;
|
||||
}
|
||||
|
||||
boolean setRGBW(int red, int green, int blue, int white) {
|
||||
setRed(red);
|
||||
setGreen(green);
|
||||
setBlue(blue);
|
||||
setWhite(white);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean setRed(int value) {
|
||||
boolean changed = red != value;
|
||||
red = value;
|
||||
percentRed = toPercent(red);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setGreen(int value) {
|
||||
boolean changed = green != value;
|
||||
green = value;
|
||||
percentGreen = toPercent(green);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setBlue(int value) {
|
||||
boolean changed = blue != value;
|
||||
blue = value;
|
||||
percentBlue = toPercent(blue);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setWhite(int value) {
|
||||
boolean changed = white != value;
|
||||
white = value;
|
||||
percentWhite = toPercent(white);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setBrightness(int value) {
|
||||
boolean changed = brightness != value;
|
||||
brightness = value;
|
||||
percentBrightness = toPercent(brightness, SHELLY_MIN_BRIGHTNESS, SHELLY_MAX_BRIGHTNESS);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setGain(int value) {
|
||||
boolean changed = gain != value;
|
||||
gain = value;
|
||||
percentGain = toPercent(gain, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setTemp(int value) {
|
||||
boolean changed = temp != value;
|
||||
temp = value;
|
||||
percentTemp = toPercent(temp, minTemp, maxTemp);
|
||||
return changed;
|
||||
}
|
||||
|
||||
boolean setEffect(int value) {
|
||||
boolean changed = effect != value;
|
||||
effect = value;
|
||||
return changed;
|
||||
}
|
||||
|
||||
public HSBType toHSB() {
|
||||
return HSBType.fromRGB(red, green, blue);
|
||||
}
|
||||
|
||||
public Integer[] fromRGBW(String rgbwString) {
|
||||
Integer values[] = new Integer[4];
|
||||
values[0] = values[1] = values[2] = values[3] = -1;
|
||||
try {
|
||||
String rgbw[] = rgbwString.split(",");
|
||||
for (int i = 0; i < rgbw.length; i++) {
|
||||
values[i] = Integer.parseInt(rgbw[i]);
|
||||
}
|
||||
} catch (NullPointerException e) { // might be a format problem
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to convert fullColor value: " + rgbwString + ", " + e.getMessage());
|
||||
}
|
||||
if (values[0] != -1) {
|
||||
setRed(values[0]);
|
||||
}
|
||||
if (values[1] != -1) {
|
||||
setGreen(values[1]);
|
||||
}
|
||||
if (values[2] != -1) {
|
||||
setBlue(values[2]);
|
||||
}
|
||||
if (values[3] != -1) {
|
||||
setWhite(values[3]);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public static PercentType toPercent(Integer value) {
|
||||
return toPercent(value, 0, SHELLY_MAX_COLOR);
|
||||
}
|
||||
|
||||
public static PercentType toPercent(Integer _value, Integer min, Integer max) {
|
||||
Double range = max.doubleValue() - min.doubleValue();
|
||||
Double value = _value.doubleValue();
|
||||
value = value < min ? min.doubleValue() : value;
|
||||
value = value > max ? max.doubleValue() : value;
|
||||
Double percent = 0.0;
|
||||
if (range > 0) {
|
||||
percent = new Double(Math.round((value - min) / range * 100));
|
||||
}
|
||||
return new PercentType(new BigDecimal(percent));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsEMeter;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsMeter;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusSensor;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
|
||||
import tec.uom.se.unit.Units;
|
||||
|
||||
/***
|
||||
* The{@link ShellyComponents} implements updates for supplemental components
|
||||
* Meter will be used by Relay + Light; Sensor is part of H&T, Flood, Door Window, Sense
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyComponents {
|
||||
|
||||
/**
|
||||
* Update device status
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
*/
|
||||
public static boolean updateDeviceStatus(ShellyBaseHandler thingHandler, ShellySettingsStatus status) {
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
|
||||
.createDeviceChannels(thingHandler.getThing(), thingHandler.getProfile(), status));
|
||||
}
|
||||
|
||||
Integer rssi = getInteger(status.wifiSta.rssi);
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPTIME,
|
||||
toQuantityType(new Double(getLong(status.uptime)), DIGITS_NONE, SmartHomeUnits.SECOND));
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_RSSI, mapSignalStrength(rssi));
|
||||
if (status.tmp != null) {
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(getDouble(status.tmp.tC), DIGITS_NONE, SIUnits.CELSIUS));
|
||||
} else if (status.temperature != null) {
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ITEMP,
|
||||
toQuantityType(getDouble(status.temperature), DIGITS_NONE, SIUnits.CELSIUS));
|
||||
}
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_UPDATE, getOnOff(status.hasUpdate));
|
||||
|
||||
return false; // device status never triggers update
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Meter channel
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*/
|
||||
public static boolean updateMeters(ShellyBaseHandler thingHandler, ShellySettingsStatus status) {
|
||||
ShellyDeviceProfile profile = thingHandler.getProfile();
|
||||
|
||||
double accumulatedWatts = 0.0;
|
||||
double accumulatedTotal = 0.0;
|
||||
double accumulatedReturned = 0.0;
|
||||
|
||||
boolean updated = false;
|
||||
// Devices without power meters get no updates
|
||||
// We need to differ
|
||||
// Roler+RGBW2 have multiple meters -> aggregate consumption to the functional device
|
||||
// Meter and EMeter have a different set of channels
|
||||
if ((profile.numMeters > 0) && ((status.meters != null) || (status.emeters != null))) {
|
||||
if (!profile.isRoller && !profile.isRGBW2) {
|
||||
thingHandler.logger.trace("{}: Updating {} {}meter(s)", thingHandler.thingName, profile.numMeters,
|
||||
!profile.isEMeter ? "standard " : "e-");
|
||||
|
||||
// In Relay mode we map eacher meter to the matching channel group
|
||||
int m = 0;
|
||||
if (!profile.isEMeter) {
|
||||
for (ShellySettingsMeter meter : status.meters) {
|
||||
Integer meterIndex = m + 1;
|
||||
if (getBool(meter.isValid) || profile.isLight) { // RGBW2-white doesn't report valid flag
|
||||
// correctly in white mode
|
||||
String groupName = "";
|
||||
if (profile.numMeters > 1) {
|
||||
groupName = CHANNEL_GROUP_METER + meterIndex.toString();
|
||||
} else {
|
||||
groupName = CHANNEL_GROUP_METER;
|
||||
}
|
||||
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
// skip for Shelly Bulb: JSON has a meter, but values don't get updated
|
||||
if (!profile.isBulb) {
|
||||
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
|
||||
.createMeterChannels(thingHandler.getThing(), meter, groupName));
|
||||
}
|
||||
}
|
||||
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(getDouble(meter.power), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
accumulatedWatts += getDouble(meter.power);
|
||||
|
||||
// convert Watt/Min to kw/h
|
||||
if (meter.total != null) {
|
||||
double kwh = getDouble(meter.total) / 60 / 1000;
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(kwh, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
accumulatedTotal += kwh;
|
||||
}
|
||||
if (meter.counters != null) {
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
|
||||
toQuantityType(getDouble(meter.counters[0]), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
}
|
||||
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
|
||||
getTimestamp(getString(profile.settings.timezone), getLong(meter.timestamp)));
|
||||
}
|
||||
m++;
|
||||
}
|
||||
} else {
|
||||
for (ShellySettingsEMeter emeter : status.emeters) {
|
||||
Integer meterIndex = m + 1;
|
||||
if (getBool(emeter.isValid)) {
|
||||
String groupName = profile.numMeters > 1 ? CHANNEL_GROUP_METER + meterIndex.toString()
|
||||
: CHANNEL_GROUP_METER;
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
|
||||
.createEMeterChannels(thingHandler.getThing(), emeter, groupName));
|
||||
}
|
||||
|
||||
// convert Watt/Hour tok w/h
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(getDouble(emeter.power), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH, toQuantityType(
|
||||
getDouble(emeter.total) / 1000, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_TOTALRET, toQuantityType(
|
||||
getDouble(emeter.totalReturned) / 1000, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_REACTWATTS,
|
||||
toQuantityType(getDouble(emeter.reactive), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_VOLTAGE,
|
||||
toQuantityType(getDouble(emeter.voltage), DIGITS_VOLT, SmartHomeUnits.VOLT));
|
||||
|
||||
if (emeter.current != null) {
|
||||
// Shelly
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_CURRENT,
|
||||
toQuantityType(getDouble(emeter.current), DIGITS_VOLT, SmartHomeUnits.AMPERE));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_EMETER_PFACTOR,
|
||||
getDecimal(emeter.pf));
|
||||
}
|
||||
|
||||
accumulatedWatts += getDouble(emeter.power);
|
||||
accumulatedTotal += getDouble(emeter.total) / 1000;
|
||||
accumulatedReturned += getDouble(emeter.totalReturned) / 1000;
|
||||
if (updated) {
|
||||
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
}
|
||||
}
|
||||
m++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In Roller Mode we accumulate all meters to a single set of meters
|
||||
thingHandler.logger.trace("{}: Updating Meter (accumulated)", thingHandler.thingName);
|
||||
double currentWatts = 0.0;
|
||||
double totalWatts = 0.0;
|
||||
double lastMin1 = 0.0;
|
||||
long timestamp = 0l;
|
||||
String groupName = CHANNEL_GROUP_METER;
|
||||
for (ShellySettingsMeter meter : status.meters) {
|
||||
if (meter.isValid) {
|
||||
currentWatts += getDouble(meter.power);
|
||||
totalWatts += getDouble(meter.total);
|
||||
if (meter.counters != null) {
|
||||
lastMin1 += getDouble(meter.counters[0]);
|
||||
}
|
||||
if (getLong(meter.timestamp) > timestamp) {
|
||||
timestamp = getLong(meter.timestamp); // newest one
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create channels for 1 Meter
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.updateChannelDefinitions(ShellyChannelDefinitionsDTO
|
||||
.createMeterChannels(thingHandler.getThing(), status.meters.get(0), groupName));
|
||||
}
|
||||
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_LASTMIN1,
|
||||
toQuantityType(getDouble(lastMin1), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
|
||||
// convert totalWatts into kw/h
|
||||
totalWatts = totalWatts / (60.0 * 10000.0);
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_CURRENTWATTS,
|
||||
toQuantityType(getDouble(currentWatts), DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
updated |= thingHandler.updateChannel(groupName, CHANNEL_METER_TOTALKWH,
|
||||
toQuantityType(getDouble(totalWatts), DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
|
||||
if (updated) {
|
||||
thingHandler.updateChannel(groupName, CHANNEL_LAST_UPDATE,
|
||||
getTimestamp(getString(profile.settings.timezone), timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
if (updated && !profile.isRoller && !profile.isRGBW2) {
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUWATTS,
|
||||
toQuantityType(accumulatedWatts, DIGITS_WATT, SmartHomeUnits.WATT));
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCUTOTAL,
|
||||
toQuantityType(accumulatedTotal, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_ACCURETURNED,
|
||||
toQuantityType(accumulatedReturned, DIGITS_KWH, SmartHomeUnits.KILOWATT_HOUR));
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Sensor channel
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static boolean updateSensors(ShellyBaseHandler thingHandler, ShellySettingsStatus status)
|
||||
throws ShellyApiException {
|
||||
ShellyDeviceProfile profile = thingHandler.getProfile();
|
||||
|
||||
boolean updated = false;
|
||||
if (profile.isSensor || profile.hasBattery || profile.isSense) {
|
||||
ShellyStatusSensor sdata = thingHandler.api.getSensorStatus();
|
||||
|
||||
if (!thingHandler.areChannelsCreated()) {
|
||||
thingHandler.logger.trace("{}: Create missing sensor channel(s)", thingHandler.thingName);
|
||||
thingHandler.updateChannelDefinitions(
|
||||
ShellyChannelDefinitionsDTO.createSensorChannels(thingHandler.getThing(), profile, sdata));
|
||||
}
|
||||
|
||||
updated |= thingHandler.updateWakeupReason(sdata.actReasons);
|
||||
|
||||
if ((sdata.contact != null) && sdata.contact.isValid) {
|
||||
// Shelly DW: “sensor”:{“state”:“open”, “is_valid”:true},
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_CONTACT,
|
||||
getString(sdata.contact.state).equalsIgnoreCase(SHELLY_API_DWSTATE_OPEN) ? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ERROR,
|
||||
getStringType(sdata.sensorError));
|
||||
if (changed) {
|
||||
thingHandler.postEvent(sdata.sensorError, true);
|
||||
}
|
||||
updated |= changed;
|
||||
}
|
||||
if ((sdata.tmp != null) && getBool(sdata.tmp.isValid)) {
|
||||
thingHandler.logger.trace("{}: Updating temperature", thingHandler.thingName);
|
||||
Double temp = getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_CELSIUS)
|
||||
? getDouble(sdata.tmp.tC)
|
||||
: getDouble(sdata.tmp.tF);
|
||||
if (getString(sdata.tmp.units).toUpperCase().equals(SHELLY_TEMP_FAHRENHEIT)) {
|
||||
// convert Fahrenheit to Celsius
|
||||
temp = ImperialUnits.FAHRENHEIT.getConverterTo(Units.CELSIUS).convert(temp).doubleValue();
|
||||
}
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TEMP,
|
||||
toQuantityType(temp.doubleValue(), DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
}
|
||||
if (sdata.hum != null) {
|
||||
thingHandler.logger.trace("{}: Updating humidity", thingHandler.thingName);
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM,
|
||||
toQuantityType(getDouble(sdata.hum.value), DIGITS_PERCENT, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
if ((sdata.lux != null) && getBool(sdata.lux.isValid)) {
|
||||
// “lux”:{“value”:30, “illumination”: “dark”, “is_valid”:true},
|
||||
thingHandler.logger.trace("{}: Updating lux", thingHandler.thingName);
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_LUX,
|
||||
toQuantityType(getDouble(sdata.lux.value), DIGITS_LUX, SmartHomeUnits.LUX));
|
||||
if (sdata.lux.illumination != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ILLUM,
|
||||
getStringType(sdata.lux.illumination));
|
||||
}
|
||||
}
|
||||
if (sdata.accel != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_TILT, toQuantityType(
|
||||
getDouble(sdata.accel.tilt.doubleValue()), DIGITS_NONE, SmartHomeUnits.DEGREE_ANGLE));
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_VIBRATION,
|
||||
getInteger(sdata.accel.vibration) == 1 ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
if (sdata.flood != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_FLOOD,
|
||||
getOnOff(sdata.flood));
|
||||
}
|
||||
if (sdata.smoke != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SMOKE,
|
||||
getOnOff(sdata.smoke));
|
||||
}
|
||||
if (sdata.gasSensor != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_SELFTTEST,
|
||||
getStringType(sdata.gasSensor.selfTestState));
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_ALARM_STATE,
|
||||
getStringType(sdata.gasSensor.alarmState));
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_SSTATE,
|
||||
getStringType(sdata.gasSensor.sensorState));
|
||||
}
|
||||
if ((sdata.concentration != null) && sdata.concentration.isValid) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_PPM,
|
||||
getDecimal(sdata.concentration.ppm));
|
||||
}
|
||||
|
||||
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, SmartHomeUnits.PERCENT));
|
||||
boolean changed = thingHandler.updateChannel(CHANNEL_GROUP_BATTERY, CHANNEL_SENSOR_BAT_LOW,
|
||||
getDouble(sdata.bat.value) < thingHandler.config.lowBattery ? OnOffType.ON : OnOffType.OFF);
|
||||
updated |= changed;
|
||||
if (changed && getDouble(sdata.bat.value) < thingHandler.config.lowBattery) {
|
||||
thingHandler.postEvent(ALARM_TYPE_LOW_BATTERY, false);
|
||||
}
|
||||
}
|
||||
if (sdata.motion != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_MOTION,
|
||||
getOnOff(sdata.motion));
|
||||
}
|
||||
if (sdata.charger != null) {
|
||||
updated |= thingHandler.updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_DEVST_CHARGER,
|
||||
getOnOff(sdata.charger));
|
||||
}
|
||||
|
||||
updated |= thingHandler.updateInputs(status);
|
||||
|
||||
if (updated) {
|
||||
thingHandler.updateChannel(profile.getControlGroup(0), CHANNEL_LAST_UPDATE, getTimestamp());
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.shelly.internal.handler;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link DeviceUpdateListener} can register on the {@link TradfriGatewayHandler} to be informed about details about
|
||||
* devices.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ShellyDeviceListener {
|
||||
|
||||
/**
|
||||
* This method is called when new device information is received.
|
||||
*/
|
||||
public boolean onEvent(String ipAddress, String deviceName, String deviceIndex, String eventType,
|
||||
Map<String, String> parameters);
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLight;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLightChannel;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyLightHandler} handles light (Bulb, Duo and RGBW2) specific commands and status. All other commands
|
||||
* will be routet of the ShellyBaseHandler.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyLightHandler extends ShellyBaseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyLightHandler.class);
|
||||
private final Map<Integer, ShellyColorUtils> channelColors;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param thing The thing passed by the HandlerFactory
|
||||
* @param bindingConfig configuration of the binding
|
||||
* @param coapServer coap server instance
|
||||
* @param localIP local IP of the openHAB host
|
||||
* @param httpPort port of the openHAB HTTP API
|
||||
*/
|
||||
public ShellyLightHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
|
||||
final ShellyBindingConfiguration bindingConfig, final ShellyCoapServer coapServer, final String localIP,
|
||||
int httpPort, final HttpClient httpClient) {
|
||||
super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
channelColors = new TreeMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Thing is using {}", this.getClass());
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleDeviceCommand(ChannelUID channelUID, Command command) throws IllegalArgumentException {
|
||||
String groupName = getString(channelUID.getGroupId());
|
||||
if (groupName.isEmpty()) {
|
||||
throw new IllegalArgumentException("Empty groupName");
|
||||
}
|
||||
|
||||
int lightId = getLightIdFromGroup(groupName);
|
||||
logger.trace("{}: Execute command {} on channel {}, lightId={}", thingName, command, channelUID.getAsString(),
|
||||
lightId);
|
||||
|
||||
try {
|
||||
ShellyColorUtils oldCol = getCurrentColors(lightId);
|
||||
oldCol.mode = profile.mode;
|
||||
ShellyColorUtils col = new ShellyColorUtils(oldCol);
|
||||
|
||||
boolean update = true;
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
default: // non-bulb commands will be handled by the generic handler
|
||||
return false;
|
||||
|
||||
case CHANNEL_LIGHT_POWER:
|
||||
logger.debug("{}: Switch light {}", thingName, command);
|
||||
api.setLightParm(lightId, SHELLY_LIGHT_TURN,
|
||||
command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
col.power = (OnOffType) command;
|
||||
requestUpdates(1, false);
|
||||
update = false;
|
||||
break;
|
||||
case CHANNEL_LIGHT_COLOR_MODE:
|
||||
logger.debug("{}: Select color mode {}", thingName, command);
|
||||
col.setMode((OnOffType) command == OnOffType.ON ? SHELLY_MODE_COLOR : SHELLY_MODE_WHITE);
|
||||
break;
|
||||
case CHANNEL_COLOR_PICKER:
|
||||
logger.debug("{}: Update colors from color picker", thingName);
|
||||
update = handleColorPicker(profile, lightId, col, command);
|
||||
break;
|
||||
case CHANNEL_COLOR_FULL:
|
||||
logger.debug("{}: Set colors to {}", thingName, command);
|
||||
handleFullColor(col, command);
|
||||
break;
|
||||
case CHANNEL_COLOR_RED:
|
||||
col.setRed(setColor(lightId, SHELLY_COLOR_RED, command, SHELLY_MAX_COLOR));
|
||||
break;
|
||||
case CHANNEL_COLOR_GREEN:
|
||||
col.setGreen(setColor(lightId, SHELLY_COLOR_GREEN, command, SHELLY_MAX_COLOR));
|
||||
break;
|
||||
case CHANNEL_COLOR_BLUE:
|
||||
col.setBlue(setColor(lightId, SHELLY_COLOR_BLUE, command, SHELLY_MAX_COLOR));
|
||||
break;
|
||||
case CHANNEL_COLOR_WHITE:
|
||||
col.setWhite(setColor(lightId, SHELLY_COLOR_WHITE, command, SHELLY_MAX_COLOR));
|
||||
break;
|
||||
case CHANNEL_COLOR_GAIN:
|
||||
col.setGain(setColor(lightId, SHELLY_COLOR_GAIN, command, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN));
|
||||
break;
|
||||
case CHANNEL_BRIGHTNESS: // only in white mode
|
||||
if (profile.inColor && !profile.isBulb) {
|
||||
logger.debug("{}: Not in white mode, brightness not available", thingName);
|
||||
break;
|
||||
}
|
||||
|
||||
int value = -1;
|
||||
if (command instanceof OnOffType) { // Switch
|
||||
logger.debug("{}: Switch light {}", thingName, command);
|
||||
ShellyShortLightStatus light = api.setRelayTurn(lightId,
|
||||
command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
col.power = getOnOff(light.ison);
|
||||
col.setBrightness(light.brightness);
|
||||
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", col.power);
|
||||
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
|
||||
toQuantityType(new Double(col.power == OnOffType.ON ? col.brightness : 0), DIGITS_NONE,
|
||||
SmartHomeUnits.PERCENT));
|
||||
update = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (command instanceof PercentType) {
|
||||
Float percent = ((PercentType) command).floatValue();
|
||||
value = percent.intValue(); // 0..100% = 0..100
|
||||
logger.debug("{}: Set brightness to {}%/{}", thingName, percent, value);
|
||||
} else if (command instanceof DecimalType) {
|
||||
value = ((DecimalType) command).intValue();
|
||||
logger.debug("{}: Set brightness to {} (Integer)", thingName, value);
|
||||
}
|
||||
if (value == 0) {
|
||||
logger.debug("{}: Brightness=0 -> switch light OFF", thingName);
|
||||
api.setRelayTurn(lightId, SHELLY_API_OFF);
|
||||
update = false;
|
||||
} else {
|
||||
if (command instanceof IncreaseDecreaseType) {
|
||||
ShellyShortLightStatus light = api.getLightStatus(lightId);
|
||||
if (((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
|
||||
value = Math.min(light.brightness + DIM_STEPSIZE, 100);
|
||||
} else {
|
||||
value = Math.max(light.brightness - DIM_STEPSIZE, 0);
|
||||
}
|
||||
logger.trace("{}: Change brightness from {} to {}", thingName, light.brightness, value);
|
||||
}
|
||||
|
||||
validateRange("brightness", value, 0, 100);
|
||||
logger.debug("{}: Changing brightness from {} to {}", thingName, oldCol.brightness, value);
|
||||
col.setBrightness(value);
|
||||
}
|
||||
updateChannel(CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_LIGHT_POWER,
|
||||
value > 0 ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
|
||||
case CHANNEL_COLOR_TEMP:
|
||||
Integer temp = -1;
|
||||
if (command instanceof PercentType) {
|
||||
logger.debug("{}: Set color temp to {}%", thingName, ((PercentType) command).floatValue());
|
||||
Float percent = ((PercentType) command).floatValue() / 100;
|
||||
temp = new DecimalType(col.minTemp + ((col.maxTemp - col.minTemp)) * percent).intValue();
|
||||
logger.debug("{}: Converted color-temp {}% to {}K (from Percent to Integer)", thingName,
|
||||
percent, temp);
|
||||
} else if (command instanceof DecimalType) {
|
||||
temp = ((DecimalType) command).intValue();
|
||||
logger.debug("{}: Set color temp to {}K (Integer)", thingName, temp);
|
||||
}
|
||||
validateRange(CHANNEL_COLOR_TEMP, temp, col.minTemp, col.maxTemp);
|
||||
col.setTemp(temp);
|
||||
col.brightness = -1;
|
||||
break;
|
||||
|
||||
case CHANNEL_COLOR_EFFECT:
|
||||
Integer effect = ((DecimalType) command).intValue();
|
||||
logger.debug("{}: Set color effect to {}", thingName, effect);
|
||||
validateRange("effect", effect, SHELLY_MIN_EFFECT, SHELLY_MAX_EFFECT);
|
||||
col.setEffect(effect.intValue());
|
||||
}
|
||||
|
||||
if (update) {
|
||||
// check for switching color mode
|
||||
if (profile.isBulb && !col.mode.isEmpty() && !col.mode.equals(oldCol.mode)) {
|
||||
logger.debug("{}: Color mode changed from {} to {}, set new mode", thingName, oldCol.mode,
|
||||
col.mode);
|
||||
api.setLightMode(col.mode);
|
||||
}
|
||||
|
||||
// send changed colors to the device
|
||||
sendColors(profile, lightId, oldCol, col, config.brightnessAutoOn);
|
||||
}
|
||||
return true;
|
||||
} catch (ShellyApiException e) {
|
||||
logger.debug("{}: Unable to handle command: {}", thingName, e.toString());
|
||||
return false;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("{}: Unable to handle command", thingName, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleColorPicker(ShellyDeviceProfile profile, Integer lightId, ShellyColorUtils col,
|
||||
Command command) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
if (command instanceof HSBType) {
|
||||
HSBType hsb = (HSBType) command;
|
||||
|
||||
logger.debug("HSB-Info={}, Hue={}, getRGB={}, toRGB={}/{}/{}", hsb, hsb.getHue(),
|
||||
String.format("0x%08X", hsb.getRGB()), hsb.toRGB()[0], hsb.toRGB()[1], hsb.toRGB()[2]);
|
||||
if (hsb.toString().contains("360,")) {
|
||||
logger.trace("{}: need to fix the Hue value (360->0)", thingName);
|
||||
HSBType fixHue = new HSBType(new DecimalType(0), hsb.getSaturation(), hsb.getBrightness());
|
||||
hsb = fixHue;
|
||||
}
|
||||
|
||||
col.setRed(getColorFromHSB(hsb.getRed()));
|
||||
col.setBlue(getColorFromHSB(hsb.getBlue()));
|
||||
col.setGreen(getColorFromHSB(hsb.getGreen()));
|
||||
col.setBrightness(getColorFromHSB(hsb.getBrightness(), BRIGHTNESS_FACTOR));
|
||||
// white, gain and temp are not part of the HSB color scheme
|
||||
updated = true;
|
||||
} else if (command instanceof PercentType) {
|
||||
if (!profile.inColor || profile.isBulb) {
|
||||
col.brightness = SHELLY_MAX_BRIGHTNESS * ((PercentType) command).intValue();
|
||||
updated = true;
|
||||
}
|
||||
} else if (command instanceof OnOffType) {
|
||||
logger.debug("{}: Switch light {}", thingName, command);
|
||||
api.setLightParm(lightId, SHELLY_LIGHT_TURN,
|
||||
(OnOffType) command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
col.power = (OnOffType) command;
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
if (!profile.inColor || profile.isBulb) {
|
||||
logger.debug("{}: {} brightness by {}", thingName, command, SHELLY_DIM_STEPSIZE);
|
||||
PercentType percent = (PercentType) super.getChannelValue(CHANNEL_GROUP_COLOR_CONTROL,
|
||||
CHANNEL_BRIGHTNESS);
|
||||
if (percent != null) {
|
||||
int currentBrightness = percent.intValue() * SHELLY_MAX_BRIGHTNESS;
|
||||
int newBrightness = currentBrightness;
|
||||
if (command == IncreaseDecreaseType.DECREASE) {
|
||||
newBrightness = Math.max(currentBrightness - SHELLY_DIM_STEPSIZE, 0);
|
||||
} else {
|
||||
newBrightness = Math.min(currentBrightness + SHELLY_DIM_STEPSIZE, SHELLY_MAX_BRIGHTNESS);
|
||||
}
|
||||
col.brightness = newBrightness;
|
||||
updated = currentBrightness != newBrightness;
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
private boolean handleFullColor(ShellyColorUtils col, Command command) throws IllegalArgumentException {
|
||||
String color = command.toString().toLowerCase();
|
||||
if (color.contains(",")) {
|
||||
col.fromRGBW(color);
|
||||
} else if (color.equals(SHELLY_COLOR_RED)) {
|
||||
col.setRGBW(SHELLY_MAX_COLOR, 0, 0, 0);
|
||||
} else if (color.equals(SHELLY_COLOR_GREEN)) {
|
||||
col.setRGBW(0, SHELLY_MAX_COLOR, 0, 0);
|
||||
} else if (color.equals(SHELLY_COLOR_BLUE)) {
|
||||
col.setRGBW(0, 0, SHELLY_MAX_COLOR, 0);
|
||||
} else if (color.equals(SHELLY_COLOR_YELLOW)) {
|
||||
col.setRGBW(SHELLY_MAX_COLOR, SHELLY_MAX_COLOR, 0, 0);
|
||||
} else if (color.equals(SHELLY_COLOR_WHITE)) {
|
||||
col.setRGBW(0, 0, 0, SHELLY_MAX_COLOR);
|
||||
col.setMode(SHELLY_MODE_WHITE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid full color selection: " + color);
|
||||
}
|
||||
col.setMode(color.equals(SHELLY_MODE_WHITE) ? SHELLY_MODE_WHITE : SHELLY_MODE_COLOR);
|
||||
return true;
|
||||
}
|
||||
|
||||
private ShellyColorUtils getCurrentColors(int lightId) {
|
||||
ShellyColorUtils col;
|
||||
if (!channelColors.containsKey(lightId)) {
|
||||
col = new ShellyColorUtils(); // create a new entry
|
||||
col.setMinMaxTemp(profile.minTemp, profile.maxTemp);
|
||||
channelColors.put(lightId, col);
|
||||
logger.trace("{}: Colors entry created for lightId {}", thingName, lightId);
|
||||
} else {
|
||||
col = channelColors.get(lightId);
|
||||
logger.trace(
|
||||
"{}: Colors loaded for lightId {}: power={}, RGBW={}/{}/{}/{}, gain={}, brightness={}, color temp={} (min={}, max={}",
|
||||
thingName, lightId, col.power, col.red, col.green, col.blue, col.white, col.gain, col.brightness,
|
||||
col.temp, col.minTemp, col.maxTemp);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDeviceStatus(ShellySettingsStatus genericStatus) throws ShellyApiException {
|
||||
if (!profile.isInitialized()) {
|
||||
logger.debug("{}: Device not yet initialized!", thingName);
|
||||
return false;
|
||||
}
|
||||
if (!profile.isLight) {
|
||||
logger.debug("{}: ERROR: Device is not a light. but class ShellyHandlerLight is called!", thingName);
|
||||
}
|
||||
|
||||
ShellyStatusLight status = api.getLightStatus();
|
||||
logger.trace("{}: Updating light status in {} mode, {} channel(s)", thingName, profile.mode,
|
||||
status.lights.size());
|
||||
|
||||
// In white mode we have multiple channels
|
||||
int lightId = 0;
|
||||
boolean updated = false;
|
||||
for (ShellyStatusLightChannel light : status.lights) {
|
||||
Integer channelId = lightId + 1;
|
||||
String controlGroup = buildControlGroupName(profile, channelId);
|
||||
// The bulb has a combined channel set for color or white mode
|
||||
// The RGBW2 uses 2 different thing types: color=1 channel, white=4 channel
|
||||
if (profile.isBulb) {
|
||||
updateChannel(CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_LIGHT_COLOR_MODE, getOnOff(profile.inColor));
|
||||
}
|
||||
|
||||
ShellyColorUtils col = getCurrentColors(lightId);
|
||||
col.power = getOnOff(light.ison);
|
||||
|
||||
// Channel control/timer
|
||||
// ShellyStatusLightChannel light = status.lights.get(i);
|
||||
updated |= updateChannel(controlGroup, CHANNEL_LIGHT_POWER, col.power);
|
||||
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(light.autoOn));
|
||||
updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(light.autoOff));
|
||||
updated |= updateInputs(controlGroup, genericStatus, lightId);
|
||||
if (getBool(light.overpower)) {
|
||||
postEvent(ALARM_TYPE_OVERPOWER, false);
|
||||
}
|
||||
|
||||
if (profile.inColor) {
|
||||
logger.trace("{}: update color settings", thingName);
|
||||
col.setRGBW(getInteger(light.red), getInteger(light.green), getInteger(light.blue),
|
||||
getInteger(light.white));
|
||||
col.setGain(getInteger(light.gain));
|
||||
col.setEffect(getInteger(light.effect));
|
||||
|
||||
String colorGroup = CHANNEL_GROUP_COLOR_CONTROL;
|
||||
logger.trace("{}: Update channels for group {}: RGBW={}/{}/{}, in %:{}%/{}%/{}%, white={}%, gain={}%",
|
||||
thingName, colorGroup, col.red, col.green, col.blue, col.percentRed, col.percentGreen,
|
||||
col.percentBlue, col.percentWhite, col.percentGain);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_RED, col.percentRed);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_GREEN, col.percentGreen);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_BLUE, col.percentBlue);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_WHITE, col.percentWhite);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_GAIN, col.percentGain);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_EFFECT, new DecimalType(col.effect));
|
||||
setFullColor(colorGroup, col);
|
||||
|
||||
logger.trace("{}: update {}.color picker", thingName, colorGroup);
|
||||
updated |= updateChannel(colorGroup, CHANNEL_COLOR_PICKER, col.toHSB());
|
||||
}
|
||||
|
||||
if (!profile.inColor || profile.isBulb) {
|
||||
String whiteGroup = buildWhiteGroupName(profile, channelId);
|
||||
col.setBrightness(getInteger(light.brightness));
|
||||
updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Switch", col.power);
|
||||
updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Value",
|
||||
toQuantityType(col.power == OnOffType.ON ? col.percentBrightness.doubleValue() : new Double(0),
|
||||
DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
|
||||
if ((profile.isBulb || profile.isDuo) && (light.temp != null)) {
|
||||
col.setTemp(getInteger(light.temp));
|
||||
updated |= updateChannel(whiteGroup, CHANNEL_COLOR_TEMP, col.percentTemp);
|
||||
logger.trace("{}: update {}.color picker", thingName, whiteGroup);
|
||||
updated |= updateChannel(whiteGroup, CHANNEL_COLOR_PICKER, col.toHSB());
|
||||
}
|
||||
}
|
||||
|
||||
// continue with next light
|
||||
lightId++;
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
private Integer setColor(Integer lightId, String colorName, Command command, Integer minValue, Integer maxValue)
|
||||
throws ShellyApiException, IllegalArgumentException {
|
||||
Integer value = -1;
|
||||
logger.debug("{}: Set {} to {} ({})", thingName, colorName, command, command.getClass());
|
||||
if (command instanceof PercentType) {
|
||||
PercentType percent = (PercentType) command;
|
||||
Double v = new Double(maxValue) * percent.doubleValue() / 100.0;
|
||||
value = v.intValue();
|
||||
logger.debug("{}: Value for {} is in percent: {}%={}", thingName, colorName, percent, value);
|
||||
} else if (command instanceof DecimalType) {
|
||||
value = ((DecimalType) command).intValue();
|
||||
logger.debug("Value for {} is a number: {}", colorName, value);
|
||||
} else if (command instanceof OnOffType) {
|
||||
value = ((OnOffType) command).equals(OnOffType.ON) ? SHELLY_MAX_COLOR : SHELLY_MIN_COLOR;
|
||||
logger.debug("{}: Value for {} of type OnOff was converted to {}", thingName, colorName, value);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid value type for " + colorName + ": " + value + " / type " + value.getClass());
|
||||
}
|
||||
validateRange(colorName, value, minValue, maxValue);
|
||||
return value.intValue();
|
||||
}
|
||||
|
||||
private Integer setColor(Integer lightId, String colorName, Command command, Integer maxValue)
|
||||
throws ShellyApiException, IllegalArgumentException {
|
||||
return setColor(lightId, colorName, command, 0, maxValue);
|
||||
}
|
||||
|
||||
private void setFullColor(String colorGroup, ShellyColorUtils col) {
|
||||
if ((col.red == SHELLY_MAX_COLOR) && (col.green == SHELLY_MAX_COLOR) && (col.blue == 0)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_YELLOW));
|
||||
} else if ((col.red == SHELLY_MAX_COLOR) && (col.green == 0) && (col.blue == 0)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_RED));
|
||||
} else if ((col.red == 0) && (col.green == SHELLY_MAX_COLOR) && (col.blue == 0)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_GREEN));
|
||||
} else if ((col.red == 0) && (col.green == 0) && (col.blue == SHELLY_MAX_COLOR)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_BLUE));
|
||||
} else if ((col.red == 0) && (col.green == 0) && (col.blue == 0) && (col.white == SHELLY_MAX_COLOR)) {
|
||||
updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_WHITE));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendColors(ShellyDeviceProfile profile, Integer lightId, ShellyColorUtils oldCol,
|
||||
ShellyColorUtils newCol, boolean autoOn) throws ShellyApiException {
|
||||
// boolean updated = false;
|
||||
Integer channelId = lightId + 1;
|
||||
Map<String, String> parms = new TreeMap<>();
|
||||
|
||||
logger.trace(
|
||||
"{}: New color settings for channel {}: RGB {}/{}/{}, white={}, gain={}, brightness={}, color-temp={}",
|
||||
thingName, channelId, newCol.red, newCol.green, newCol.blue, newCol.white, newCol.gain,
|
||||
newCol.brightness, newCol.temp);
|
||||
if (autoOn && (newCol.brightness >= 0)) {
|
||||
parms.put(SHELLY_LIGHT_TURN, profile.inColor || newCol.brightness > 0 ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
}
|
||||
if (profile.inColor) {
|
||||
if (oldCol.red != newCol.red || oldCol.green != newCol.green || oldCol.blue != newCol.blue
|
||||
|| oldCol.white != newCol.white) {
|
||||
logger.debug("{}: Setting RGBW to {}/{}/{}/{}", thingName, newCol.red, newCol.green, newCol.blue,
|
||||
newCol.white);
|
||||
parms.put(SHELLY_COLOR_RED, String.valueOf(newCol.red));
|
||||
parms.put(SHELLY_COLOR_GREEN, String.valueOf(newCol.green));
|
||||
parms.put(SHELLY_COLOR_BLUE, String.valueOf(newCol.blue));
|
||||
parms.put(SHELLY_COLOR_WHITE, String.valueOf(newCol.white));
|
||||
}
|
||||
}
|
||||
if ((!profile.inColor) && (oldCol.temp != newCol.temp)) {
|
||||
logger.debug("{}: Setting color temp to {}", thingName, newCol.temp);
|
||||
parms.put(SHELLY_COLOR_TEMP, String.valueOf(newCol.temp));
|
||||
}
|
||||
if (oldCol.gain != newCol.gain) {
|
||||
logger.debug("{}: Setting gain to {}", thingName, newCol.gain);
|
||||
parms.put(SHELLY_COLOR_GAIN, String.valueOf(newCol.gain));
|
||||
}
|
||||
if ((newCol.brightness >= 0) && (!profile.inColor || profile.isBulb)
|
||||
&& (oldCol.brightness != newCol.brightness)) {
|
||||
logger.debug("{}: Setting brightness to {}", thingName, newCol.brightness);
|
||||
parms.put(SHELLY_COLOR_BRIGHTNESS, String.valueOf(newCol.brightness));
|
||||
}
|
||||
if (!oldCol.effect.equals(newCol.effect)) {
|
||||
logger.debug("{}: Setting effect to {}", thingName, newCol.effect);
|
||||
parms.put(SHELLY_COLOR_EFFECT, newCol.effect.toString());
|
||||
}
|
||||
if (parms.size() > 0) {
|
||||
logger.debug("{}: Send light settings: {}", thingName, parms);
|
||||
api.setLightParms(lightId, parms);
|
||||
updateCurrentColors(lightId, newCol);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCurrentColors(int lightId, ShellyColorUtils col) {
|
||||
channelColors.replace(lightId, col);
|
||||
logger.debug("{}: Colors updated for lightId {}: RGBW={}/{}/{}/{}, Sat/Gain={}, Bright={}, Temp={} ", thingName,
|
||||
lightId, col.red, col.green, col.blue, col.white, col.gain, col.brightness, col.temp);
|
||||
}
|
||||
|
||||
private Integer getColorFromHSB(PercentType colorPercent) {
|
||||
return getColorFromHSB(colorPercent, new Double(SATURATION_FACTOR));
|
||||
}
|
||||
|
||||
private Integer getColorFromHSB(PercentType colorPercent, Double factor) {
|
||||
Double value = new Double(Math.round(colorPercent.doubleValue() * factor));
|
||||
logger.trace("{}: convert {}% into {}/{} (factor={})", thingName, colorPercent, value, value.intValue(),
|
||||
factor);
|
||||
return value.intValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyProtectedHandler} implements a dummy handler for password protected devices.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyProtectedHandler extends ShellyBaseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyProtectedHandler.class);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param thing The thing passed by the HandlerFactory
|
||||
* @param bindingConfig configuration of the binding
|
||||
* @param coapServer coap server instance
|
||||
* @param localIP local IP of the openHAB host
|
||||
* @param httpPort port of the openHAB HTTP API
|
||||
*/
|
||||
public ShellyProtectedHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
|
||||
final ShellyBindingConfiguration bindingConfig, final ShellyCoapServer coapServer, final String localIP,
|
||||
int httpPort, final HttpClient httpClient) {
|
||||
super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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 static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyControlRoller;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsDimmer;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsRoller;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusRelay;
|
||||
import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
|
||||
import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
|
||||
import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/***
|
||||
* The{@link ShellyRelayHandler} handles light (bulb+rgbw2) specific commands and status. All other commands will be
|
||||
* handled by the generic thing handler.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyRelayHandler extends ShellyBaseHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyRelayHandler.class);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param thing The thing passed by the HandlerFactory
|
||||
* @param bindingConfig configuration of the binding
|
||||
* @param coapServer coap server instance
|
||||
* @param localIP local IP of the openHAB host
|
||||
* @param httpPort port of the openHAB HTTP API
|
||||
*/
|
||||
public ShellyRelayHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
|
||||
final ShellyBindingConfiguration bindingConfig, final ShellyCoapServer coapServer, final String localIP,
|
||||
int httpPort, final HttpClient httpClient) {
|
||||
super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleDeviceCommand(ChannelUID channelUID, Command command) throws ShellyApiException {
|
||||
// Process command
|
||||
String groupName = getString(channelUID.getGroupId());
|
||||
Integer rIndex = 0;
|
||||
if (groupName.startsWith(CHANNEL_GROUP_RELAY_CONTROL)
|
||||
&& groupName.length() > CHANNEL_GROUP_RELAY_CONTROL.length()) {
|
||||
rIndex = Integer.parseInt(substringAfter(channelUID.getGroupId(), CHANNEL_GROUP_RELAY_CONTROL)) - 1;
|
||||
} else if (groupName.startsWith(CHANNEL_GROUP_ROL_CONTROL)
|
||||
&& groupName.length() > CHANNEL_GROUP_ROL_CONTROL.length()) {
|
||||
rIndex = Integer.parseInt(substringAfter(channelUID.getGroupId(), CHANNEL_GROUP_ROL_CONTROL)) - 1;
|
||||
}
|
||||
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
default:
|
||||
return false;
|
||||
|
||||
case CHANNEL_OUTPUT:
|
||||
if (!profile.isRoller) {
|
||||
// extract relay number of group name (relay0->0, relay1->1...)
|
||||
logger.debug("{}: Set relay output to {}", thingName, command);
|
||||
api.setRelayTurn(rIndex, command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
} else {
|
||||
logger.debug("{}: Device is in roller mode, channel command {} ignored", thingName, channelUID);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_BRIGHTNESS: // e.g.Dimmer, Duo
|
||||
handleBrightness(command, rIndex);
|
||||
break;
|
||||
|
||||
case CHANNEL_ROL_CONTROL_POS:
|
||||
case CHANNEL_ROL_CONTROL_CONTROL:
|
||||
logger.debug("{}: Roller command/position {}", thingName, command);
|
||||
handleRoller(command, groupName, rIndex,
|
||||
channelUID.getIdWithoutGroup().equals(CHANNEL_ROL_CONTROL_CONTROL));
|
||||
|
||||
// request updates the next 45sec to update roller position after it stopped
|
||||
requestUpdates(autoCoIoT ? 1 : 45 / UPDATE_STATUS_INTERVAL_SECONDS, false);
|
||||
break;
|
||||
|
||||
case CHANNEL_TIMER_AUTOON:
|
||||
logger.debug("{}: Set Auto-ON timer to {}", thingName, command);
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOON, getNumber(command));
|
||||
break;
|
||||
case CHANNEL_TIMER_AUTOOFF:
|
||||
logger.debug("{}: Set Auto-OFF timer to {}", thingName, command);
|
||||
api.setTimer(rIndex, SHELLY_TIMER_AUTOOFF, getNumber(command));
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* PaperUI Control has a combined Slider for Brightness combined with On/Off
|
||||
* Brightness channel has 2 functions: Switch On/Off (OnOnType) and setting brightness (PercentType)
|
||||
* There is some more logic in the control. When brightness is set to 0 the control sends also an OFF command
|
||||
* When current brightness is 0 and slider will be moved the new brightness will be set, but also a ON command is
|
||||
* send.
|
||||
*
|
||||
* @param command
|
||||
* @param index
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
private void handleBrightness(Command command, Integer index) throws ShellyApiException {
|
||||
Integer value = -1;
|
||||
if (command instanceof PercentType) { // Dimmer
|
||||
value = ((PercentType) command).intValue();
|
||||
} else if (command instanceof DecimalType) { // Number
|
||||
value = ((DecimalType) command).intValue();
|
||||
} else if (command instanceof OnOffType) { // Switch
|
||||
logger.debug("{}: Switch output {}", thingName, command);
|
||||
updateBrightnessChannel(index, (OnOffType) command, value);
|
||||
return;
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
ShellyShortLightStatus light = api.getLightStatus(index);
|
||||
if (((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
|
||||
value = Math.min(light.brightness + DIM_STEPSIZE, 100);
|
||||
} else {
|
||||
value = Math.max(light.brightness - DIM_STEPSIZE, 0);
|
||||
}
|
||||
logger.debug("{}: Increase/Decrease brightness from {} to {}", thingName, light.brightness, value);
|
||||
}
|
||||
validateRange("brightness", value, 0, 100);
|
||||
|
||||
// Switch light off on brightness = 0
|
||||
if (value == 0) {
|
||||
logger.debug("{}: Brightness=0 -> switch output OFF", thingName);
|
||||
updateBrightnessChannel(index, OnOffType.OFF, 0);
|
||||
} else {
|
||||
logger.debug("{}: Setting dimmer brightness to {}", thingName, value);
|
||||
updateBrightnessChannel(index, OnOffType.ON, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBrightnessChannel(int lightId, OnOffType power, int brightness) throws ShellyApiException {
|
||||
if (brightness > 0) {
|
||||
api.setBrightness(lightId, brightness, config.brightnessAutoOn);
|
||||
} else {
|
||||
api.setRelayTurn(lightId, power == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
|
||||
}
|
||||
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", power);
|
||||
updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value", toQuantityType(
|
||||
new Double(power == OnOffType.ON ? brightness : 0), DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateDeviceStatus(ShellySettingsStatus status) throws ShellyApiException {
|
||||
// map status to channels
|
||||
boolean updated = false;
|
||||
updated |= updateRelays(status);
|
||||
updated |= updateDimmers(status);
|
||||
updated |= updateLed(status);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Roller Commands
|
||||
*
|
||||
* @param command from handleCommand()
|
||||
* @param groupName relay, roller...
|
||||
* @param index relay number
|
||||
* @param isControl true: is the Rollershutter channel, false: rollerpos channel
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
private void handleRoller(Command command, String groupName, Integer index, boolean isControl)
|
||||
throws ShellyApiException {
|
||||
Integer position = -1;
|
||||
|
||||
if ((command instanceof UpDownType) || (command instanceof OnOffType)) {
|
||||
ShellyControlRoller rstatus = api.getRollerStatus(index);
|
||||
|
||||
if (!getString(rstatus.state).isEmpty() && !getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_STOP)) {
|
||||
boolean up = command instanceof UpDownType && (UpDownType) command == UpDownType.UP;
|
||||
boolean down = command instanceof UpDownType && (UpDownType) command == UpDownType.DOWN;
|
||||
if ((up && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_OPEN))
|
||||
|| (down && getString(rstatus.state).equals(SHELLY_ALWD_ROLLER_TURN_CLOSE))) {
|
||||
logger.debug("{}: Roller is already moving ({}), ignore command {}", thingName,
|
||||
getString(rstatus.state), command);
|
||||
requestUpdates(1, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (((command instanceof UpDownType) && UpDownType.UP.equals(command))
|
||||
|| ((command instanceof OnOffType) && OnOffType.ON.equals(command))) {
|
||||
logger.debug("{}: Open roller", thingName);
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_OPEN);
|
||||
position = SHELLY_MAX_ROLLER_POS;
|
||||
|
||||
}
|
||||
if (((command instanceof UpDownType) && UpDownType.DOWN.equals(command))
|
||||
|| ((command instanceof OnOffType) && OnOffType.OFF.equals(command))) {
|
||||
logger.debug("{}: Closing roller", thingName);
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_CLOSE);
|
||||
position = SHELLY_MIN_ROLLER_POS;
|
||||
}
|
||||
} else if ((command instanceof StopMoveType) && StopMoveType.STOP.equals(command)) {
|
||||
logger.debug("{}: Stop roller", thingName);
|
||||
api.setRollerTurn(index, SHELLY_ALWD_ROLLER_TURN_STOP);
|
||||
} else {
|
||||
logger.debug("{}: Set roller to position {}", thingName, command);
|
||||
if (command instanceof PercentType) {
|
||||
PercentType p = (PercentType) command;
|
||||
position = p.intValue();
|
||||
} else if (command instanceof DecimalType) {
|
||||
DecimalType d = (DecimalType) command;
|
||||
position = d.intValue();
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid value type for roller control/posiution" + command.getClass().toString());
|
||||
}
|
||||
|
||||
// take position from RollerShutter control and map to Shelly positon (OH:
|
||||
// 0=closed, 100=open; Shelly 0=open, 100=closed)
|
||||
// take position 1:1 from position channel
|
||||
position = isControl ? SHELLY_MAX_ROLLER_POS - position : position;
|
||||
validateRange("roller position", position, SHELLY_MIN_ROLLER_POS, SHELLY_MAX_ROLLER_POS);
|
||||
|
||||
logger.debug("{}: Changing roller position to {}", thingName, position);
|
||||
api.setRollerPos(index, position);
|
||||
}
|
||||
if (position != -1) {
|
||||
// make sure both are in sync
|
||||
if (isControl) {
|
||||
int pos = SHELLY_MAX_ROLLER_POS - Math.max(0, Math.min(position, SHELLY_MAX_ROLLER_POS));
|
||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL, new PercentType(pos));
|
||||
} else {
|
||||
updateChannel(groupName, CHANNEL_ROL_CONTROL_POS, new PercentType(position));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-create relay channels depending on relay type/mode
|
||||
*/
|
||||
private void createRelayChannels(ShellyStatusRelay relay, int idx) {
|
||||
if (!areChannelsCreated()) {
|
||||
updateChannelDefinitions(ShellyChannelDefinitionsDTO.createRelayChannels(getThing(), profile, relay, idx));
|
||||
}
|
||||
}
|
||||
|
||||
private void createRollerChannels(ShellyControlRoller roller) {
|
||||
if (!areChannelsCreated()) {
|
||||
updateChannelDefinitions(ShellyChannelDefinitionsDTO.createRollerChannels(getThing(), roller));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Relay/Roller channels
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public boolean updateRelays(ShellySettingsStatus status) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
// Check for Relay in Standard Mode
|
||||
if (profile.hasRelays && !profile.isRoller && !profile.isDimmer) {
|
||||
logger.trace("{}: Updating {} relay(s)", thingName, profile.numRelays);
|
||||
|
||||
int i = 0;
|
||||
ShellyStatusRelay rstatus = api.getRelayStatus(i);
|
||||
for (ShellyShortStatusRelay relay : rstatus.relays) {
|
||||
createRelayChannels(rstatus, i);
|
||||
if ((relay.isValid == null) || relay.isValid) {
|
||||
String groupName = profile.getControlGroup(i);
|
||||
ShellySettingsRelay rs = profile.settings.relays.get(i);
|
||||
updated |= updateChannel(groupName, CHANNEL_OUTPUT_NAME, getStringType(rs.name));
|
||||
|
||||
if (getBool(relay.overpower)) {
|
||||
postEvent(ALARM_TYPE_OVERPOWER, false);
|
||||
}
|
||||
|
||||
updated |= updateChannel(groupName, CHANNEL_OUTPUT, getOnOff(relay.ison));
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_ACTIVE, getOnOff(relay.hasTimer));
|
||||
if (rstatus.extTemperature != null) {
|
||||
// Shelly 1/1PM support up to 3 external sensors
|
||||
// for whatever reason those are not represented as an array, but 3 elements
|
||||
if (rstatus.extTemperature.sensor1 != null) {
|
||||
updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENDOR_TEMP1, toQuantityType(
|
||||
getDouble(rstatus.extTemperature.sensor1.tC), DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
}
|
||||
if (rstatus.extTemperature.sensor2 != null) {
|
||||
updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENDOR_TEMP2, toQuantityType(
|
||||
getDouble(rstatus.extTemperature.sensor2.tC), DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
}
|
||||
if (rstatus.extTemperature.sensor3 != null) {
|
||||
updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_ESENDOR_TEMP3, toQuantityType(
|
||||
getDouble(rstatus.extTemperature.sensor3.tC), DIGITS_TEMP, SIUnits.CELSIUS));
|
||||
}
|
||||
}
|
||||
if ((rstatus.extHumidity != null) && (rstatus.extHumidity.sensor1 != null)) {
|
||||
updated |= updateChannel(CHANNEL_GROUP_SENSOR, CHANNEL_SENSOR_HUM, toQuantityType(
|
||||
getDouble(rstatus.extHumidity.sensor1.hum), DIGITS_PERCENT, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
|
||||
// Update Auto-ON/OFF timer
|
||||
ShellySettingsRelay rsettings = profile.settings.relays.get(i);
|
||||
if (rsettings != null) {
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON,
|
||||
toQuantityType(getDouble(rsettings.autoOn), SmartHomeUnits.SECOND));
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
|
||||
toQuantityType(getDouble(rsettings.autoOff), SmartHomeUnits.SECOND));
|
||||
}
|
||||
|
||||
// Update input(s) state
|
||||
updated |= updateInputs(groupName, status, i);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Relay in Roller Mode
|
||||
if (profile.hasRelays && profile.isRoller && (status.rollers != null)) {
|
||||
logger.trace("{}: Updating {} rollers", thingName, profile.numRollers);
|
||||
int i = 0;
|
||||
|
||||
for (ShellySettingsRoller roller : status.rollers) {
|
||||
if (roller.isValid) {
|
||||
ShellyControlRoller control = api.getRollerStatus(i);
|
||||
Integer relayIndex = i + 1;
|
||||
String groupName = profile.numRollers > 1 ? CHANNEL_GROUP_ROL_CONTROL + relayIndex.toString()
|
||||
: CHANNEL_GROUP_ROL_CONTROL;
|
||||
|
||||
createRollerChannels(control);
|
||||
|
||||
if (control.name != null) {
|
||||
updated |= updateChannel(groupName, CHANNEL_OUTPUT_NAME, getStringType(control.name));
|
||||
}
|
||||
|
||||
String state = getString(control.state);
|
||||
if (state.equals(SHELLY_ALWD_ROLLER_TURN_STOP)) { // only valid in stop state
|
||||
int pos = Math.max(SHELLY_MIN_ROLLER_POS, Math.min(control.currentPos, SHELLY_MAX_ROLLER_POS));
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_CONTROL,
|
||||
toQuantityType(new Double(SHELLY_MAX_ROLLER_POS - pos), SmartHomeUnits.PERCENT));
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_POS,
|
||||
toQuantityType(new Double(pos), SmartHomeUnits.PERCENT));
|
||||
scheduledUpdates = 1; // one more poll and then stop
|
||||
}
|
||||
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STATE, new StringType(state));
|
||||
updated |= updateChannel(groupName, CHANNEL_ROL_CONTROL_STOPR, getStringType(control.stopReason));
|
||||
updated |= updateInputs(groupName, status, i);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Relay/Roller channels
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*
|
||||
* @throws ShellyApiException
|
||||
*/
|
||||
public boolean updateDimmers(ShellySettingsStatus orgStatus) throws ShellyApiException {
|
||||
boolean updated = false;
|
||||
if (profile.isDimmer) {
|
||||
// We need to fixup the returned Json: The dimmer returns light[] element, which is ok, but it doesn't have
|
||||
// the same structure as lights[] from Bulb,RGBW2 and Duo. The tag gets replaced by dimmers[] so that Gson
|
||||
// maps to a different structure (ShellyShortLight).
|
||||
Gson gson = new Gson();
|
||||
ShellySettingsStatus dstatus = gson.fromJson(ShellyApiJsonDTO.fixDimmerJson(orgStatus.json),
|
||||
ShellySettingsStatus.class);
|
||||
|
||||
logger.trace("{}: Updating {} dimmers(s)", thingName, dstatus.dimmers.size());
|
||||
int l = 0;
|
||||
for (ShellyShortLightStatus dimmer : dstatus.dimmers) {
|
||||
Integer r = l + 1;
|
||||
String groupName = profile.numRelays <= 1 ? CHANNEL_GROUP_DIMMER_CONTROL
|
||||
: CHANNEL_GROUP_DIMMER_CONTROL + r.toString();
|
||||
|
||||
// On a status update we map a dimmer.ison = false to brightness 0 rather than the device's brightness
|
||||
// and send a OFF status to the same channel.
|
||||
// When the device's brightness is > 0 we send the new value to the channel and a ON command
|
||||
if (dimmer.ison) {
|
||||
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.ON);
|
||||
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value", toQuantityType(
|
||||
new Double(getInteger(dimmer.brightness)), DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
} else {
|
||||
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Switch", OnOffType.OFF);
|
||||
updated |= updateChannel(groupName, CHANNEL_BRIGHTNESS + "$Value",
|
||||
toQuantityType(new Double(0), DIGITS_NONE, SmartHomeUnits.PERCENT));
|
||||
}
|
||||
|
||||
ShellySettingsDimmer dsettings = profile.settings.dimmers.get(l);
|
||||
if (dsettings != null) {
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOON,
|
||||
toQuantityType(getDouble(dsettings.autoOn), SmartHomeUnits.SECOND));
|
||||
updated |= updateChannel(groupName, CHANNEL_TIMER_AUTOOFF,
|
||||
toQuantityType(getDouble(dsettings.autoOff), SmartHomeUnits.SECOND));
|
||||
}
|
||||
|
||||
updated |= updateInputs(groupName, orgStatus, l);
|
||||
l++;
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update LED channels
|
||||
*
|
||||
* @param th Thing Handler instance
|
||||
* @param profile ShellyDeviceProfile
|
||||
* @param status Last ShellySettingsStatus
|
||||
*/
|
||||
public boolean updateLed(ShellySettingsStatus status) {
|
||||
boolean updated = false;
|
||||
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_LED_STATUS_DISABLE,
|
||||
getOnOff(profile.settings.ledStatusDisable));
|
||||
updated |= updateChannel(CHANNEL_GROUP_DEV_STATUS, CHANNEL_LED_POWER_DISABLE,
|
||||
getOnOff(profile.settings.ledPowerDisable));
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.util;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.util.ShellyUtils.mkChannelId;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.shelly.internal.handler.ShellyBaseHandler;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ShellyChannelCache} implements a caching layer for channel updates.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyChannelCache {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShellyChannelCache.class);
|
||||
|
||||
private final ShellyBaseHandler thingHandler;
|
||||
private final Map<String, State> channelData = new ConcurrentHashMap<>();
|
||||
private String thingName = "";
|
||||
private boolean enabled = false;
|
||||
|
||||
public ShellyChannelCache(ShellyBaseHandler thingHandler) {
|
||||
this.thingHandler = thingHandler;
|
||||
setThingName(thingHandler.thingName);
|
||||
}
|
||||
|
||||
public void setThingName(String thingName) {
|
||||
this.thingName = thingName;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
public synchronized void disable() {
|
||||
clear();
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update one channel. Use Channel Cache to avoid unnecessary updates (and avoid
|
||||
* messing up the log with those updates)
|
||||
*
|
||||
* @param channelId Channel id
|
||||
* @param value Value (State)
|
||||
* @param forceUpdate true: ignore cached data, force update; false check cache of changed data
|
||||
* @return true, if successful
|
||||
*/
|
||||
public boolean updateChannel(String channelId, State newValue, Boolean forceUpdate) {
|
||||
try {
|
||||
State current = null;
|
||||
if (channelData.containsKey(channelId)) {
|
||||
current = channelData.get(channelId);
|
||||
}
|
||||
if (!enabled || forceUpdate || (current == null) || !current.equals(newValue)) {
|
||||
if ((current != null) && current.getClass().isEnum() && (current == newValue)) {
|
||||
return false; // special case for OnOffType
|
||||
}
|
||||
// For channels that support multiple types (like brightness) a suffix is added
|
||||
// this gets removed to get the channelId for updateState
|
||||
thingHandler.publishState(channelId, newValue);
|
||||
if (current == null) {
|
||||
channelData.put(channelId, newValue);
|
||||
} else {
|
||||
channelData.replace(channelId, newValue);
|
||||
}
|
||||
logger.debug("{}: Channel {} updated with {} (type {}).", thingName, channelId, newValue,
|
||||
newValue.getClass());
|
||||
return true;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("{}: Unable to update channel {} with {} (type {}): {} ({})", thingName, channelId, newValue,
|
||||
newValue.getClass(), ShellyUtils.getMessage(e), e.getClass());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean updateChannel(String group, String channel, State value) {
|
||||
return updateChannel(mkChannelId(group, channel), value, false);
|
||||
}
|
||||
|
||||
public boolean updateChannel(String channelId, State value) {
|
||||
return updateChannel(channelId, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from the Channel Cache
|
||||
*
|
||||
* @param group Channel Group
|
||||
* @param channel Channel Name
|
||||
* @return the data from that channel
|
||||
*/
|
||||
|
||||
public State getValue(String group, String channel) {
|
||||
return getValue(mkChannelId(group, channel));
|
||||
}
|
||||
|
||||
public State getValue(String channelId) {
|
||||
if (channelData.containsKey(channelId)) {
|
||||
return channelData.get(channelId);
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
public void resetChannel(String channelId) {
|
||||
channelData.remove(channelId);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
channelData.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.util;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.osgi.framework.Bundle;
|
||||
|
||||
/**
|
||||
* {@link ShellyTranslationProvider} provides i18n message lookup
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyTranslationProvider {
|
||||
|
||||
private final Bundle bundle;
|
||||
private final TranslationProvider i18nProvider;
|
||||
private final LocaleProvider localeProvider;
|
||||
|
||||
public ShellyTranslationProvider(Bundle bundle, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
|
||||
this.bundle = bundle;
|
||||
this.i18nProvider = i18nProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
public ShellyTranslationProvider(final ShellyTranslationProvider other) {
|
||||
this.bundle = other.bundle;
|
||||
this.i18nProvider = other.i18nProvider;
|
||||
this.localeProvider = other.localeProvider;
|
||||
}
|
||||
|
||||
public @Nullable String get(String key, @Nullable Object... arguments) {
|
||||
return getText(key.contains("@text/") || key.contains(".shelly.") ? key : "message." + key, arguments);
|
||||
}
|
||||
|
||||
public @Nullable String getText(String key, @Nullable Object... arguments) {
|
||||
try {
|
||||
Locale locale = localeProvider.getLocale();
|
||||
return i18nProvider.getText(bundle, key, getDefaultText(key), locale, arguments);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return "Unable to load message for key " + key;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getDefaultText(String key) {
|
||||
return i18nProvider.getText(bundle, key, key, Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.util;
|
||||
|
||||
import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyApiException;
|
||||
import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* {@link ShellyUtils} provides general utility functions
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyUtils {
|
||||
public static String mkChannelId(String group, String channel) {
|
||||
return group + "#" + channel;
|
||||
}
|
||||
|
||||
public static String getString(@Nullable String value) {
|
||||
return value != null ? value : "";
|
||||
}
|
||||
|
||||
public static String substringBefore(@Nullable String string, String pattern) {
|
||||
if (string != null) {
|
||||
int pos = string.indexOf(pattern);
|
||||
if (pos > 0) {
|
||||
return string.substring(0, pos);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String substringBeforeLast(@Nullable String string, String pattern) {
|
||||
if (string != null) {
|
||||
int pos = string.lastIndexOf(pattern);
|
||||
if (pos > 0) {
|
||||
return string.substring(0, pos);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String substringAfter(@Nullable String string, String pattern) {
|
||||
if (string != null) {
|
||||
int pos = string.indexOf(pattern);
|
||||
if (pos != -1) {
|
||||
return string.substring(pos + pattern.length());
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String substringBetween(@Nullable String string, String begin, String end) {
|
||||
if (string != null) {
|
||||
int s = string.indexOf(begin);
|
||||
if (s != -1) {
|
||||
// The end tag might be included before the start tag, e.g.
|
||||
// when using "http://" and ":" to get the IP from http://192.168.1.1:8081/xxx
|
||||
// therefore make it 2 steps
|
||||
String result = string.substring(s + begin.length());
|
||||
return substringBefore(result, end);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String getMessage(Exception e) {
|
||||
String message = e.getMessage();
|
||||
return message != null ? message : "";
|
||||
}
|
||||
|
||||
public static Integer getInteger(@Nullable Integer value) {
|
||||
return (value != null ? (Integer) value : 0);
|
||||
}
|
||||
|
||||
public static Long getLong(@Nullable Long value) {
|
||||
return (value != null ? (Long) value : 0);
|
||||
}
|
||||
|
||||
public static Double getDouble(@Nullable Double value) {
|
||||
return (value != null ? (Double) value : 0);
|
||||
}
|
||||
|
||||
public static Boolean getBool(@Nullable Boolean value) {
|
||||
return (value != null ? (Boolean) value : false);
|
||||
}
|
||||
|
||||
// as State
|
||||
|
||||
public static StringType getStringType(@Nullable String value) {
|
||||
return new StringType(value != null ? value : "");
|
||||
}
|
||||
|
||||
public static DecimalType getDecimal(@Nullable Double value) {
|
||||
return new DecimalType((value != null ? value : 0));
|
||||
}
|
||||
|
||||
public static DecimalType getDecimal(@Nullable Integer value) {
|
||||
return new DecimalType((value != null ? value : 0));
|
||||
}
|
||||
|
||||
public static DecimalType getDecimal(@Nullable Long value) {
|
||||
return new DecimalType((value != null ? value : 0));
|
||||
}
|
||||
|
||||
public static Double getNumber(Command command) throws IllegalArgumentException {
|
||||
if (command instanceof DecimalType) {
|
||||
return ((DecimalType) command).doubleValue();
|
||||
}
|
||||
if (command instanceof QuantityType) {
|
||||
return ((QuantityType<?>) command).doubleValue();
|
||||
}
|
||||
throw new IllegalArgumentException("Unable to convert number");
|
||||
}
|
||||
|
||||
public static OnOffType getOnOff(@Nullable Boolean value) {
|
||||
return (value != null ? value ? OnOffType.ON : OnOffType.OFF : OnOffType.OFF);
|
||||
}
|
||||
|
||||
public static OnOffType getOnOff(int value) {
|
||||
return value == 0 ? OnOffType.OFF : OnOffType.ON;
|
||||
}
|
||||
|
||||
public static State toQuantityType(@Nullable Double value, int digits, Unit<?> unit) {
|
||||
if (value == null) {
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
BigDecimal bd = new BigDecimal(value.doubleValue());
|
||||
return toQuantityType(bd.setScale(digits, BigDecimal.ROUND_HALF_UP), unit);
|
||||
}
|
||||
|
||||
public static State toQuantityType(@Nullable Number value, Unit<?> unit) {
|
||||
return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
|
||||
}
|
||||
|
||||
public static State toQuantityType(@Nullable PercentType value, Unit<?> unit) {
|
||||
return value == null ? UnDefType.NULL : toQuantityType(value.toBigDecimal(), unit);
|
||||
}
|
||||
|
||||
public static void validateRange(String name, Integer value, int min, int max) {
|
||||
if ((value < min) || (value > max)) {
|
||||
throw new IllegalArgumentException("Value " + name + " is out of range (" + min + "-" + max + ")");
|
||||
}
|
||||
}
|
||||
|
||||
public static String urlEncode(String input) throws ShellyApiException {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static Long now() {
|
||||
return System.currentTimeMillis() / 1000L;
|
||||
}
|
||||
|
||||
public static DateTimeType getTimestamp() {
|
||||
return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(now()), ZoneId.systemDefault()));
|
||||
}
|
||||
|
||||
public static DateTimeType getTimestamp(String zone, long timestamp) {
|
||||
try {
|
||||
if (timestamp == 0) {
|
||||
return getTimestamp();
|
||||
}
|
||||
ZoneId zoneId = !zone.isEmpty() ? ZoneId.of(zone) : ZoneId.systemDefault();
|
||||
ZonedDateTime zdt = LocalDateTime.now().atZone(zoneId);
|
||||
int delta = zdt.getOffset().getTotalSeconds();
|
||||
return new DateTimeType(ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestamp - delta), zoneId));
|
||||
} catch (DateTimeException e) {
|
||||
// Unable to convert device's timezone, use system one
|
||||
return getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
public static Integer getLightIdFromGroup(String groupName) {
|
||||
if (groupName.startsWith(CHANNEL_GROUP_LIGHT_CHANNEL)) {
|
||||
return Integer.parseInt(substringAfter(groupName, CHANNEL_GROUP_LIGHT_CHANNEL)) - 1;
|
||||
}
|
||||
return 0; // only 1 light, e.g. bulb or rgbw2 in color mode
|
||||
}
|
||||
|
||||
public static String buildControlGroupName(ShellyDeviceProfile profile, Integer channelId) {
|
||||
return profile.isBulb || profile.isDuo || profile.inColor ? CHANNEL_GROUP_LIGHT_CONTROL
|
||||
: CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
|
||||
}
|
||||
|
||||
public static String buildWhiteGroupName(ShellyDeviceProfile profile, Integer channelId) {
|
||||
return profile.isBulb || profile.isDuo && !profile.inColor ? CHANNEL_GROUP_WHITE_CONTROL
|
||||
: CHANNEL_GROUP_LIGHT_CHANNEL + channelId.toString();
|
||||
}
|
||||
|
||||
public static DecimalType mapSignalStrength(int dbm) {
|
||||
int strength = -1;
|
||||
if (dbm > -60) {
|
||||
strength = 4;
|
||||
} else if (dbm > -70) {
|
||||
strength = 3;
|
||||
} else if (dbm > -80) {
|
||||
strength = 2;
|
||||
} else if (dbm > -90) {
|
||||
strength = 1;
|
||||
} else {
|
||||
strength = 0;
|
||||
}
|
||||
return new DecimalType(strength);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.util;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ShellyVersionDTO} compares 2 version strings.
|
||||
*
|
||||
* @author Markus Michels - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShellyVersionDTO {
|
||||
private class VersionTokenizer {
|
||||
private final String versionString;
|
||||
private final int length;
|
||||
|
||||
private int position;
|
||||
private int number;
|
||||
private String suffix = "";
|
||||
|
||||
public VersionTokenizer(@Nullable String versionString) {
|
||||
if (versionString == null) {
|
||||
throw new IllegalArgumentException("versionString is null");
|
||||
}
|
||||
|
||||
this.versionString = versionString;
|
||||
length = versionString.length();
|
||||
}
|
||||
|
||||
private boolean moveNext() {
|
||||
number = 0;
|
||||
suffix = "";
|
||||
|
||||
// No more characters
|
||||
if (position >= length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (position < length) {
|
||||
char c = versionString.charAt(position);
|
||||
if (c < '0' || c > '9') {
|
||||
break;
|
||||
}
|
||||
number = number * 10 + (c - '0');
|
||||
position++;
|
||||
}
|
||||
|
||||
int suffixStart = position;
|
||||
|
||||
while (position < length) {
|
||||
char c = versionString.charAt(position);
|
||||
if (c == '.') {
|
||||
break;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
|
||||
suffix = versionString.substring(suffixStart, position);
|
||||
|
||||
if (position < length) {
|
||||
position++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
private String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean equals(String s1, String s2) {
|
||||
return compare(s1, s2) == 0;
|
||||
}
|
||||
|
||||
public int compare(String version1, String version2) {
|
||||
VersionTokenizer tokenizer1 = new VersionTokenizer(version1);
|
||||
VersionTokenizer tokenizer2 = new VersionTokenizer(version2);
|
||||
|
||||
int number1 = 0, number2 = 0;
|
||||
String suffix1 = "", suffix2 = "";
|
||||
|
||||
while (tokenizer1.moveNext()) {
|
||||
if (!tokenizer2.moveNext()) {
|
||||
do {
|
||||
number1 = tokenizer1.getNumber();
|
||||
suffix1 = tokenizer1.getSuffix();
|
||||
if (number1 != 0 || suffix1.length() != 0) {
|
||||
// Version one is longer than number two, and non-zero
|
||||
return 1;
|
||||
}
|
||||
} while (tokenizer1.moveNext());
|
||||
|
||||
// Version one is longer than version two, but zero
|
||||
return 0;
|
||||
}
|
||||
|
||||
number1 = tokenizer1.getNumber();
|
||||
suffix1 = tokenizer1.getSuffix();
|
||||
number2 = tokenizer2.getNumber();
|
||||
suffix2 = tokenizer2.getSuffix();
|
||||
|
||||
if (number1 < number2) {
|
||||
// Number one is less than number two
|
||||
return -1;
|
||||
}
|
||||
if (number1 > number2) {
|
||||
// Number one is greater than number two
|
||||
return 1;
|
||||
}
|
||||
|
||||
boolean empty1 = suffix1.length() == 0;
|
||||
boolean empty2 = suffix2.length() == 0;
|
||||
|
||||
if (empty1 && empty2) {
|
||||
continue;
|
||||
} // No suffixes
|
||||
if (empty1) {
|
||||
return 1;
|
||||
} // First suffix is empty (1.2 > 1.2b)
|
||||
if (empty2) {
|
||||
return -1;
|
||||
} // Second suffix is empty (1.2a < 1.2)
|
||||
|
||||
// Lexical comparison of suffixes
|
||||
int result = suffix1.compareTo(suffix2);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
while (tokenizer2.moveNext()) {
|
||||
number2 = tokenizer2.getNumber();
|
||||
suffix2 = tokenizer2.getSuffix();
|
||||
if (number2 != 0 || suffix2.length() != 0) {
|
||||
// Version one is longer than version two, and non-zero
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// Version two is longer than version one, but zero
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean checkBeta(@Nullable String version) {
|
||||
if (version == null) {
|
||||
return false;
|
||||
}
|
||||
return version.isEmpty() || version.contains("???") || version.toLowerCase().contains("master")
|
||||
|| (version.toLowerCase().contains("-rc"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="shelly" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Shelly Binding</name>
|
||||
<description>This binding supports the Shelly series of devices.</description>
|
||||
<author>Markus Michels</author>
|
||||
|
||||
<config-description>
|
||||
<parameter name="defaultUserId" type="text">
|
||||
<default>admin</default>
|
||||
<label>Default User</label>
|
||||
<description>Default userId to access protected Shelly devices.</description>
|
||||
</parameter>
|
||||
<parameter name="defaultPassword" type="text">
|
||||
<default>admin</default>
|
||||
<label>Default Password</label>
|
||||
<description>Default password to access protected Shelly devices.</description>
|
||||
</parameter>
|
||||
<parameter name="autoCoIoT" type="boolean">
|
||||
<default>true</default>
|
||||
<label>Auto-enable CoIoT</label>
|
||||
<description>True: Enable CoIoT events by default when firmware 1.6+ is detected</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,298 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:shelly:relay">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="eventsButton" type="boolean" required="false">
|
||||
<label>Enable Button Events</label>
|
||||
<description>True if the binding should register to get the Buttons Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsSwitch" type="boolean" required="false">
|
||||
<label>Enable Switch Events (Out on/off)</label>
|
||||
<description>True if the binding should register to get the Relay Switch Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsPush" type="boolean" required="false">
|
||||
<label>Enable Push Events (short/long)</label>
|
||||
<description>True if the binding should register to get the Short/Long Push Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:roller">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="eventsRoller" type="boolean" required="false">
|
||||
<label>Enable Roller Events (Roller only)</label>
|
||||
<description>True if the binding should register to get Roller Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
<config-description uri="thing-type:shelly:dimmer">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="brightnessAutoOn" type="boolean" required="false">
|
||||
<label>Brightness Auto-ON</label>
|
||||
<description>true: Turn device ON if brightness>0 is set; false: don't touch power status when brightness is set.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="eventsButton" type="boolean" required="false">
|
||||
<label>Enable Button Events</label>
|
||||
<description>True if the binding should register to get the Buttons Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsSwitch" type="boolean" required="false">
|
||||
<label>Enable Switch Events (Out on/off)</label>
|
||||
<description>True if the binding should register to get the Relay Switch Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsPush" type="boolean" required="false">
|
||||
<label>Enable Push Events (short/long)</label>
|
||||
<description>True if the binding should register to get the Short/Long Push Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:light">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="brightnessAutoOn" type="boolean" required="false">
|
||||
<label>Brightness Auto-ON</label>
|
||||
<description>true: Turn device ON if brightness > 0 is set; false: don't touch power status when brightness is set.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="eventsSwitch" type="boolean" required="false">
|
||||
<label>Enable Switch Events (Out on/off)</label>
|
||||
<description>True if the binding should register to get the Relay Switch Events.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:rgbw2">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="brightnessAutoOn" type="boolean" required="false">
|
||||
<label>Brightness Auto-ON</label>
|
||||
<description>true: Turn device ON if brightness > 0 is set; false: don't touch power status when brightness is set.</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>60</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
|
||||
<config-description uri="thing-type:shelly:battery">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User ID</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="lowBattery" type="integer" required="false">
|
||||
<label>Low Battery Threshold</label>
|
||||
<description>Threshold for the battery level, alert will be signed when battery level is below. Default: 20%</description>
|
||||
<default>20</default>
|
||||
<unitLabel>%</unitLabel>
|
||||
</parameter>
|
||||
<parameter name="eventsSensorReport" type="boolean" required="false">
|
||||
<label>Enable Sensor Events</label>
|
||||
<description>True: Register event URL for sensor updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>3600</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="thing-type:shelly:basic">
|
||||
<parameter name="userId" type="text" required="false">
|
||||
<label>User ID</label>
|
||||
<description>User ID for HTTP API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="false">
|
||||
<label>Password</label>
|
||||
<description>Password for HTTP API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>Device IP Address</label>
|
||||
<description>IP-Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
<parameter name="eventsSensorReport" type="boolean" required="false">
|
||||
<label>Enable Sensor Events</label>
|
||||
<description>True: Register event URL for sensor updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="eventsCoIoT" type="boolean" required="false">
|
||||
<label>Enable CoIoT Events</label>
|
||||
<description>If enabled, the CoAP protocol is used to receive status updates.</description>
|
||||
<advanced>true</advanced>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="updateInterval" type="integer" required="true" unit="s">
|
||||
<label>Update Interval</label>
|
||||
<description>Interval to query an update from the device.</description>
|
||||
<default>3600</default>
|
||||
<unitLabel>seconds</unitLabel>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,56 @@
|
||||
discovery.failed# Config status messages
|
||||
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.conf-error-access-denied = Access denied, check user id and password.
|
||||
offline.conf-error-wrong-mode = Device is no longer in the configured device mode {0}, required {1}. Delete the thing and re-discover the device.
|
||||
offline.status-error-timeout = Device is not reachable (API timeout).
|
||||
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.
|
||||
|
||||
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.
|
||||
message.versioncheck.tooold = WARNING: Firmware might be too old, installed: {0}/{1} ({2}), required minimal {3}.
|
||||
message.versioncheck.update = INFO: New firmware available: current version: {0}, new version: {1}
|
||||
message.versioncheck.autocoiot = INFO: Firmware is full-filling the minimum version to auto-enable CoIoT
|
||||
message.roller.calibrating = Device is not calibrated, use Shelly App to perform initial roller calibration.
|
||||
message.init.noipaddress = Unable to detect local IP address. Please make sure that IPv4 is enabled for this interface and check openHAB Network Configuration.
|
||||
message.init.protected = Device is password protected, enter correct credentials in thing configuration.
|
||||
message.command.failed = ERROR: Unable to process command {0} for channel {1}
|
||||
message.command.init = Thing not yet initialized, command {0} triggers initialization
|
||||
message.statusupdate.failed = Unable to update status
|
||||
message.event.triggered = Event triggered: {0}
|
||||
message.coap.init.failed = Unable to start CoIoT: {0}
|
||||
message.discovery.disabled = Device is marked as non-discoverable -> skip
|
||||
message.discovery.protected = Device {0} reported 'Access defined' (missing userid/password or incorrect).
|
||||
message.discovery.failed = Device discovery of device with IP address {0} failed: {1}
|
||||
|
||||
# Device
|
||||
channel-type.shelly.deviceName.label = Device Name
|
||||
channel-type.shelly.deviceName.description = Symbolic Device Name as configured in the Shelly App.
|
||||
|
||||
# Relay, external sensors
|
||||
channel-type.shelly.outputName.label = Output Name
|
||||
channel-type.shelly.outputName.description = Output/Channel Name as configured in the Shelly App.
|
||||
channel-type.shelly.temperature1.label = Temperature 1
|
||||
channel-type.shelly.temperature1.description = Temperature of external Sensor #1
|
||||
channel-type.shelly.temperature2.label = Temperature 2
|
||||
channel-type.shelly.temperature2.description = Temperature of external Sensor #2
|
||||
channel-type.shelly.temperature3.label = Temperature 3
|
||||
channel-type.shelly.temperature3.description = Temperature of external Sensor #3
|
||||
channel-type.shelly.humidity.label = Humidity
|
||||
channel-type.shelly.humidity.description = Relative humidity (0..100%)
|
||||
|
||||
# Roller
|
||||
channel-type.shelly.rollerState.label = State
|
||||
channel-type.shelly.rollerState.description = State of the roller (open/closed/stopped).
|
||||
|
||||
|
||||
# LED disable
|
||||
channel-type.shelly.ledPowerDisable.label = Disable Power LED
|
||||
channel-type.shelly.ledPowerDisable.description = ON: The power status LED will be decativated
|
||||
channel-type.shelly.ledStatusDisable.label = Disable Status LED
|
||||
channel-type.shelly.ledStatusDisable.description = ON: The WiFi status LED will be decativated
|
||||
|
||||
@@ -0,0 +1,582 @@
|
||||
# binding
|
||||
binding.shelly.name = Shelly Binding
|
||||
binding.shelly.description = Dieses Binding integriert Shelly-Komponenten, die über WiFi gesteuert werden können.
|
||||
|
||||
# Config status messages
|
||||
config-status.error.missing-deviceip=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.conf-error-access-denied = Der Zugriff wurde verweigert. überprüfen Sie die konfigurierten Zugangsdaten, oder setzen diese entsprechend (analog zur Shelly App).
|
||||
offline.conf-error-wrong-mode = Das Gerät befindet sich nicht mehr in der erwarteten Betriebsart. Löschen Sie das Gerät und führen Sie eine erneute Geräteerkennung durch.
|
||||
offline.status-error-timeout = Das Gerät ist nicht erreichbar (API Timeout).
|
||||
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.
|
||||
|
||||
# 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.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.
|
||||
message.init.protected = Das Gerät ist passwortgeschützt, die Zugangsdaten müssen in der Thing Konfiguration hinterlegt werden.
|
||||
message.command.failed = FEHLER: Der Befehl {0} für Kanal {1} kann nicht verarbeitet werden
|
||||
message.command.init = Thing aktuell nicht initialisiert, der Befehl {0} führt zur Initialisierung
|
||||
message.statusupdate.failed = Status konnte nicht aktualisiert werden
|
||||
message.event.triggered = Event erzeugt: {0}
|
||||
message.coap.init.failed = CoAP/CoIoT konnte nicht gestartet werden: {0}
|
||||
message.discovery.disabled = Das Gerät ist als "nicht erkennen" markiert und wird nicht übernommen.
|
||||
message.discovery.protected = Das Gerät mit der IP-Adresse {0} ist zugriffsgeschützt und keine Zugangsdaten konfiguriert.
|
||||
message.discovery.failed = Erkennung des Gerätes mit der IP-Adresse {0} ist fehlgeschlagen
|
||||
message.roller.calibrating = Das Gerät ist nicht kalibriert. Es ist eine Kalibrierung mit der Shelly App erforderlich.
|
||||
|
||||
# thing types
|
||||
thing-type.shelly.shelly1.label = Shelly1 (SHSW-1)
|
||||
thing-type.shelly.shelly1.description = Shelly 1 (1 Relay)
|
||||
thing-type.shelly.shelly1pm.label = Shelly 1PM (SHSW-PM)
|
||||
thing-type.shelly.shelly1pm.description = Shelly 1PM mit 1xRelais und Strommesser
|
||||
thing-type.shelly.shellyem.label = Shelly EM (SHEM)
|
||||
thing-type.shelly.shellyem.description = Shelly EM zur Energiemessung
|
||||
thing-type.shelly.shellyem3.label = Shelly 3EM (SHEM-3)
|
||||
thing-type.shelly.shellyem3.description = Shelly 3EM zur Energiemessung
|
||||
thing-type.shelly.shelly2-relay.label = Shelly2 Relay (SHSW-21)
|
||||
thing-type.shelly.shelly2-relay.description = Shelly2 im Relais-Modus (2xRelais, Strommesser)
|
||||
thing-type.shelly.shelly2-roller.label = Shelly2 Relay (SHSW-21)
|
||||
thing-type.shelly.shelly2-roller.description = Shelly2 im Rollladen-Modus (1xRollladen, Strommesser)
|
||||
thing-type.shelly.shelly25-relay.label = Shelly2.5 Relay (SHSW-25)
|
||||
thing-type.shelly.shelly25-relay.description = Shelly2.5 im Relais-Modus (2xRelais, 2 Strommesser)
|
||||
thing-type.shelly.shelly25-roller.label = Shelly2.5 Roller (SHSW-25)
|
||||
thing-type.shelly.shelly25-roller.description = Shelly2.5 im Rollladen-Modus (1xRollladen, Strommesser)
|
||||
thing-type.shelly.shelly4pro.label = Shelly4 Pro Relay (SHSW-4)
|
||||
thing-type.shelly.shelly4pro.description = Shelly 4 Pro mit 4 Relais und Strommessern
|
||||
thing-type.shelly.shellyplug.label = Shelly Plug (SHPLG)
|
||||
thing-type.shelly.shellyplug.description = Shelly Plug als schaltbare Steckdose
|
||||
thing-type.shelly.shellyplugs.label = Shelly Plug-S (SHPLG-S)
|
||||
thing-type.shelly.shellyplugs.description = Shelly Plug-S als schaltbare Steckdose
|
||||
thing-type.shelly.shellydimmer.label = Shelly Dimmer (SHDM-1)
|
||||
thing-type.shelly.shellydimmer.description = Shelly mit Dimmer-Funktion
|
||||
thing-type.shelly.shellydimmer2.label = Shelly Dimmer (SHDM-2)
|
||||
thing-type.shelly.shellydimmer2.description = Shelly mit Dimmer-Funktion, 2. Generation
|
||||
thing-type.shelly.shellybutton1.label = Shelly Button 1 (SHBTN-1)
|
||||
thing-type.shelly.shellybutton1.description = Shelly Button 1 (batteriebetrieben)
|
||||
thing-type.shelly.shellyht.label = Shelly H&T (SHHT-1)
|
||||
thing-type.shelly.shellyht.description = Shelly H&T Sensor
|
||||
thing-type.shelly.shellydw.label = Shelly Door/Window Sensor (SHDW-1)
|
||||
thing-type.shelly.shellydw.description = Shelly Tür/Fenstersensor (batteriebetrieben)
|
||||
thing-type.shelly.shellydw2.label = Shelly Door/Window Sensor 2 (SHDW-2)
|
||||
thing-type.shelly.shellydw2.description = Shelly Tür/Fenstersensor (batteriebetrieben)
|
||||
thing-type.shelly.shellysmoke.label = Shelly Smoke
|
||||
thing-type.shelly.shellysmoke.description = Shelly Rauchmelder
|
||||
thing-type.shelly.shellyflood.label = Shelly Flood (SHWT-1)
|
||||
thing-type.shelly.shellyflood.description = Shelly Wassermelder
|
||||
thing-type.shelly.shellysense.label = Shelly Sense (SHSEN-1)
|
||||
thing-type.shelly.shellysense.description = Shelly Bewegungsmelder
|
||||
thing-type.shelly.shellybulb.label = Shelly Bulb (SHBLB-1)
|
||||
thing-type.shelly.shellybulb.description = Shelly Glühbirne weiß/Farbe
|
||||
thing-type.shelly.shellybulbduo.label = Shelly Duo (SHBDUO-1)
|
||||
thing-type.shelly.shellybulbduo.description = Shelly Duo Glühbirne
|
||||
thing-type.shelly.shellyvintage.label = Shelly Vintage (SHVIN-1)
|
||||
thing-type.shelly.shellyvintage.description = Shelly Vintage Glühbirne
|
||||
thing-type.shelly.shellyrgbw2-color.label = Shelly RGBW2 Color Mode (SHRGBW2)
|
||||
thing-type.shelly.shellyrgbw2-color.description = Shelly RGBW-Controller im Farbmodus
|
||||
thing-type.shelly.shellyrgbw2-white.label = Shelly RGBW2 White Mode (SHRGBW2)
|
||||
thing-type.shelly.shellyrgbw2-white.description = Shelly RGBW-Controller im Weiß-Modus, 4 Streifen
|
||||
|
||||
# thing config - generic
|
||||
thing-type.config.shelly.generic.userId.label = Benutzer
|
||||
thing-type.config.shelly.generic.userId.description = Benutzerkennung für API-Zugriff
|
||||
thing-type.config.shelly.generic.password.label = Passwort
|
||||
thing-type.config.shelly.generic.password.description = Passwort für API-Zugriff
|
||||
thing-type.config.shelly.generic.deviceIp.label = IP Adresse
|
||||
thing-type.config.shelly.generic.deviceIp.description = IP Adresse der Shelly-Komponente
|
||||
thing-type.config.shelly.generic.weakSignal.label = Schwaches Signal (dBm)
|
||||
thing-type.config.shelly.generic.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
|
||||
thing-type.config.shelly.generic.eventsButton.label = Button Events
|
||||
thing-type.config.shelly.generic.eventsButton.description = Aktiviert die Button Action URLS
|
||||
thing-type.config.shelly.generic.eventsPush.label = Push Events
|
||||
thing-type.config.shelly.generic.eventsPush.description = Aktiviert die Push Button Action URLS
|
||||
thing-type.config.shelly.generic.eventsSwitch.label = Output Events
|
||||
thing-type.config.shelly.generic.eventsSwitch.description = Aktiviert die Output Action URLS
|
||||
thing-type.config.shelly.generic.eventsCoIoT.label = CoIoT aktivieren
|
||||
thing-type.config.shelly.generic.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
|
||||
thing-type.config.shelly.generic.updateInterval.label = Status-Intervall
|
||||
thing-type.config.shelly.generic.updateInterval.description = Intervall für die Hintergundaktualisierung
|
||||
|
||||
# thing config - light
|
||||
thing-type.config.shelly.light.userId.label = Benutzer
|
||||
thing-type.config.shelly.light.userId.description = Benutzerkennung für API-Zugriff
|
||||
thing-type.config.shelly.light.password.label = Passwort
|
||||
thing-type.config.shelly.light.password.description = Passwort für API-Zugriff
|
||||
thing-type.config.shelly.light.deviceIp.label = IP Adresse
|
||||
thing-type.config.shelly.light.deviceIp.description = IP Adresse der Shelly-Komponente
|
||||
thing-type.config.shelly.light.weakSignal.label = Schwaches Signal (dBm)
|
||||
thing-type.config.shelly.light.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
|
||||
thing-type.config.shelly.light.brightnessAutoOn.label = Helligkeit Auto-EIN
|
||||
thing-type.config.shelly.light.brightnessAutoOn.description = AN: Setzen einer Helligkeit > 0 schaltet das Gerät automatisch ein; AUS: Gerätestatus wird nicht ge#ndert
|
||||
thing-type.config.shelly.light.eventsButton.label = Button Events
|
||||
thing-type.config.shelly.light.eventsButton.description = Aktiviert die Button Action URLS
|
||||
thing-type.config.shelly.light.eventsPush.label = Push Events
|
||||
thing-type.config.shelly.light.eventsPush.description = Aktiviert die Push Button Action URLS
|
||||
thing-type.config.shelly.light.eventsSwitch.label = Output Events
|
||||
thing-type.config.shelly.light.eventsSwitch.description = Aktiviert die Output Action URLS
|
||||
thing-type.config.shelly.light.eventsCoIoT.label = CoIoT aktivieren
|
||||
thing-type.config.shelly.light.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
|
||||
thing-type.config.shelly.light.updateInterval.label = Status-Intervall
|
||||
thing-type.config.shelly.light.updateInterval.description = Intervall für die Hintergrundaktualisierung
|
||||
|
||||
# thing config - battery
|
||||
thing-type.config.shelly.battery.userId.label = Benutzer
|
||||
thing-type.config.shelly.battery.userId.description = Benutzerkennung für API-Zugriff
|
||||
thing-type.config.shelly.battery.password.label = Passwort
|
||||
thing-type.config.shelly.battery.password.description = Passwort für API-Zugriff
|
||||
thing-type.config.shelly.battery.deviceIp.label = IP Adresse
|
||||
thing-type.config.shelly.battery.deviceIp.description = IP Adresse der Shelly-Komponente
|
||||
thing-type.config.shelly.battery.eventsSensorReport.label = Sensor Events
|
||||
thing-type.config.shelly.battery.eventsSensorReport.description = Aktiviert die Sensor Action URLS
|
||||
thing-type.config.shelly.battery.eventsCoIoT.label = CoIoT aktivieren
|
||||
thing-type.config.shelly.battery.eventsCoIoT.description = Aktiviert CoIoT-Protokoll (CoAP-basiert)
|
||||
thing-type.config.shelly.battery.weakSignal.label = Schwaches Signal (dBm)
|
||||
thing-type.config.shelly.battery.weakSignal.description = Ein Alarm wird ausgelöst, wenn das WiFi-Signal diesen Wert unterschreitet. Voreinstellung: -80 dBm
|
||||
thing-type.config.shelly.battery.lowBattery.label = Batterieladung niedrig (%)
|
||||
thing-type.config.shelly.battery.lowBattery.description = Ein Alarm wird ausgelöst, wenn das Gerät eine Batterieladung kleiner diesem Schwellwert meldet. Default: 20%
|
||||
thing-type.config.shelly.battery.updateInterval.label = Status-Intervall
|
||||
thing-type.config.shelly.battery.updateInterval.description = Intervall für die Hintergundaktualisierung
|
||||
|
||||
# thing config - unknown
|
||||
thing-type.config.shellydevice.userId.label = Benutzer
|
||||
thing-type.config.shellydevice.userId.description = Benutzerkennung für API-Zugriff
|
||||
thing-type.config.shellydevice.password.label = Passwort
|
||||
thing-type.config.shellydevice.password.description = Passwort für API-Zugriff
|
||||
thing-type.config.shellydevice.deviceIp.label = IP Adresse
|
||||
thing-type.config.shellydevice.deviceIp.description = IP Adresse der Shelly-Komponente
|
||||
|
||||
# channel-groups
|
||||
thing-type.shelly.shelly1.group.relay.label = Relais
|
||||
thing-type.shelly.shelly1.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly1.group.sensors.label = Externe Sensoren
|
||||
thing-type.shelly.shelly1.group.sensors.description = Temperaturwerte der externen Sensoren (nur wenn angeschlossen)
|
||||
thing-type.shelly.shelly1.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly1.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly1pm.group.relay.label = Relais
|
||||
thing-type.shelly.shelly1pm.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly1pm.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shelly1pm.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly1pm.group.sensors.label = Externe Sensoren
|
||||
thing-type.shelly.shelly1pm.group.sensors.description = Werte der externen Sensoren (nur wenn angeschlossen)
|
||||
thing-type.shelly.shelly1pm.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly1pm.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyem.group.relay.label = Relais
|
||||
thing-type.shelly.shellyem.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellyem.group.meter1.label = Verbrauch 1
|
||||
thing-type.shelly.shellyem.group.meter1.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem.group.meter2.label = Verbrauch 2
|
||||
thing-type.shelly.shellyem.group.meter2.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyem.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyem3.group.relay.label = Relais
|
||||
thing-type.shelly.shellyem3.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellyem3.group.meter1.label = Verbrauch 1
|
||||
thing-type.shelly.shellyem3.group.meter1.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem3.group.meter2.label = Verbrauch 2
|
||||
thing-type.shelly.shellyem3.group.meter2.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem3.group.meter3.label = Verbrauch 3
|
||||
thing-type.shelly.shellyem3.group.meter3.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyem3.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyem3.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly2-relay.group.relay1.label = Relais 1
|
||||
thing-type.shelly.shelly2-relay.group.relay1.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly2-relay.group.relay2.label = Relais 2
|
||||
thing-type.shelly.shelly2-relay.group.relay2.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly2-relay.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shelly2-relay.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly2-relay.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly2-relay.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly2-roller.group.roller.label = Rollladen
|
||||
thing-type.shelly.shelly2-roller.group.roller.description = Rollladensteuerung und Status
|
||||
thing-type.shelly.shelly2-roller.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shelly2-roller.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly2-roller.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly2-roller.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly25-relay.group.relay1.label = Relais 1
|
||||
thing-type.shelly.shelly25-relay.group.relay1.description = Relais Ein-/Ausgänge und -Funktionen
|
||||
thing-type.shelly.shelly25-relay.group.relay2.label = Relais 2
|
||||
thing-type.shelly.shelly25-relay.group.relay2.description = Relais Ein-/Ausgänge und -Funktionen
|
||||
thing-type.shelly.shelly25-relay.group.meter1.label = Verbrauch 1
|
||||
thing-type.shelly.shelly25-relay.group.meter1.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly25-relay.group.meter2.label = Verbrauch 2
|
||||
thing-type.shelly.shelly25-relay.group.meter2.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly25-relay.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly25-relay.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly25-roller.group.roller.label = Rollladen
|
||||
thing-type.shelly.shelly25-roller.group.roller.description = Rollladensteuerung und Status
|
||||
thing-type.shelly.shelly25-roller.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shelly25-roller.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly25-roller.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly25-roller.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shelly4pro.group.relay1.label = Relais 1
|
||||
thing-type.shelly.shelly4pro.group.relay1.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly4pro.group.relay2.label = Relais 2
|
||||
thing-type.shelly.shelly4pro.group.relay2.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly4pro.group.relay3.label = Relais 3
|
||||
thing-type.shelly.shelly4pro.group.relay3.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly4pro.group.relay4.label = Relais 4
|
||||
thing-type.shelly.shelly4pro.group.relay4.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shelly4pro.group.meter1.label = Verbrauch 1
|
||||
thing-type.shelly.shelly4pro.group.meter1.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly4pro.group.meter2.label = Verbrauch 2
|
||||
thing-type.shelly.shelly4pro.group.meter2.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly4pro.group.meter3.label = Verbrauch 3
|
||||
thing-type.shelly.shelly4pro.group.meter3.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly4pro.group.meter4.label = Verbrauch 4
|
||||
thing-type.shelly.shelly4pro.group.meter4.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shelly4pro.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shelly4pro.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyplug.group.relay.label = Relais
|
||||
thing-type.shelly.shellyplug.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellyplug.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyplug.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyplug.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyplug.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyplugs.group.relay.label = Relais
|
||||
thing-type.shelly.shellyplugs.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellyplugs.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyplugs.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyplugs.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyplugs.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellydimmer.group.relay.label = Relais
|
||||
thing-type.shelly.shellydimmer.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellydimmer.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellydimmer.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellydimmer.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellydimmer.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellydimmer2.group.relay.label = Relais
|
||||
thing-type.shelly.shellydimmer2.group.relay.description = Relais Ein-/Ausgänge und Status
|
||||
thing-type.shelly.shellydimmer2.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellydimmer2.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellydimmer2.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellydimmer2.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyix3.group.status1.label = Eingang #1
|
||||
thing-type.shelly.shellyix3.group.status1.description = Status Informationen zum Eingang 1
|
||||
thing-type.shelly.shellyix3.group.status2.label = Eingang #2
|
||||
thing-type.shelly.shellyix3.group.status2.description = Status Informationen zum Eingang 2
|
||||
thing-type.shelly.shellyix3.group.status3.label = Eingang #3
|
||||
thing-type.shelly.shellyix3.group.status3.description = Status Informationen zum Eingang 3
|
||||
thing-type.shelly.shellyix3.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyix3.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellybutton1.group.status.label = Taster-Status
|
||||
thing-type.shelly.shellybutton1.group.status.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellybutton1.group.battery.label = Batteriestatus
|
||||
thing-type.shelly.shellybutton1.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellybutton1.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellybutton1.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellybulb.group.control.label = Steuerung
|
||||
thing-type.shelly.shellybulb.group.control.description = Steuerung des Lichts
|
||||
thing-type.shelly.shellybulb.group.color.label = Farbmodus
|
||||
thing-type.shelly.shellybulb.group.color.description = Einstellungen für den Farbmodus
|
||||
thing-type.shelly.shellybulb.group.white.label = Weißwerte
|
||||
thing-type.shelly.shellybulb.group.white.description = Einstellungen für den Weiß-Modus
|
||||
thing-type.shelly.shellybulb.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellybulb.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellybulbduo.group.control.label = Steuerung
|
||||
thing-type.shelly.shellybulbduo.group.control.description = Steuerung des Lichts
|
||||
thing-type.shelly.shellybulbduo.group.white.label = Weißwerte
|
||||
thing-type.shelly.shellybulbduo.group.white.description = Einstellungen für den Weiß-Modus
|
||||
thing-type.shelly.shellybulbduo.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellybulbduo.group.meter.description = Verbrauchswerte
|
||||
thing-type.shelly.shellybulbduo.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellybulbduo.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyvintage.group.control.label = Steuerung
|
||||
thing-type.shelly.shellyvintage.group.control.description = Steuerung des Lichts
|
||||
thing-type.shelly.shellyvintage.group.white.label = Weißwerte
|
||||
thing-type.shelly.shellyvintage.group.white.description = Einstellungen für den Weiß-Modus
|
||||
thing-type.shelly.shellyvintage.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyvintage.group.meter.description = Verbrauchswerte
|
||||
thing-type.shelly.shellyvintage.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyvintage.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyrgbw2-color.group.control.label = Steuerung
|
||||
thing-type.shelly.shellyrgbw2-color.group.control.description = Steuerung des Controllers
|
||||
thing-type.shelly.shellyrgbw2-color.group.color.label = Farben
|
||||
thing-type.shelly.shellyrgbw2-color.group.color.description = Farbwerte und Profile
|
||||
thing-type.shelly.shellyrgbw2-color.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyrgbw2-color.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyrgbw2-color.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyrgbw2-color.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyrgbw2-white.group.control.label = Steuerung
|
||||
thing-type.shelly.shellyrgbw2-white.group.control.description = Lichtsteuerung
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel1.label = Kanal 1
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel1.description = Steuerung für Kanal 1
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel2.label = Kanal 2
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel2.description = Steuerung für Kanal 2
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel3.label = Kanal 3
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel3.description = Steuerung für Kanal 3
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel4.label = Kanal 4
|
||||
thing-type.shelly.shellyrgbw2-white.group.channel4.description = Steuerung für Kanal 4
|
||||
thing-type.shelly.shellyrgbw2-white.group.meter.label = Verbrauch
|
||||
thing-type.shelly.shellyrgbw2-white.group.meter.description = Verbrauchswerte und andere Informationen
|
||||
thing-type.shelly.shellyrgbw2-white.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyrgbw2-white.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyht.group.sensors.label = Sensorwerte
|
||||
thing-type.shelly.shellyht.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellyht.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellyht.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellyht.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyht.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellyflood.group.sensors.label = Sensorwerte
|
||||
thing-type.shelly.shellyflood.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellyflood.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellyflood.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellyflood.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellyflood.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellydw.group.sensors.label = Sensordaten
|
||||
thing-type.shelly.shellydw.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellydw.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellydw.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellydw.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellydw.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellydw2.group.sensors.label = Sensordaten
|
||||
thing-type.shelly.shellydw2.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellydw2.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellydw2.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellydw2.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellydw2.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellysmoke.sensors.device.label = Sensordaten
|
||||
thing-type.shelly.shellysmoke.sensors.device.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellysmoke.group.battery.label = Batterie-Status
|
||||
thing-type.shelly.shellysmoke.group.battery.description = Informationen zum Akku
|
||||
thing-type.shelly.shellysmoke.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellysmoke.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellysense.sensors.device.label = Sensordaten
|
||||
thing-type.shelly.shellysense.sensors.device.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellysense.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellysense.group.device.description = Informationen zum Gerätestatus
|
||||
thing-type.shelly.shellygas.group.sensors.label = Sensordaten
|
||||
thing-type.shelly.shellygas.group.sensors.description = Messwerte und Status des Sensors
|
||||
thing-type.shelly.shellygas.group.device.label = Gerätestatus
|
||||
thing-type.shelly.shellygas.group.device.description = Informationen zum Gerätestatus
|
||||
|
||||
|
||||
# channels
|
||||
channel-type.shelly.outputName.label = Ausgangsname
|
||||
channel-type.shelly.outputName.description = Name des Relais-Ausgangs/Kanals (Konfiguration innerhalb der Shelly App)
|
||||
channel-type.shelly.timerAutoOn.label = Auto-EIN Timer
|
||||
channel-type.shelly.timerAutoOn.description = Wenn das Relais ausgeschaltet wird, wird dieses automatisch wieder nach n Sekunden eingeschaltet
|
||||
channel-type.shelly.timerAutoOff.label = Auto-AUS Timer
|
||||
channel-type.shelly.timerAutoOff.description = Wenn das Relais eingeschaltet wird, wird dieses automatisch wieder nach n Sekunden ausgeschaltet
|
||||
channel-type.shelly.timerActive.label = Timer aktiv
|
||||
channel-type.shelly.timerActive.description = ON: Auto-On/Off Timer ist aktiv
|
||||
channel-type.shelly.temperature1.label = Temperatur 1
|
||||
channel-type.shelly.temperature1.description = Temperatur des externen Sensors #1
|
||||
channel-type.shelly.temperature2.label = Temperatur 2
|
||||
channel-type.shelly.temperature2.description = Temperatur des externen Sensors #2
|
||||
channel-type.shelly.temperature3.label = Temperatur 3
|
||||
channel-type.shelly.temperature3.description = Temperatur des externen Sensors #3
|
||||
channel-type.shelly.humidity.label = Luftfeuchtigkeit
|
||||
channel-type.shelly.humidity.description = Relative Luftfeuchtigkeit (0..100%)
|
||||
channel-type.shelly.rollerShutter.label = Steuerung (0=offen, 100=geschlossen)
|
||||
channel-type.shelly.rollerShutter.description = Steuerung für den Rollladen: UP, DOWN, STOP, Position (0=offen, 100=geschlossen)
|
||||
channel-type.shelly.rollerPosition.label = Position (100=offen, 0=zu)
|
||||
channel-type.shelly.rollerPosition.description = Invertierte Position des Rollladen: 100=offen, 0=zu
|
||||
channel-type.shelly.rollerState.label = Status
|
||||
channel-type.shelly.rollerState.description = Zustand des Rollladen (open/closed/stopped).
|
||||
channel-type.shelly.rollerState.state.option.open = öffnet
|
||||
channel-type.shelly.rollerState.state.option.close = schließt
|
||||
channel-type.shelly.rollerState.state.option.stop = gestoppt
|
||||
channel-type.shelly.rollerStop.label = Stoppgrund
|
||||
channel-type.shelly.rollerStop.description = Letzter Grund für das Stoppen des Rollladens (normal=normaler Stopp, safety_switch=Sicherheits-Stopp, obstacle=Rollladen verhakt (Widerstand erkannt)
|
||||
channel-type.shelly.inputState.label = Eingang
|
||||
channel-type.shelly.inputState.description = Status des Relais-Eingangs
|
||||
channel-type.shelly.inputState1.label = Eingang 1
|
||||
channel-type.shelly.inputState1.description = Status des Relais-Eingangs 1
|
||||
channel-type.shelly.inputState2.label = Eingang 2
|
||||
channel-type.shelly.inputState2.description = Status des Relais-Eingangs 2
|
||||
channel-type.shelly.inputState3.label = Eingang 3
|
||||
channel-type.shelly.inputState3.description = Status des Relais-Eingangs 3
|
||||
channel-type.shelly.dimmerBrightness.label = Helligkeit
|
||||
channel-type.shelly.dimmerBrightness.description = Helligkeit (0-100%, 0=aus)
|
||||
channel-type.shelly.whiteBrightness.label = Helligkeit
|
||||
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 = 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 = Kumulierter Einspeisung
|
||||
channel-type.shelly.meterAccuReturned.description = Kumulierter Einspeisung in kW/h
|
||||
channel-type.shelly.meterCurrent.label = Stromstärke
|
||||
channel-type.shelly.meterCurrent.description = Aktuelle gemessene Stromstärke
|
||||
channel-type.shelly.meterTotal.label = Gesamtverbrauch
|
||||
channel-type.shelly.meterTotal.description = Gesamtverbrauch seit Neustart in kW/h
|
||||
channel-type.shelly.meterReturned.label = Einspeisung
|
||||
channel-type.shelly.meterReturned.description = Einspeisung in kW/h
|
||||
channel-type.shelly.meterVoltage.label = Spannung
|
||||
channel-type.shelly.meterVoltage.description = Spannung in Volt
|
||||
channel-type.shelly.meterPowerFactor.label = Stromfaktor
|
||||
channel-type.shelly.meterPowerFactor.description = Faktor für Strom bei Photovoltaik
|
||||
channel-type.shelly.meterReactive.label = Rückstrom
|
||||
channel-type.shelly.meterReactive.description = Aktueller Stromverbrauch (Watt) des Rückstroms
|
||||
channel-type.shelly.lastPower1.label = Schnitt letzte Min
|
||||
channel-type.shelly.lastPower1.description = Durchschnittsverbrauch der letzten Minute, 60s in Watt/Min
|
||||
channel-type.shelly.timestamp.label = Letzte Aktualisierung
|
||||
channel-type.shelly.timestamp.description = Zeitstempel der letzten Aktualisierung (lokale Zeitzone)
|
||||
channel-type.shelly.ledStatusDisable.label = Status-LED aus
|
||||
channel-type.shelly.ledStatusDisable.description = ON: Die Status-LED am Gerät ist deaktiviert
|
||||
channel-type.shelly.ledPowerDisable.label = Betriebs-LED aus
|
||||
channel-type.shelly.ledPowerDisable.description = ON: Die Betriebsanzeige (LED) am Gerät ist deaktiviert
|
||||
channel-type.shelly.colorMode.label = Farbmodus
|
||||
channel-type.shelly.colorMode.description = Betriebsart: ON: Farbe, OFF: Weiß
|
||||
channel-type.shelly.colorFull.label = Voll-Farbe
|
||||
channel-type.shelly.colorFull.description = Ausgewählte Farbe (red/green/blue/yellow/white) wird auf volle Intensität gesetzt (255)
|
||||
channel-type.shelly.colorFull.state.option.red = Rot
|
||||
channel-type.shelly.colorFull.state.option.green = Grün
|
||||
channel-type.shelly.colorFull.state.option.blue = Blau
|
||||
channel-type.shelly.colorFull.state.option.yellow = Gelb
|
||||
channel-type.shelly.colorFull.state.option.white = Weiß
|
||||
channel-type.shelly.colorRed.label = Rot
|
||||
channel-type.shelly.colorRed.description = Rot-Anteil des RGB-Wertes (0-255)
|
||||
channel-type.shelly.colorGreen.label = Grün
|
||||
channel-type.shelly.colorGreen.description = Grün-Anteil des RGB-Wertes (0-255)
|
||||
channel-type.shelly.colorBlue.label = Blau
|
||||
channel-type.shelly.colorBlue.description = Blau-Anteil des RGB-Wertes (0-255)
|
||||
channel-type.shelly.colorWhite.label = Weiß
|
||||
channel-type.shelly.colorWhite.description = Weiß-Anteil des RGBW-Wertes (0-255)
|
||||
channel-type.shelly.colorGain.label = Verstärkung
|
||||
channel-type.shelly.colorGain.description = Verstärkung des Farbwertes (1-100%)
|
||||
channel-type.shelly.whiteTemp.label = Lichttemperatur
|
||||
channel-type.shelly.whiteTemp.description = Temperatur des Weißlichtes (Bulb/RGBW2: 3000..6500K; Duo: 2700K..6500K)
|
||||
channel-type.shelly.colorEffectBulb.label = Lichteffekt
|
||||
channel-type.shelly.colorEffectBulb.description = Lichteffekt: 0: keiner, 1: Meteoritenregen, 2: Verlauf, 3: Atmen, 4: Blitzen, 5: ‹bergang ein/aus, 6: Rot/Grün-Wechsel
|
||||
channel-type.shelly.colorEffectBulb.option.0 = Aus
|
||||
channel-type.shelly.colorEffectBulb.option.1 = Meteoritenregen
|
||||
channel-type.shelly.colorEffectBulb.option.2 = Verlauf
|
||||
channel-type.shelly.colorEffectBulb.option.3 = Atmen
|
||||
channel-type.shelly.colorEffectBulb.option.4 = Blitzen
|
||||
channel-type.shelly.colorEffectBulb.option.5 = Übergang ein/aus
|
||||
channel-type.shelly.colorEffectBulb.option.6 = Rot/Grün-Wechsel
|
||||
channel-type.shelly.colorEffectRGBW2.label = Farbeffekt
|
||||
channel-type.shelly.colorEffectRGBW2.description = Lichteffekt: 0: keiner, 1: Meteoritenregen, 2: Farbverlauf, 3: Blitzen
|
||||
channel-type.shelly.colorEffectRGBW2.option.0 = Aus
|
||||
channel-type.shelly.colorEffectRGBW2.option.1 = Meteoritenregen
|
||||
channel-type.shelly.colorEffectRGBW2.option.2 = Farbverlauf
|
||||
channel-type.shelly.colorEffectRGBW2.option.3 = Blitzen
|
||||
channel-type.shelly.sensorTemp.label = Temperatur
|
||||
channel-type.shelly.sensorTemp.description = Aktuelle Temperatur des Sensors in ∞C
|
||||
channel-type.shelly.sensorExtTemp.label = Temperatur
|
||||
channel-type.shelly.sensorExtTemp.description = Aktuelle Temperatur des externen Sensors in ∞C
|
||||
channel-type.shelly.sensorExtHum.label = Luftfeuchtigkeit
|
||||
channel-type.shelly.sensorExtHum.description = Relative Luftfeuchtigkeit in %
|
||||
channel-type.shelly.sensorHumidity.label = Luftfeuchtigkeit
|
||||
channel-type.shelly.sensorHumidity.description = Relative Luftfeuchtigkeit in %
|
||||
channel-type.shelly.sensorFlood.label = Wasseralarm
|
||||
channel-type.shelly.sensorFlood.description = Alarm: Es wurde Wasser erkannt
|
||||
channel-type.shelly.sensorSmoke.label = Rauchalarm
|
||||
channel-type.shelly.sensorSmoke.description = Alarm: Es wurde Rauch erkannt
|
||||
channel-type.shelly.sensorLux.label = Helligkeit
|
||||
channel-type.shelly.sensorLux.description = Helligkeit in Lux
|
||||
channel-type.shelly.sensorIllumination.label = Tageslicht
|
||||
channel-type.shelly.sensorIllumination.description = Erkanntes Tageslicht (bright=taghell, twilight=Dämmerung, dark=Abend/Nacht)
|
||||
channel-type.shelly.sensorIllumination.state.option.dark = Dunkel
|
||||
channel-type.shelly.sensorIllumination.state.option.twilight = Dämmerung
|
||||
channel-type.shelly.sensorIllumination.state.option.bright = Hell
|
||||
channel-type.shelly.sensorIllumination.state.option.unknown = Unbekannt
|
||||
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.sensorTilt.label = Öffnungswinkel
|
||||
channel-type.shelly.sensorTilt.description = Öffnungswinkel in Grad (erfordert Kalibrierung in der App)
|
||||
channel-type.shelly.sensorVibration.label = Vibration
|
||||
channel-type.shelly.sensorVibration.description = ON: Sensor hat eine Vibration erkannt
|
||||
channel-type.shelly.sensorValve.label = Ventil
|
||||
channel-type.shelly.sensorValve.description = Gibt den Status des Ventils an, sofern eines angeschlossen ist.
|
||||
channel-type.shelly.sensorValve.state.option.closed = geschlossen
|
||||
channel-type.shelly.sensorValve.state.option.opened = geöffnet
|
||||
channel-type.shelly.sensorValve.state.option.not_connected = nicht angeschlossen
|
||||
channel-type.shelly.sensorValve.state.option.failure = Störung
|
||||
channel-type.shelly.sensorValve.state.option.closing = schließt
|
||||
channel-type.shelly.sensorValve.state.option.opening = öffnet
|
||||
channel-type.shelly.sensorValve.state.option.checking = Test läuft
|
||||
channel-type.shelly.sensorValve.state.option.UNKNOWN = unbekannt
|
||||
channel-type.shelly.sensorCharger.label = Ladegerät
|
||||
channel-type.shelly.sensorCharger.description = ON: Ein Ladegerät ist angeschlossen
|
||||
channel-type.shelly.sensorContact.label = Kontakt
|
||||
channel-type.shelly.sensorContact.description = Status des Sensors (OPEN=offen, CLOSED=geschlossen)
|
||||
channel-type.shelly.sensorContact.state.option.OPEN = geöffnet
|
||||
channel-type.shelly.sensorContact.state.option.CLOSED = geschlossen
|
||||
channel-type.shelly.sensorState.label = Gerätestatus
|
||||
channel-type.shelly.sensorState.description = Status der Betriebsbereitschaft (warmup/normal/fault)
|
||||
channel-type.shelly.sensorState.state.option.warmup= Initialisierung
|
||||
channel-type.shelly.sensorState.state.option.normal= Normal
|
||||
channel-type.shelly.sensorState.state.option.fault= Fehler
|
||||
channel-type.shelly.sensorState.state.option.unknown= Unbekannt
|
||||
channel-type.shelly.sensorWakeup.label = Aufwachgrund
|
||||
channel-type.shelly.sensorWakeup.description = Grund des letzten Aufweckens (button/battery/perodice/powerdown/sensor/alarm/ext_power)
|
||||
channel-type.shelly.sensorWakeup.state.option.button = Taste
|
||||
channel-type.shelly.sensorWakeup.state.option.battery = Batterie
|
||||
channel-type.shelly.sensorWakeup.state.option.periodic = Intervall
|
||||
channel-type.shelly.sensorWakeup.state.option.poweron = Betrieb
|
||||
channel-type.shelly.sensorWakeup.state.option.sensor = Statusänderung
|
||||
channel-type.shelly.sensorWakeup.state.option.alarm = Alarm
|
||||
channel-type.shelly.sensorWakeup.state.option.ext_power = Ladegerät verbunden
|
||||
channel-type.shelly.batVoltage.label = Batteriespannung
|
||||
channel-type.shelly.batVoltage.description = Batteriespannung in Volt (V)
|
||||
channel-type.shelly.charger.label = Ladegerät
|
||||
channel-type.shelly.charger.description = ON: Ein Ladegerät ist angeschlossen
|
||||
channel-type.shelly.senseKey.label = IR-Code
|
||||
channel-type.shelly.senseKey.description = IR Code senden im Pronto- oder HEX64-Format
|
||||
channel-type.shelly.deviceName.label = Gerätename
|
||||
channel-type.shelly.deviceName.description = Symbolischer Name des Gerätes (Konfiguration über Shelly App)
|
||||
channel-type.shelly.uptime.label = Laufzeit
|
||||
channel-type.shelly.uptime.description = Anzahl Sekunden seit dem das Gerät mit Strom versorgt wurde
|
||||
channel-type.shelly.heartBeat.label = Letzte Aktivität
|
||||
channel-type.shelly.heartBeat.description = Zeitpunkt der letzten Aktivität. Hierbei kann es sich um einen erfolgreichen API-Aufruf, oder Sensor-Aktualisierung handeln. Dies schließt eine erfolgreiche Netzwerk-Kommunikation ein (WiFi + IP).
|
||||
channel-type.shelly.updateAvailable.label = Firmwareaktualisierung vorhanden
|
||||
channel-type.shelly.updateAvailable.description = ON: Es ist eine neuere Firmwareversion verfügbar (kann mit der Shelly App durchgef¸hrt werden)
|
||||
channel-type.shelly.deviceTemp.label = Gerätetemperatur
|
||||
channel-type.shelly.deviceTemp.description = Temperatur im Gerät. Hohe Temperaturen deuten ggf. auch ein Hitzestau/Installationsproblem hin.
|
||||
channel-type.shelly.lastUpdate.label = Letzte Aktualisierung
|
||||
channel-type.shelly.lastUpdate.description = Zeitstempel der letzten Aktualisierung (lokale Zeitzone)
|
||||
channel-type.shelly.lastEvent.label = Letztes Ereignis
|
||||
channel-type.shelly.lastEvent.description = Typ des letzten Ereignisses (S=kurz, SS=2xkurz, SSS=3xkurz, L=lang)
|
||||
channel-type.shelly.lastEvent.state.option.S = 1x kurz
|
||||
channel-type.shelly.lastEvent.state.option.SS = 2x kurz
|
||||
channel-type.shelly.lastEvent.state.option.SSS = 3x kurz
|
||||
channel-type.shelly.lastEvent.state.option.L = lang
|
||||
channel-type.shelly.lastEvent.state.option.SL = kurz-lang
|
||||
channel-type.shelly.lastEvent.state.option.LS = lang-kurz
|
||||
channel-type.shelly.eventCount.label = Ereigniszähler
|
||||
channel-type.shelly.eventCount.description = Anzahl der empfangenen Ereignisse.
|
||||
channel-type.shelly.eventTrigger.label = Ereignis
|
||||
channel-type.shelly.eventTrigger.description = Signalisiert Ereignisse (Trigger): ROLLER_OPEN=Rollladen geöffnet, ROLLER_CLOSE=Rollladen geschlossen, ROLLER_STOP=Rollladen angehalten
|
||||
channel-type.shelly.eventTrigger.option.ROLLER_OPEN = Rollladen geöffnet
|
||||
channel-type.shelly.eventTrigger.option.ROLLER_CLOSE = Rollladen geschlossen
|
||||
channel-type.shelly.eventTrigger.option.ROLLER_STOP = Rollladen gestoppt
|
||||
channel-type.shelly.alarmTrigger.description = Signalisiert Alarme (Trigger): NONE=kein Alarm, WEAK_SIGNAL=Schlechte WiFi-Verbindung, RESTARTED=Gerät neu gestartet, OVERTEMP=Überhitzung, OVERLOAD=Überlast, OVERPOWER=Maximale Last überschritten, LOAD_ERROR=Lastfehler, LOW_BATTERY=Batterie schwach
|
||||
channel-type.shelly.alarmTrigger.option.NONE = kein Alarm
|
||||
channel-type.shelly.alarmTrigger.option.WEAK_SIGNAL = Schlechte WiFi-Verbindung
|
||||
channel-type.shelly.alarmTrigger.option.RESTARTED = Gerät neu gestartet
|
||||
channel-type.shelly.alarmTrigger.option.OVERTEMP = Überhitzung
|
||||
channel-type.shelly.alarmTrigger.option.OVERLOAD = Überlast
|
||||
channel-type.shelly.alarmTrigger.option.OVERPOWER = Maximale Last überschritten
|
||||
channel-type.shelly.alarmTrigger.option.LOAD_ERROR = Lastfehler
|
||||
channel-type.shelly.alarmTrigger.option.LOW_BATTERY = Batterieladung schwach
|
||||
channel-type.shelly.alarmState.label = Alarmstatus
|
||||
channel-type.shelly.alarmState.description = Typ des Alarms (unknown/none/mild/heavy/test)
|
||||
channel-type.shelly.alarmState.state.option.unknown = Unbekannt
|
||||
channel-type.shelly.alarmState.state.option.none = Kein Alarm
|
||||
channel-type.shelly.alarmState.state.option.mild = Leichte Konzentration
|
||||
channel-type.shelly.alarmState.state.option.heavy = Hohe Konzentration
|
||||
channel-type.shelly.alarmState.state.option.test = Testknopf gedrückt
|
||||
channel-type.shelly.selfTest.label = Selbsttest
|
||||
channel-type.shelly.selfTest.description = Status des Selbsttests
|
||||
channel-type.shelly.selfTest.state.option.pending = ausstehend
|
||||
channel-type.shelly.selfTest.state.option.not_completed = Nicht abgeschlossen
|
||||
channel-type.shelly.selfTest.state.option.running = Test läuft
|
||||
channel-type.shelly.selfTest.state.option.completed = abgeschlossen
|
||||
channel-type.shelly.selfTest.state.option.unknown = unbekannt
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="shelly"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-group-type id="deviceStatus">
|
||||
<label>Device Status</label>
|
||||
<description>Information about the device status</description>
|
||||
<channels>
|
||||
<channel id="alarm" typeId="alarmTrigger"/>
|
||||
<channel id="wifiSignal" typeId="system.signal-strength"/>
|
||||
<channel id="uptime" typeId="uptime"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-type id="alarmTrigger">
|
||||
<kind>trigger</kind>
|
||||
<label>Alarm</label>
|
||||
<description>Alarm Trigger, e.g. weak WiFi Signal detected or over heating.</description>
|
||||
<event>
|
||||
<options>
|
||||
<option value="NONE">None</option>
|
||||
<option value="WEAK_SIGNAL">Weak WiFi</option>
|
||||
<option value="RESTARTED">Device restarted</option>
|
||||
<option value="OVERTEMP">Device is overheating</option>
|
||||
<option value="OVERLOAD">Device is overloaded</option>
|
||||
<option value="OVERPOWER">Device is over max power</option>
|
||||
<option value="LOAD_ERROR">Load error</option>
|
||||
<option value="LOW_BATTERY">Low battery</option>
|
||||
<option value="BATTERY">Wakeup by battery</option>
|
||||
<option value="POWERON">Device was powered on</option>
|
||||
<option value="BUTTON">Button was pressed</option>
|
||||
<option value="SENSOR">Sensor data updated</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
<channel-type id="sensorWakeup" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Wakeup Reason</label>
|
||||
<description>Last reason, which woke up the device</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="button">Button</option>
|
||||
<option value="battery">Battery</option>
|
||||
<option value="periodic">Periodic</option>
|
||||
<option value="poweron">Power-On</option>
|
||||
<option value="sensor">Sensor</option>
|
||||
<option value="alarm">Alarm</option>
|
||||
<option value="ext_power">Charger connected</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="deviceName">
|
||||
<item-type>String</item-type>
|
||||
<label>@text/channel-type.shelly.deviceName.label</label>
|
||||
<description>@text/channel-type.shelly.deviceName.description</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="charger" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Charger Connected</label>
|
||||
<description>ON: Device is charging, OFF: No charger connected</description>
|
||||
<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="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="uptime" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Uptime</label>
|
||||
<description>Number of seconds since the device was powered up</description>
|
||||
<state readOnly="true" pattern="%d %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="heartBeat" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Heartbeat</label>
|
||||
<description>Last time we received a signal from the device (API result or sensor report), This indicates that device
|
||||
communication works on the network layer (WiFi + IP).</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="updateAvailable" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Update available</label>
|
||||
<description>ON: A firmware update is available (use Shelly App to perform update)</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="deviceTemp" advanced="true">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Internal Temperature</label>
|
||||
<description>Internal device temperature, helps to detect overheating due to installation issues</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>CurrentTemperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="selfTest">
|
||||
<item-type>String</item-type>
|
||||
<label>Self Test</label>
|
||||
<description>Self Test Status/Result</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="not_completed">Not completed</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="running">Running</option>
|
||||
<option value="pending">Pending</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,264 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="shelly"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="shellybulb">
|
||||
<label>Shelly Bulb (SHBULB)</label>
|
||||
<description>Shelly Bulb</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="bulbControl"/>
|
||||
<channel-group id="color" typeId="colorSettingsBulb"/>
|
||||
<channel-group id="white" typeId="whiteSettings"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:light"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellybulbduo">
|
||||
<label>Shelly Duo (SHBDUO-1)</label>
|
||||
<description>Shelly Duo</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="duoControl"/>
|
||||
<channel-group id="white" typeId="whiteSettings"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:light"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyvintage">
|
||||
<label>Shelly Vintage (SHVIN-1)</label>
|
||||
<description>Shelly Vintage Light Bulb</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="duoControl"/>
|
||||
<channel-group id="white" typeId="whiteSettingsSimple"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:light"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyrgbw2-color">
|
||||
<label>Shelly RGBW2 Color Mode (SHRGBW2)</label>
|
||||
<description>Shelly RGBW2 Controller - Color Mode</description>
|
||||
<category>Lightbulb</category>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="rgbw2ColorControl"/>
|
||||
<channel-group id="color" typeId="colorSettingsRGBW2"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:rgbw2"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyrgbw2-white">
|
||||
<label>Shelly RGBW2 White Mode (SHRGBW2)</label>
|
||||
<description>Shelly RGBW2 Controller - White Mode</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="rgbw2WhiteControl"/>
|
||||
<channel-group id="channel1" typeId="rgbw2Channel">
|
||||
<label>Channel 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="channel2" typeId="rgbw2Channel">
|
||||
<label>Channel 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="channel3" typeId="rgbw2Channel">
|
||||
<label>Channel 3</label>
|
||||
</channel-group>
|
||||
<channel-group id="channel4" typeId="rgbw2Channel">
|
||||
<label>Channel 4</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:rgbw2"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="bulbControl">
|
||||
<label>Light Control</label>
|
||||
<description>Control your light</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
<channel id="mode" typeId="colorMode"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="duoControl">
|
||||
<label>Light Control</label>
|
||||
<description>Control your light</description>
|
||||
<channels>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="rgbw2ColorControl">
|
||||
<label>Light Control</label>
|
||||
<description>Control your light channels</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="rgbw2WhiteControl">
|
||||
<label>White Control</label>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="colorSettingsBulb">
|
||||
<label>Colors</label>
|
||||
<description>Light colors</description>
|
||||
<channels>
|
||||
<channel id="hsb" typeId="system.color"/>
|
||||
<channel id="full" typeId="colorFull"/>
|
||||
<channel id="red" typeId="colorRed"/>
|
||||
<channel id="green" typeId="colorGreen"/>
|
||||
<channel id="blue" typeId="colorBlue"/>
|
||||
<channel id="white" typeId="colorWhite"/>
|
||||
<channel id="gain" typeId="colorGain"/>
|
||||
<channel id="effect" typeId="colorEffectBulb"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="colorSettingsRGBW2">
|
||||
<label>Colors</label>
|
||||
<description>Light colors</description>
|
||||
<channels>
|
||||
<channel id="hsb" typeId="system.color"/>
|
||||
<channel id="full" typeId="colorFull"/>
|
||||
<channel id="red" typeId="colorRed"/>
|
||||
<channel id="green" typeId="colorGreen"/>
|
||||
<channel id="blue" typeId="colorBlue"/>
|
||||
<channel id="white" typeId="colorWhite"/>
|
||||
<channel id="gain" typeId="colorGain"/>
|
||||
<channel id="effect" typeId="colorEffectRGBW2"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="whiteSettings">
|
||||
<label>White settings</label>
|
||||
<description>Adjust colors when device is in white mode</description>
|
||||
<channels>
|
||||
<channel id="brightness" typeId="whiteBrightness"/>
|
||||
<channel id="temperature" typeId="whiteTemp"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="whiteSettingsSimple">
|
||||
<label>White settings</label>
|
||||
<description>Adjust colors when device is in white mode</description>
|
||||
<channels>
|
||||
<channel id="brightness" typeId="whiteBrightness"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="rgbw2Channel">
|
||||
<label>Control</label>
|
||||
<description>Switch channel and adjust settings</description>
|
||||
<channels>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
<channel id="brightness" typeId="whiteBrightness"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
|
||||
<channel-type id="colorMode">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Mode (ON=color, OFF=white)</label>
|
||||
<description>ON: Device is in color mode, OFF: Device is in White Mode</description>
|
||||
</channel-type>
|
||||
<channel-type id="colorFull">
|
||||
<item-type>String</item-type>
|
||||
<label>Full Color</label>
|
||||
<description>Setting this channel sets the selected color to 255, all others to 0</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="blue">Blue</option>
|
||||
<option value="yellow">Yellow</option>
|
||||
<option value="white">White</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="colorRed" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Red</label>
|
||||
<description>red, 0..255, only in Color Mode</description>
|
||||
<state min="0" max="255" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="colorGreen" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Green</label>
|
||||
<description>green, 0..255, applies Color Mode</description>
|
||||
<state min="0" max="255" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="colorBlue" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Blue</label>
|
||||
<description>blue, 0..255, only in Color Mode</description>
|
||||
<state min="0" max="255" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="colorWhite" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>White</label>
|
||||
<description>white, 0..255, applies in Color Mode</description>
|
||||
<state min="0" max="255" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="colorGain">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Gain</label>
|
||||
<description>gain for all channels, 0..100, applies in Color Mode</description>
|
||||
<state min="0" max="100" step="1" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="whiteTemp">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Light Temperature</label>
|
||||
<description>Light Temperature 3000..6500K</description>
|
||||
<state min="3000" max="6500" step="10" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="whiteBrightness">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Brightness</label>
|
||||
<description>Brightness: 0..100%</description>
|
||||
<category>DimmableLight</category>
|
||||
</channel-type>
|
||||
<channel-type id="colorEffectBulb">
|
||||
<item-type>Number</item-type>
|
||||
<label>Effect</label>
|
||||
<description>Currently applied effect, description: 0: Off, 1: Meteor Shower, 2: Gradual Change, 3: Breath, 4: Flash,
|
||||
5: On/Off Gradual, 6: Red/Green Change</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Meteor Shower</option>
|
||||
<option value="2">Gradual Change</option>
|
||||
<option value="3">Breath</option>
|
||||
<option value="4">Flash</option>
|
||||
<option value="5">On/Off Gradual</option>
|
||||
<option value="6">Red/Green Change</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="colorEffectRGBW2">
|
||||
<item-type>Number</item-type>
|
||||
<label>Effect</label>
|
||||
<description>Currently applied effect, description: 0: Off, 1: Meteor Shower, 2: Gradual Change, 3: Flash</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="1">Meteor Shower</option>
|
||||
<option value="2">Gradual Change</option>
|
||||
<option value="3">Flash</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,528 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="shelly"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="shelly1">
|
||||
<label>Shelly1 (SHSW-1)</label>
|
||||
<description>Shelly1 device with single relay</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="sensors" typeId="externalSensors"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly1pm">
|
||||
<label>Shelly1PM (SHSW-PM)</label>
|
||||
<description>Shelly1PM device with single relay and power meter</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="sensors" typeId="externalSensors"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyem">
|
||||
<label>ShellyEM (SHEM)</label>
|
||||
<description>Shelly EM device with single relay and power meter</description>
|
||||
<channel-groups>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>Power Meter 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>Power Meter 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<!-- Shelly 3EM - device reports wrong service name: shellyem3, see README -->
|
||||
<thing-type id="shellyem3">
|
||||
<label>Shelly 3EM (SHEM-3)</label>
|
||||
<description>Shelly 3EM device with 3 power meters and a relay (thing name is shellyem3, see README)</description>
|
||||
<channel-groups>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>Power Meter 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>Power Meter 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter3" typeId="meter">
|
||||
<label>Power Meter 3</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay" typeId="relayChannel"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly2-relay">
|
||||
<label>Shelly2 Relay (SHSW-21)</label>
|
||||
<description>Shelly2 device with two relays</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>Relay 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>Relay 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
<thing-type id="shelly2-roller">
|
||||
<label>Shelly2 Roller (SHSW-21)</label>
|
||||
<description>Shelly2 in Roller Mode</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="roller" typeId="rollerControl"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:roller"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly25-relay">
|
||||
<label>Shelly2.5 Relay (SHSW-25)</label>
|
||||
<description>Shelly2.5 device with two relays, 2 meters</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>Relay 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>Power Meter 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>Relay 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>Power Meter 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
<thing-type id="shelly25-roller">
|
||||
<label>Shelly2.5 Roller (SHSW-25)</label>
|
||||
<description>Shelly2 in Roller Mode</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="roller" typeId="rollerControl"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:roller"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shelly4pro">
|
||||
<label>Shelly4 Pro Relay (SHSW-44)</label>
|
||||
<description>Shelly4Pro device with 4 relays</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="relay1" typeId="relayChannel">
|
||||
<label>Relay 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter1" typeId="meter">
|
||||
<label>Power Meter 1</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay2" typeId="relayChannel">
|
||||
<label>Relay 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter2" typeId="meter">
|
||||
<label>Power Meter 2</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay3" typeId="relayChannel">
|
||||
<label>Relay 3</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter3" typeId="meter">
|
||||
<label>Power Meter 3</label>
|
||||
</channel-group>
|
||||
<channel-group id="relay4" typeId="relayChannel">
|
||||
<label>Relay 4</label>
|
||||
</channel-group>
|
||||
<channel-group id="meter4" typeId="meter">
|
||||
<label>Power Meter 4</label>
|
||||
</channel-group>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplug">
|
||||
<label>Shelly Plug</label>
|
||||
<description>Shelly Plug device with relay and power meter</description>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannelPlug"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyplugs">
|
||||
<label>Shelly Plug-S (SHPLG-S)</label>
|
||||
<description>Shelly Plug-S with relay, meter and LED control</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="relayChannelPlug"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydimmer">
|
||||
<label>Shelly Dimmer (SHDM-1)</label>
|
||||
<description>Shelly Dimmer</description>
|
||||
<category>DimmableLight</category>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="dimmerChannel"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:dimmer"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydimmer2">
|
||||
<label>Shelly Dimmer 2 (SHDM-2)</label>
|
||||
<description>Shelly Dimmer 2</description>
|
||||
<category>DimmableLight</category>
|
||||
<channel-groups>
|
||||
<channel-group id="relay" typeId="dimmerChannel"/>
|
||||
<channel-group id="meter" typeId="meter"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:dimmer"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyix3">
|
||||
<label>Shelly ix3 (SHIX3-1)</label>
|
||||
<description>Shelly i3 Device with 3 inputs</description>
|
||||
<channel-groups>
|
||||
<channel-group id="status1" typeId="ix3Channel"/>
|
||||
<channel-group id="status2" typeId="ix3Channel"/>
|
||||
<channel-group id="status3" typeId="ix3Channel"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:relay"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="relayChannel">
|
||||
<label>Relay</label>
|
||||
<description>A Shelly relay channel</description>
|
||||
<channels>
|
||||
<channel id="output" typeId="system.power"/>
|
||||
<channel id="input" typeId="inputState"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="relayChannelPlug">
|
||||
<label>Relay</label>
|
||||
<description>A Shelly relay channel</description>
|
||||
<channels>
|
||||
<channel id="output" typeId="system.power"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
<channel id="timerActive" typeId="timerActive"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="dimmerChannel">
|
||||
<label>Dimmer</label>
|
||||
<description>A Shelly Dimmer channel</description>
|
||||
<channels>
|
||||
<channel id="brightness" typeId="dimmerBrightness"/>
|
||||
<channel id="input1" typeId="inputState1"/>
|
||||
<channel id="input2" typeId="inputState2"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="autoOn" typeId="timerAutoOn"/>
|
||||
<channel id="autoOff" typeId="timerAutoOff"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="ix3Channel">
|
||||
<label>Input</label>
|
||||
<description>Input Status</description>
|
||||
<channels>
|
||||
<channel id="input" typeId="inputState"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="lastEvent" typeId="lastEvent"/>
|
||||
<channel id="eventCount" typeId="eventCount"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="rollerControl">
|
||||
<label>Roller Control</label>
|
||||
<description>Controlling the roller mode</description>
|
||||
<channels>
|
||||
<channel id="control" typeId="rollerShutter"/>
|
||||
<channel id="rollerpos" typeId="rollerPosition"/>
|
||||
<channel id="state" typeId="rollerState"/>
|
||||
<channel id="stopReason" typeId="rollerStop"/>
|
||||
<channel id="input1" typeId="inputState1"/>
|
||||
<channel id="input2" typeId="inputState2"/>
|
||||
<channel id="event" typeId="eventTrigger"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="meter">
|
||||
<label>Power Meter</label>
|
||||
<description>Power consumption for the relay</description>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="externalSensors">
|
||||
<label>External Sensors</label>
|
||||
<description>Temperatures from external sensors connected to the optional Addon</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-type id="outputName">
|
||||
<item-type>String</item-type>
|
||||
<label>@text/channel-type.shelly.outputName.label</label>
|
||||
<description>@text/channel-type.shelly.outputName.description</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="timerAutoOn" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Auto-ON Timer</label>
|
||||
<description>ON: After the output was turned off it turns on automatically after xx seconds; 0 disables the timer</description>
|
||||
<state min="0" step="1" pattern="%d %unit%" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="timerAutoOff" advanced="true">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Auto-OFF Timer</label>
|
||||
<description>ON: After the output was turned on it turns off automatically after xx seconds; 0 disables the timer</description>
|
||||
<state min="0" step="1" pattern="%d %unit%" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="timerActive" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Auto ON/OFF timer active</label>
|
||||
<description>ON: A timer is active, OFF: no timer active</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="rollerShutter">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Roller Control (0=open, 100=closed))</label>
|
||||
<description>Controls the roller</description>
|
||||
<category>Blinds</category>
|
||||
</channel-type>
|
||||
<channel-type id="rollerPosition" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Roller Position (100=open, 0=closed)</label>
|
||||
<description>Position the roller (100..0 in %, where 100%=open, 0%=closed)</description>
|
||||
<state readOnly="false" min="0" max="100"/>
|
||||
</channel-type>
|
||||
<channel-type id="rollerState">
|
||||
<item-type>String</item-type>
|
||||
<label>State</label>
|
||||
<description>State of the roller (open/close/stop)</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="open">opening</option>
|
||||
<option value="close">closing</option>
|
||||
<option value="stop">stopped</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="rollerStop">
|
||||
<item-type>String</item-type>
|
||||
<label>Roller stop reason</label>
|
||||
<description>Last cause for stopping: normal, safety switch, obstacle</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="normal">normal</option>
|
||||
<option value="safety_switch">safety switch</option>
|
||||
<option value="obstacle">obstacle detected</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="rollerDirection">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Roller Direction</label>
|
||||
<description>Last direction: open or close</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="open">open</option>
|
||||
<option value="close">close</option>
|
||||
<option value="stop">stopped</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="inputState">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Input</label>
|
||||
<description>Input/Button state</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="inputState1" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Input #1</label>
|
||||
<description>Input/Button state #1</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="inputState2" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Input #2</label>
|
||||
<description>Input/Button state #2</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="dimmerBrightness">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Brightness</label>
|
||||
<description>Light brightness in %</description>
|
||||
<category>DimmableLight</category>
|
||||
</channel-type>
|
||||
<channel-type id="meterWatts">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Watt</label>
|
||||
<description>Current power consumption in Watt</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterAccuWatts" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Accumulated Watt</label>
|
||||
<description>Accumulated current power consumption in Watt from all meters</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterAccuTotal" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Accumulated Total</label>
|
||||
<description>Accumulated total power consumption in kw/h from all meters</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterAccuReturned" advanced="true">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Accumulated Returned</label>
|
||||
<description>Accumulated returned power consumption in kw/h from all meters</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterReactive">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Reactive Watt</label>
|
||||
<description>Instantaneous reactive power in Watts (W)</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="lastPower1" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Last Power #1</label>
|
||||
<description>Last power consumption #1 - one rounded minute</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="lastPower2" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Last Power #2</label>
|
||||
<description>Last power consumption #2 - one rounded minute</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="lastPower3" advanced="true">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Last Power #3</label>
|
||||
<description>Last power consumption #3 - one rounded minute</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterTotal">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Energy</label>
|
||||
<description>Total power consumption in kw/h</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterReturned">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Total Returned Energy (kw/h)</label>
|
||||
<description>Total returned energy in kw/h</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterVoltage">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Voltage</label>
|
||||
<description>RMS voltage, Volts </description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterCurrent">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Current</label>
|
||||
<description>Current in A </description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="meterPowerFactor">
|
||||
<item-type>Number</item-type>
|
||||
<label>Power Factor</label>
|
||||
<description></description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="timestamp">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Update</label>
|
||||
<description>Timestamp of last measurement</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="ledStatusDisable">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Disable Status LED</label>
|
||||
<description>Disable LED indication for network status</description>
|
||||
</channel-type>
|
||||
<channel-type id="ledPowerDisable">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Disable Power LED</label>
|
||||
<description>Disable LED indication for output status</description>
|
||||
</channel-type>
|
||||
<channel-type id="lastUpdate" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Update</label>
|
||||
<description>Timestamp of last status update</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="eventTrigger">
|
||||
<kind>trigger</kind>
|
||||
<label>Event</label>
|
||||
<description>Event Trigger, e.g. button on/off, Output on/off</description>
|
||||
<event>
|
||||
<options>
|
||||
<option value="ROLLER_OPEN">Roller is open</option>
|
||||
<option value="ROLLER_CLOSE">Roller is closed</option>
|
||||
<option value="ROLLER_STOP">Roller has stopped</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,347 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="shelly"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="shellyht">
|
||||
<label>Shelly H&T (SHHT-1)</label>
|
||||
<description>Shelly H&T Sensor</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="htSensor"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellysmoke">
|
||||
<label>Shelly Smoke</label>
|
||||
<description>Shelly Smoke Sensor (battery powered)</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="smokeSensor"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellygas">
|
||||
<label>Shelly GAS (SHGS-1)</label>
|
||||
<description>Shelly Gas Sensor</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="gasSensor"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:basic"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyflood">
|
||||
<label>Shelly Flood (SHWT-1)</label>
|
||||
<description>Shelly Flood Sensor (battery powered)</description>
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="floodSensor"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydw">
|
||||
<label>Shelly Door/Window (SHDW-1)</label>
|
||||
<description>Shelly Door/Window Sensor (battery powered)</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="doorWinSensors"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellydw2">
|
||||
<label>Shelly Door/Window (SHDW-2)</label>
|
||||
<description>Shelly Door/Window 2 Sensor (battery powered)</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="sensors" typeId="doorWinSensors"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellysense">
|
||||
<label>Shelly Sense (SHSEN-1)</label>
|
||||
<description>Shelly Sense Remote IR Controller</description>
|
||||
<channel-groups>
|
||||
<channel-group id="control" typeId="senseControl"/>
|
||||
<channel-group id="sensors" typeId="senseSensors"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellybutton1">
|
||||
<label>Shelly Button 1 (SHBTN-1)</label>
|
||||
<description>Shelly Button 1 (battery powered)</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="status" typeId="buttonState"/>
|
||||
<channel-group id="battery" typeId="batteryStatus"/>
|
||||
<channel-group id="device" typeId="deviceStatus"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:shelly:battery"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="htSensor">
|
||||
<label>Sensor Data</label>
|
||||
<description>Data from the HT Sensor</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="floodSensor">
|
||||
<label>Sensor Data</label>
|
||||
<description>Data from the Flood Sensor</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="smokeSensor">
|
||||
<label>Sensor Data</label>
|
||||
<description>Data from the Flood Sensor</description>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="gasSensor">
|
||||
<label>Sensor Data</label>
|
||||
<description>Data from the Gas Sensor</description>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="doorWinSensors">
|
||||
<label>Sensors</label>
|
||||
<description>Data from the sensors</description>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="senseSensors">
|
||||
<label>Sensors</label>
|
||||
<description>Data from the Sense sensors</description>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="batteryStatus">
|
||||
<label>Battery Status</label>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="senseControl">
|
||||
<label>Sense Control</label>
|
||||
<description>Sense Settings</description>
|
||||
<channels>
|
||||
<channel id="key" typeId="senseKey"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="buttonState">
|
||||
<label>Button State</label>
|
||||
<description>Status of the Button</description>
|
||||
<channels>
|
||||
<channel id="input" typeId="inputState"/>
|
||||
<channel id="button" typeId="system.button"/>
|
||||
<channel id="lastEvent" typeId="lastEvent"/>
|
||||
<channel id="eventCount" typeId="eventCount"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
|
||||
<channel-type id="sensorContact">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Contact</label>
|
||||
<description>State of the contact: open/close</description>
|
||||
<category>Contact</category>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="OPEN">Open</option>
|
||||
<option value="CLOSED">Closed</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorState">
|
||||
<item-type>String</item-type>
|
||||
<label>Sensor State</label>
|
||||
<description>Sensor State: normal</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="warmup">Warm-Up</option>
|
||||
<option value="normal">Normal</option>
|
||||
<option value="fault">Fault</option>
|
||||
<option value="unknown">unknown</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="alarmState">
|
||||
<item-type>String</item-type>
|
||||
<label>Alarm State</label>
|
||||
<description>Alarm State: normal</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="unknown">Unknown</option>
|
||||
<option value="none">None</option>
|
||||
<option value="mild">Mild</option>
|
||||
<option value="heavy">Heavy</option>
|
||||
<option value="test">Test</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="lastEvent">
|
||||
<item-type>String</item-type>
|
||||
<label>Event</label>
|
||||
<description>Event Type</description>
|
||||
<state pattern="%s" readOnly="true">
|
||||
<options>
|
||||
<option value="S">Short push</option>
|
||||
<option value="SS">Double-Short push</option>
|
||||
<option value="SSS">Triple-Short push</option>
|
||||
<option value="L">Long push</option>
|
||||
<option value="SL">Short-Long push</option>
|
||||
<option value="LS">Long-Short push</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="eventCount">
|
||||
<item-type>Number</item-type>
|
||||
<label>Event Count</label>
|
||||
<description>Event Count</description>
|
||||
<state pattern="%d" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sensorTemp">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Temperature from the sensor</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>CurrentTemperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorExtTemp">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<description>Temperature from the external sensor</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>CurrentTemperature</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorExtHum">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<description>Relative humidity in % (0..100%) from external sensor</description>
|
||||
<category>Humidity</category>
|
||||
<tags>
|
||||
<tag>CurrentHumidity</tag>
|
||||
</tags>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorHumidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<description>Relative humidity in % (0..100%)</description>
|
||||
<tags>
|
||||
<tag>CurrentHumidity</tag>
|
||||
</tags>
|
||||
<state readOnly="true" min="0" max="100" pattern="%f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="sensorFlood">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Flood Alarm</label>
|
||||
<description>ON: Indicates flood condition / water detected</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorSmoke">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Smoke Alarm</label>
|
||||
<description>ON: Indicates smoke detection</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorLux">
|
||||
<item-type>Number:Illuminance</item-type>
|
||||
<label>Lux</label>
|
||||
<description>Brightness from the sensor (Lux)</description>
|
||||
<state readOnly="true" pattern="%f %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorIllumination">
|
||||
<item-type>String</item-type>
|
||||
<label>Illumination</label>
|
||||
<description>Current illumination: dark/twilight/bright</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="twilight">Twilight</option>
|
||||
<option value="bright">Bright</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorVibration">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Vibration</label>
|
||||
<description>ON: Vibration detected</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorTilt">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Tilt</label>
|
||||
<description>Tilt in degrees (requires calibration)</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorPPM">
|
||||
<item-type>Number:Density</item-type>
|
||||
<label>Concentration</label>
|
||||
<description>Gas concentration in ppm</description>
|
||||
<state readOnly="true" pattern="%d %unit%">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorValve">
|
||||
<item-type>String</item-type>
|
||||
<label>Valve</label>
|
||||
<description>Current valve status: closed/opened/not_connected/failure/closing/opening/checking</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="closed">closed</option>
|
||||
<option value="opened">opened</option>
|
||||
<option value="not_connected">not connected</option>
|
||||
<option value="failure">failure</option>
|
||||
<option value="closing">closing</option>
|
||||
<option value="opening">opening</option>
|
||||
<option value="checking">checking</option>
|
||||
<option value="unknown">UNKNOWN</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sensorError">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Error</label>
|
||||
<description>Only valid in case of error</description>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="senseKey">
|
||||
<item-type>String</item-type>
|
||||
<label>IR Key to Send</label>
|
||||
<description>Send a defined key code</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="shelly"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="shellydevice">
|
||||
<label>Shelly Device</label>
|
||||
<description>A password protected or unknown device.</description>
|
||||
|
||||
<config-description>
|
||||
<parameter name="userId" type="text" required="true">
|
||||
<label>UserID</label>
|
||||
<description>User ID for API access.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<label>Password</label>
|
||||
<description>Password for API access.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="deviceIp" type="text" required="true">
|
||||
<label>IP Address</label>
|
||||
<description>IP Address of the Shelly device.</description>
|
||||
<context>network-address</context>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shellyunknown">
|
||||
<label>Unknown Shelly Device</label>
|
||||
<description>This device is currently not supported</description>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user