[somfytahoma] added support for the control over the LAN (local mode) (#13411)
* [somfytahoma] added support for the control over the LAN (local mode) Signed-off-by: Ondrej Pecta <opecta@gmail.com>
This commit is contained in:
parent
48b471a313
commit
45052a810c
@ -45,7 +45,7 @@ Any home automation system based on the OverKiz API is potentially supported.
|
|||||||
- water heater system (monitor and control)
|
- water heater system (monitor and control)
|
||||||
- Yutaki heat pump consisting of heat pump, heating control and hot water tank (controls and a lot of states; it is tested with components RAS-4WHNPE, RWM-4.ONE, DHWT-300S-3.0H2E)
|
- Yutaki heat pump consisting of heat pump, heating control and hot water tank (controls and a lot of states; it is tested with components RAS-4WHNPE, RWM-4.ONE, DHWT-300S-3.0H2E)
|
||||||
|
|
||||||
Both Somfy Tahoma and Somfy Connexoon gateways have been confirmed working.
|
Both Somfy Tahoma and Somfy Connexoon gateways have been confirmed working in the cloud mode.
|
||||||
|
|
||||||
## Discovery
|
## Discovery
|
||||||
|
|
||||||
@ -59,7 +59,41 @@ If you are missing some device, check the debug log during the discovery and cre
|
|||||||
|
|
||||||
## Thing Configuration
|
## Thing Configuration
|
||||||
|
|
||||||
To retrieve thing configuration and url parameter, just add the automatically discovered device from your inbox and copy its values from thing edit page. (the url parameter is visible on edit page only)
|
### bridge
|
||||||
|
|
||||||
|
| Parameter | Parameter ID | Required/Optional | Description |
|
||||||
|
|----------------|---------------|-------------------|--------------------------------------------------------------------------------------|
|
||||||
|
| Cloud portal | cloudPortal | Optional | Cloud portal to connect to |
|
||||||
|
| Email address | email | Required | Email address for the portal |
|
||||||
|
| Password | password | Required | Password for the portal |
|
||||||
|
| Refresh | refresh | Optional | Refresh time for polling events (in seconds) |
|
||||||
|
| Status timeout | statusTimeout | Optional | Reconciliation timeout after which the status is refreshed (in seconds) |
|
||||||
|
| Retries | retries | Optional | Specifies the number of retries when command execution |
|
||||||
|
| Retry delay | retryDelay | Optional | Delay in milliseconds between subsequent retries after a command failure |
|
||||||
|
| Developer mode | devMode | Optional | Enables the direct control of your devices over the lan using the local API endpoint |
|
||||||
|
| Gateway IP | ip | Optional | Local IP address of gateway, relevant only if developer mode is enabled |
|
||||||
|
| Gateway PIN | pin | Optional | Gateway PIN in format ABCD-EFGH-IJKL, relevant only if developer mode is enabled |
|
||||||
|
| Local token | token | Optional | Token for local communication, relevant only if developer mode is enabled |
|
||||||
|
|
||||||
|
For more information about the developer mode please see https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode.
|
||||||
|
If the gateway ip or pin are not provided, the binding tries to detect it automatically and saves it into the configuration.
|
||||||
|
If the local token is not provided, the binding creates the local token automatically and saves it into the configuration.
|
||||||
|
Please note that the action groups (scenarios) control does not work in local mode due to missing support in the gateway firmware.
|
||||||
|
The gateway support for the developer mode is limited as well, so far Connexoon gateways do not support the developer mode.
|
||||||
|
|
||||||
|
### gateway
|
||||||
|
|
||||||
|
| Parameter | Parameter ID | Required/Optional | Description |
|
||||||
|
|------------|--------------|-------------------|-------------------------------------------|
|
||||||
|
| Gateway id | id | Required | ID of your gateway (sometimes called pin) |
|
||||||
|
|
||||||
|
### other devices
|
||||||
|
|
||||||
|
| Parameter | Parameter ID | Required/Optional | Description |
|
||||||
|
|------------|--------------|-------------------|------------------------------|
|
||||||
|
| Device URL | url | Required | The identifier of the device |
|
||||||
|
|
||||||
|
To retrieve the url parameter or gateway id, just add the automatically discovered device from your inbox and copy its values from thing edit page. (the url parameter is visible on edit page only)
|
||||||
Please see the example below.
|
Please see the example below.
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|||||||
@ -291,7 +291,6 @@ public class SomfyTahomaBindingConstants {
|
|||||||
public static final String POWER_HEAT_PUMP = "power_heatpump";
|
public static final String POWER_HEAT_PUMP = "power_heatpump";
|
||||||
public static final String POWER_HEAT_ELEC = "power_heatelec";
|
public static final String POWER_HEAT_ELEC = "power_heatelec";
|
||||||
public static final String WATER_HEATER_MODE = "mode";
|
public static final String WATER_HEATER_MODE = "mode";
|
||||||
public static final String WATER_TEMPERATURE = "water_temperature";
|
|
||||||
public static final String ELECTRIC_BOOSTER_OPERATING_TIME = "electric_booster_operating_time";
|
public static final String ELECTRIC_BOOSTER_OPERATING_TIME = "electric_booster_operating_time";
|
||||||
public static final String SHOWERS = "showers";
|
public static final String SHOWERS = "showers";
|
||||||
|
|
||||||
@ -367,12 +366,15 @@ public class SomfyTahomaBindingConstants {
|
|||||||
public static final String API_BASE_URL = "/enduser-mobile-web/enduserAPI/";
|
public static final String API_BASE_URL = "/enduser-mobile-web/enduserAPI/";
|
||||||
public static final String EVENTS_URL = "events/";
|
public static final String EVENTS_URL = "events/";
|
||||||
public static final String SETUP_URL = "setup/";
|
public static final String SETUP_URL = "setup/";
|
||||||
|
|
||||||
|
public static final String CONFIG_URL = "config/";
|
||||||
public static final String GATEWAYS_URL = SETUP_URL + "gateways/";
|
public static final String GATEWAYS_URL = SETUP_URL + "gateways/";
|
||||||
public static final String DEVICES_URL = SETUP_URL + "devices/";
|
public static final String DEVICES_URL = SETUP_URL + "devices/";
|
||||||
public static final String REFRESH_URL = DEVICES_URL + "states/refresh";
|
public static final String REFRESH_URL = DEVICES_URL + "states/refresh";
|
||||||
public static final String EXEC_URL = "exec/";
|
public static final String EXEC_URL = "exec/";
|
||||||
public static final String DELETE_URL = EXEC_URL + "current/setup/";
|
public static final String DELETE_URL = EXEC_URL + "current/setup/";
|
||||||
public static final String TAHOMA_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36";
|
public static final String LOCAL_TOKENS_URL = "/local/tokens/";
|
||||||
|
public static final String TAHOMA_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.82 Safari/537.36";
|
||||||
public static final int TAHOMA_TIMEOUT = 5;
|
public static final int TAHOMA_TIMEOUT = 5;
|
||||||
public static final String UNAUTHORIZED = "Not logged in";
|
public static final String UNAUTHORIZED = "Not logged in";
|
||||||
public static final int TYPE_NONE = 0;
|
public static final int TYPE_NONE = 0;
|
||||||
@ -381,9 +383,13 @@ public class SomfyTahomaBindingConstants {
|
|||||||
public static final int TYPE_STRING = 3;
|
public static final int TYPE_STRING = 3;
|
||||||
public static final int TYPE_BOOLEAN = 6;
|
public static final int TYPE_BOOLEAN = 6;
|
||||||
public static final String UNAVAILABLE = "unavailable";
|
public static final String UNAVAILABLE = "unavailable";
|
||||||
public static final String AUTHENTICATION_CHALLENGE = "HTTP protocol violation: Authentication challenge without WWW-Authenticate header";
|
public static final String TEMPORARILY_BANNED = "Too many attempts with an invalid token, temporarily banned.";
|
||||||
public static final String AUTHENTICATION_OAUTH_GRANT_ERROR = "Provided Authorization Grant is invalid.";
|
|
||||||
public static final String TOO_MANY_REQUESTS = "Too many requests, try again later";
|
public static final String TOO_MANY_REQUESTS = "Too many requests, try again later";
|
||||||
|
public static final String EVENT_LISTENER_TIMEOUT = "No registered event listener";
|
||||||
|
public static final String AUTHENTICATION_OAUTH_GRANT_ERROR = "Provided Authorization Grant is invalid.";
|
||||||
|
public static final String AUTHENTICATION_OAUTH_INVALID_GRANT = "error.invalid.grant";
|
||||||
|
public static final String OPENHAB_TOKEN = "openHAB token";
|
||||||
public static final int SUSPEND_TIME = 120;
|
public static final int SUSPEND_TIME = 120;
|
||||||
public static final int RECONCILIATION_TIME = 600;
|
public static final int RECONCILIATION_TIME = 600;
|
||||||
|
|
||||||
@ -449,6 +455,7 @@ public class SomfyTahomaBindingConstants {
|
|||||||
public static final String RADIO_PART_BATTERY_STATE = "io:MaintenanceRadioPartBatteryState";
|
public static final String RADIO_PART_BATTERY_STATE = "io:MaintenanceRadioPartBatteryState";
|
||||||
public static final String SENSOR_PART_BATTERY_STATE = "io:MaintenanceSensorPartBatteryState";
|
public static final String SENSOR_PART_BATTERY_STATE = "io:MaintenanceSensorPartBatteryState";
|
||||||
public static final String ZWAVE_SET_POINT_TYPE_STATE = "zwave:SetPointTypeState";
|
public static final String ZWAVE_SET_POINT_TYPE_STATE = "zwave:SetPointTypeState";
|
||||||
|
public static final String LUMINANCE_STATE = "core:LuminanceState";
|
||||||
|
|
||||||
// supported uiClasses
|
// supported uiClasses
|
||||||
public static final String CLASS_ROLLER_SHUTTER = "RollerShutter";
|
public static final String CLASS_ROLLER_SHUTTER = "RollerShutter";
|
||||||
|
|||||||
@ -31,6 +31,10 @@ public class SomfyTahomaConfig {
|
|||||||
private int statusTimeout = 300;
|
private int statusTimeout = 300;
|
||||||
private int retries = 10;
|
private int retries = 10;
|
||||||
private int retryDelay = 1000;
|
private int retryDelay = 1000;
|
||||||
|
private boolean devMode = false;
|
||||||
|
private String pin = "";
|
||||||
|
private String ip = "";
|
||||||
|
private String token = "";
|
||||||
|
|
||||||
public String getCloudPortal() {
|
public String getCloudPortal() {
|
||||||
return cloudPortal;
|
return cloudPortal;
|
||||||
@ -60,6 +64,22 @@ public class SomfyTahomaConfig {
|
|||||||
return retryDelay;
|
return retryDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDevMode() {
|
||||||
|
return devMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPin() {
|
||||||
|
return pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIp() {
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
public void setCloudPortal(String cloudPortal) {
|
public void setCloudPortal(String cloudPortal) {
|
||||||
this.cloudPortal = cloudPortal;
|
this.cloudPortal = cloudPortal;
|
||||||
}
|
}
|
||||||
@ -79,4 +99,16 @@ public class SomfyTahomaConfig {
|
|||||||
public void setRetryDelay(int retryDelay) {
|
public void setRetryDelay(int retryDelay) {
|
||||||
this.retryDelay = retryDelay;
|
this.retryDelay = retryDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPin(String pin) {
|
||||||
|
this.pin = pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIp(String ip) {
|
||||||
|
this.ip = ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
protected void stopBackgroundDiscovery() {
|
protected void stopBackgroundDiscovery() {
|
||||||
logger.debug("Stopping SomfyTahoma background discovery");
|
logger.debug("Stopping SomfyTahoma background discovery");
|
||||||
ScheduledFuture<?> localDiscoveryJob = discoveryJob;
|
ScheduledFuture<?> localDiscoveryJob = discoveryJob;
|
||||||
if (localDiscoveryJob != null && !localDiscoveryJob.isCancelled()) {
|
if (localDiscoveryJob != null) {
|
||||||
localDiscoveryJob.cancel(true);
|
localDiscoveryJob.cancel(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,6 +137,8 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
gatewayDiscovered(gw);
|
gatewayDiscovered(gw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// local mode does not have action groups
|
||||||
|
if (!localBridgeHandler.isDevModeReady()) {
|
||||||
List<SomfyTahomaActionGroup> actions = localBridgeHandler.listActionGroups();
|
List<SomfyTahomaActionGroup> actions = localBridgeHandler.listActionGroups();
|
||||||
|
|
||||||
for (SomfyTahomaActionGroup group : actions) {
|
for (SomfyTahomaActionGroup group : actions) {
|
||||||
@ -144,7 +146,8 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
String label = group.getLabel();
|
String label = group.getLabel();
|
||||||
|
|
||||||
// actiongroups use oid as deviceURL
|
// actiongroups use oid as deviceURL
|
||||||
actionGroupDiscovered(label, oid, oid);
|
actionGroupDiscovered(label, oid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Cannot start discovery since the bridge is not online!");
|
logger.debug("Cannot start discovery since the bridge is not online!");
|
||||||
@ -154,7 +157,8 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
private void discoverDevice(SomfyTahomaDevice device, SomfyTahomaSetup setup) {
|
private void discoverDevice(SomfyTahomaDevice device, SomfyTahomaSetup setup) {
|
||||||
logger.debug("url: {}", device.getDeviceURL());
|
logger.debug("url: {}", device.getDeviceURL());
|
||||||
String place = getPlaceLabel(setup, device.getPlaceOID());
|
String place = getPlaceLabel(setup, device.getPlaceOID());
|
||||||
switch (device.getUiClass()) {
|
String widget = device.getDefinition().getWidgetName();
|
||||||
|
switch (device.getDefinition().getUiClass()) {
|
||||||
case CLASS_AWNING:
|
case CLASS_AWNING:
|
||||||
// widget: PositionableHorizontalAwning
|
// widget: PositionableHorizontalAwning
|
||||||
// widget: DynamicAwning
|
// widget: DynamicAwning
|
||||||
@ -180,7 +184,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
deviceDiscovered(device, THING_TYPE_GARAGEDOOR, place);
|
deviceDiscovered(device, THING_TYPE_GARAGEDOOR, place);
|
||||||
break;
|
break;
|
||||||
case CLASS_LIGHT:
|
case CLASS_LIGHT:
|
||||||
if ("DimmerLight".equals(device.getWidget()) || "DynamicLight".equals(device.getWidget())) {
|
if ("DimmerLight".equals(widget) || "DynamicLight".equals(widget)) {
|
||||||
// widget: DimmerLight
|
// widget: DimmerLight
|
||||||
// widget: DynamicLight
|
// widget: DynamicLight
|
||||||
deviceDiscovered(device, THING_TYPE_DIMMER_LIGHT, place);
|
deviceDiscovered(device, THING_TYPE_DIMMER_LIGHT, place);
|
||||||
@ -243,7 +247,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
if (device.getDeviceURL().startsWith("internal:")) {
|
if (device.getDeviceURL().startsWith("internal:")) {
|
||||||
// widget: TSKAlarmController
|
// widget: TSKAlarmController
|
||||||
deviceDiscovered(device, THING_TYPE_INTERNAL_ALARM, place);
|
deviceDiscovered(device, THING_TYPE_INTERNAL_ALARM, place);
|
||||||
} else if ("MyFoxAlarmController".equals(device.getWidget())) {
|
} else if ("MyFoxAlarmController".equals(widget)) {
|
||||||
// widget: MyFoxAlarmController
|
// widget: MyFoxAlarmController
|
||||||
deviceDiscovered(device, THING_TYPE_MYFOX_ALARM, place);
|
deviceDiscovered(device, THING_TYPE_MYFOX_ALARM, place);
|
||||||
} else {
|
} else {
|
||||||
@ -256,9 +260,9 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CLASS_HEATING_SYSTEM:
|
case CLASS_HEATING_SYSTEM:
|
||||||
if ("SomfyThermostat".equals(device.getWidget())) {
|
if ("SomfyThermostat".equals(widget)) {
|
||||||
deviceDiscovered(device, THING_TYPE_THERMOSTAT, place);
|
deviceDiscovered(device, THING_TYPE_THERMOSTAT, place);
|
||||||
} else if ("ValveHeatingTemperatureInterface".equals(device.getWidget())) {
|
} else if ("ValveHeatingTemperatureInterface".equals(widget)) {
|
||||||
deviceDiscovered(device, THING_TYPE_VALVE_HEATING_SYSTEM, place);
|
deviceDiscovered(device, THING_TYPE_VALVE_HEATING_SYSTEM, place);
|
||||||
} else if (isOnOffHeatingSystem(device)) {
|
} else if (isOnOffHeatingSystem(device)) {
|
||||||
deviceDiscovered(device, THING_TYPE_ONOFF_HEATING_SYSTEM, place);
|
deviceDiscovered(device, THING_TYPE_ONOFF_HEATING_SYSTEM, place);
|
||||||
@ -269,7 +273,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CLASS_EXTERIOR_HEATING_SYSTEM:
|
case CLASS_EXTERIOR_HEATING_SYSTEM:
|
||||||
if ("DimmerExteriorHeating".equals(device.getWidget())) {
|
if ("DimmerExteriorHeating".equals(widget)) {
|
||||||
// widget: DimmerExteriorHeating
|
// widget: DimmerExteriorHeating
|
||||||
deviceDiscovered(device, THING_TYPE_EXTERIOR_HEATING_SYSTEM, place);
|
deviceDiscovered(device, THING_TYPE_EXTERIOR_HEATING_SYSTEM, place);
|
||||||
} else {
|
} else {
|
||||||
@ -288,7 +292,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
deviceDiscovered(device, THING_TYPE_DOOR_LOCK, place);
|
deviceDiscovered(device, THING_TYPE_DOOR_LOCK, place);
|
||||||
break;
|
break;
|
||||||
case CLASS_PERGOLA:
|
case CLASS_PERGOLA:
|
||||||
if ("BioclimaticPergola".equals(device.getWidget())) {
|
if ("BioclimaticPergola".equals(widget)) {
|
||||||
// widget: BioclimaticPergola
|
// widget: BioclimaticPergola
|
||||||
deviceDiscovered(device, THING_TYPE_BIOCLIMATIC_PERGOLA, place);
|
deviceDiscovered(device, THING_TYPE_BIOCLIMATIC_PERGOLA, place);
|
||||||
} else {
|
} else {
|
||||||
@ -315,7 +319,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
break;
|
break;
|
||||||
case CLASS_WATER_HEATING_SYSTEM:
|
case CLASS_WATER_HEATING_SYSTEM:
|
||||||
// widget: DomesticHotWaterProduction
|
// widget: DomesticHotWaterProduction
|
||||||
if ("DomesticHotWaterProduction".equals(device.getWidget())) {
|
if ("DomesticHotWaterProduction".equals(widget)) {
|
||||||
deviceDiscovered(device, THING_TYPE_WATERHEATINGSYSTEM, place);
|
deviceDiscovered(device, THING_TYPE_WATERHEATINGSYSTEM, place);
|
||||||
} else {
|
} else {
|
||||||
logUnsupportedDevice(device);
|
logUnsupportedDevice(device);
|
||||||
@ -340,13 +344,13 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CLASS_HITACHI_HEATING_SYSTEM:
|
case CLASS_HITACHI_HEATING_SYSTEM:
|
||||||
if ("HitachiAirToWaterHeatingZone".equals(device.getWidget())) {
|
if ("HitachiAirToWaterHeatingZone".equals(widget)) {
|
||||||
// widget: HitachiAirToWaterHeatingZone
|
// widget: HitachiAirToWaterHeatingZone
|
||||||
deviceDiscovered(device, THING_TYPE_HITACHI_ATWHZ, place);
|
deviceDiscovered(device, THING_TYPE_HITACHI_ATWHZ, place);
|
||||||
} else if ("HitachiAirToWaterMainComponent".equals(device.getWidget())) {
|
} else if ("HitachiAirToWaterMainComponent".equals(widget)) {
|
||||||
// widget: HitachiAirToWaterMainComponent
|
// widget: HitachiAirToWaterMainComponent
|
||||||
deviceDiscovered(device, THING_TYPE_HITACHI_ATWMC, place);
|
deviceDiscovered(device, THING_TYPE_HITACHI_ATWMC, place);
|
||||||
} else if ("HitachiDHW".equals(device.getWidget())) {
|
} else if ("HitachiDHW".equals(widget)) {
|
||||||
// widget: HitachiDHW
|
// widget: HitachiDHW
|
||||||
deviceDiscovered(device, THING_TYPE_HITACHI_DHW, place);
|
deviceDiscovered(device, THING_TYPE_HITACHI_DHW, place);
|
||||||
} else {
|
} else {
|
||||||
@ -354,7 +358,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CLASS_RAIN_SENSOR:
|
case CLASS_RAIN_SENSOR:
|
||||||
if ("RainSensor".equals(device.getWidget())) {
|
if ("RainSensor".equals(widget)) {
|
||||||
// widget: RainSensor
|
// widget: RainSensor
|
||||||
deviceDiscovered(device, THING_TYPE_RAINSENSOR, place);
|
deviceDiscovered(device, THING_TYPE_RAINSENSOR, place);
|
||||||
} else {
|
} else {
|
||||||
@ -391,8 +395,8 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
|
|
||||||
private void logUnsupportedDevice(SomfyTahomaDevice device) {
|
private void logUnsupportedDevice(SomfyTahomaDevice device) {
|
||||||
if (!isStateLess(device)) {
|
if (!isStateLess(device)) {
|
||||||
logger.debug("Detected a new unsupported device: {} with widgetName: {}", device.getUiClass(),
|
logger.debug("Detected a new unsupported device: {} with widgetName: {}",
|
||||||
device.getWidget());
|
device.getDefinition().getUiClass(), device.getDefinition().getWidgetName());
|
||||||
logger.debug("If you want to add the support, please create a new issue and attach the information below");
|
logger.debug("If you want to add the support, please create a new issue and attach the information below");
|
||||||
logger.debug("Device definition:\n{}", device.getDefinition());
|
logger.debug("Device definition:\n{}", device.getDefinition());
|
||||||
|
|
||||||
@ -417,11 +421,11 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSilentRollerShutter(SomfyTahomaDevice device) {
|
private boolean isSilentRollerShutter(SomfyTahomaDevice device) {
|
||||||
return "PositionableRollerShutterWithLowSpeedManagement".equals(device.getWidget());
|
return "PositionableRollerShutterWithLowSpeedManagement".equals(device.getDefinition().getWidgetName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUnoRollerShutter(SomfyTahomaDevice device) {
|
private boolean isUnoRollerShutter(SomfyTahomaDevice device) {
|
||||||
return "PositionableRollerShutterUno".equals(device.getWidget());
|
return "PositionableRollerShutterUno".equals(device.getDefinition().getWidgetName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOnOffHeatingSystem(SomfyTahomaDevice device) {
|
private boolean isOnOffHeatingSystem(SomfyTahomaDevice device) {
|
||||||
@ -441,11 +445,10 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
if (place != null && !place.isBlank()) {
|
if (place != null && !place.isBlank()) {
|
||||||
label += " (" + place + ")";
|
label += " (" + place + ")";
|
||||||
}
|
}
|
||||||
deviceDiscovered(label, device.getDeviceURL(), device.getOid(), thingTypeUID,
|
deviceDiscovered(label, device.getDeviceURL(), thingTypeUID, hasState(device, RSSI_LEVEL_STATE));
|
||||||
hasState(device, RSSI_LEVEL_STATE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deviceDiscovered(String label, String deviceURL, String oid, ThingTypeUID thingTypeUID, boolean rssi) {
|
private void deviceDiscovered(String label, String deviceURL, ThingTypeUID thingTypeUID, boolean rssi) {
|
||||||
Map<String, Object> properties = new HashMap<>();
|
Map<String, Object> properties = new HashMap<>();
|
||||||
properties.put("url", deviceURL);
|
properties.put("url", deviceURL);
|
||||||
properties.put(NAME_STATE, label);
|
properties.put(NAME_STATE, label);
|
||||||
@ -455,17 +458,18 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
|
|||||||
|
|
||||||
SomfyTahomaBridgeHandler localBridgeHandler = bridgeHandler;
|
SomfyTahomaBridgeHandler localBridgeHandler = bridgeHandler;
|
||||||
if (localBridgeHandler != null) {
|
if (localBridgeHandler != null) {
|
||||||
ThingUID thingUID = new ThingUID(thingTypeUID, localBridgeHandler.getThing().getUID(), oid);
|
ThingUID thingUID = new ThingUID(thingTypeUID, localBridgeHandler.getThing().getUID(),
|
||||||
|
deviceURL.replaceAll("[^a-zA-Z0-9_]", ""));
|
||||||
|
|
||||||
logger.debug("Detected a/an {} - label: {} oid: {}", thingTypeUID.getId(), label, oid);
|
logger.debug("Detected a/an {} - label: {} device URL: {}", thingTypeUID.getId(), label, deviceURL);
|
||||||
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
|
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
|
||||||
.withProperties(properties).withRepresentationProperty("url").withLabel(label)
|
.withProperties(properties).withRepresentationProperty("url").withLabel(label)
|
||||||
.withBridge(localBridgeHandler.getThing().getUID()).build());
|
.withBridge(localBridgeHandler.getThing().getUID()).build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void actionGroupDiscovered(String label, String deviceURL, String oid) {
|
private void actionGroupDiscovered(String label, String deviceURL) {
|
||||||
deviceDiscovered(label, deviceURL, oid, THING_TYPE_ACTIONGROUP, false);
|
deviceDiscovered(label, deviceURL, THING_TYPE_ACTIONGROUP, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void gatewayDiscovered(SomfyTahomaGateway gw) {
|
private void gatewayDiscovered(SomfyTahomaGateway gw) {
|
||||||
|
|||||||
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.somfytahoma.internal.discovery;
|
||||||
|
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import javax.jmdns.ServiceEvent;
|
||||||
|
import javax.jmdns.ServiceInfo;
|
||||||
|
import javax.jmdns.ServiceListener;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaBridgeHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SomfyTahomaMDNSDiscoveryListener} represents a mDNS listener
|
||||||
|
* for a mDNS discovery.
|
||||||
|
*
|
||||||
|
* @author Ondrej Pecta - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SomfyTahomaMDNSDiscoveryListener implements ServiceListener {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(SomfyTahomaMDNSDiscoveryListener.class);
|
||||||
|
private final SomfyTahomaBridgeHandler handler;
|
||||||
|
|
||||||
|
public SomfyTahomaMDNSDiscoveryListener(SomfyTahomaBridgeHandler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serviceAdded(@Nullable ServiceEvent event) {
|
||||||
|
if (event != null) {
|
||||||
|
logger.trace("Service added: {}", event.getInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serviceRemoved(@Nullable ServiceEvent event) {
|
||||||
|
if (event != null) {
|
||||||
|
logger.trace("Service removed: {}", event.getInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serviceResolved(@Nullable ServiceEvent event) {
|
||||||
|
if (event == null || event.getInfo() == null) {
|
||||||
|
logger.debug("Null event received");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceInfo info = event.getInfo();
|
||||||
|
logger.trace("Service resolved: {}", info);
|
||||||
|
if (info.getInet4Addresses().length > 0) {
|
||||||
|
logger.debug("Server address: {}", info.getInet4Addresses()[0].getHostAddress());
|
||||||
|
handler.setGatewayIPAddress(info.getInet4Addresses()[0].getHostAddress());
|
||||||
|
}
|
||||||
|
Enumeration<String> e = info.getPropertyNames();
|
||||||
|
if (e != null) {
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
String name = e.nextElement();
|
||||||
|
if ("gateway_pin".equals(name)) {
|
||||||
|
String pin = info.getPropertyString(name);
|
||||||
|
logger.debug("Gateway PIN: {}", pin);
|
||||||
|
handler.setGatewayPin(pin);
|
||||||
|
handler.updateConfiguration();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,6 +14,8 @@ package org.openhab.binding.somfytahoma.internal.handler;
|
|||||||
|
|
||||||
import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.*;
|
import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
@ -28,23 +30,29 @@ import java.util.concurrent.ScheduledFuture;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import javax.jmdns.JmDNS;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.WWWAuthenticationProtocolHandler;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.openhab.binding.somfytahoma.internal.config.SomfyTahomaConfig;
|
import org.openhab.binding.somfytahoma.internal.config.SomfyTahomaConfig;
|
||||||
import org.openhab.binding.somfytahoma.internal.discovery.SomfyTahomaItemDiscoveryService;
|
import org.openhab.binding.somfytahoma.internal.discovery.SomfyTahomaItemDiscoveryService;
|
||||||
|
import org.openhab.binding.somfytahoma.internal.discovery.SomfyTahomaMDNSDiscoveryListener;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaAction;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaAction;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaActionGroup;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaActionGroup;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaApplyResponse;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaApplyResponse;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaDevice;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaDevice;
|
||||||
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaError;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaEvent;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaEvent;
|
||||||
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaLocalToken;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaLoginResponse;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaLoginResponse;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaOauth2Error;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaOauth2Error;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaOauth2Reponse;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaOauth2Reponse;
|
||||||
@ -53,7 +61,9 @@ import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaSetup;
|
|||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaState;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaState;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaStatus;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaStatus;
|
||||||
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaStatusResponse;
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaStatusResponse;
|
||||||
|
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaTokenReponse;
|
||||||
import org.openhab.core.cache.ExpiringCache;
|
import org.openhab.core.cache.ExpiringCache;
|
||||||
|
import org.openhab.core.config.core.Configuration;
|
||||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
@ -86,7 +96,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
/**
|
/**
|
||||||
* The shared HttpClient
|
* The shared HttpClient
|
||||||
*/
|
*/
|
||||||
private final HttpClient httpClient;
|
private @Nullable HttpClient httpClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Future to poll for updates
|
* Future to poll for updates
|
||||||
@ -103,6 +113,11 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
*/
|
*/
|
||||||
private @Nullable ScheduledFuture<?> reconciliationFuture;
|
private @Nullable ScheduledFuture<?> reconciliationFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Future for postponed login
|
||||||
|
*/
|
||||||
|
private @Nullable ScheduledFuture<?> loginFuture;
|
||||||
|
|
||||||
// List of futures used for command retries
|
// List of futures used for command retries
|
||||||
private Collection<ScheduledFuture<?>> retryFutures = new ConcurrentLinkedQueue<ScheduledFuture<?>>();
|
private Collection<ScheduledFuture<?>> retryFutures = new ConcurrentLinkedQueue<ScheduledFuture<?>>();
|
||||||
|
|
||||||
@ -120,6 +135,9 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
// Reconciliation flag
|
// Reconciliation flag
|
||||||
private boolean reconciliation = false;
|
private boolean reconciliation = false;
|
||||||
|
|
||||||
|
// Cloud fallback
|
||||||
|
private boolean cloudFallback = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Our configuration
|
* Our configuration
|
||||||
*/
|
*/
|
||||||
@ -130,6 +148,8 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
*/
|
*/
|
||||||
private String eventsId = "";
|
private String eventsId = "";
|
||||||
|
|
||||||
|
private String localToken = "";
|
||||||
|
|
||||||
private Map<String, SomfyTahomaDevice> devicePlaces = new HashMap<>();
|
private Map<String, SomfyTahomaDevice> devicePlaces = new HashMap<>();
|
||||||
|
|
||||||
private ExpiringCache<List<SomfyTahomaDevice>> cachedDevices = new ExpiringCache<>(Duration.ofSeconds(30),
|
private ExpiringCache<List<SomfyTahomaDevice>> cachedDevices = new ExpiringCache<>(Duration.ofSeconds(30),
|
||||||
@ -138,9 +158,11 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
// Gson & parser
|
// Gson & parser
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
|
private final HttpClientFactory httpClientFactory;
|
||||||
|
|
||||||
public SomfyTahomaBridgeHandler(Bridge thing, HttpClientFactory httpClientFactory) {
|
public SomfyTahomaBridgeHandler(Bridge thing, HttpClientFactory httpClientFactory) {
|
||||||
super(thing);
|
super(thing);
|
||||||
this.httpClient = httpClientFactory.createHttpClient("somfy_" + thing.getUID().getId());
|
this.httpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -149,7 +171,24 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
thingConfig = getConfigAs(SomfyTahomaConfig.class);
|
thingConfig = getConfigAs(SomfyTahomaConfig.class);
|
||||||
|
createHttpClient();
|
||||||
|
|
||||||
|
scheduler.execute(() -> {
|
||||||
|
login();
|
||||||
|
initPolling();
|
||||||
|
logger.debug("Initialize done...");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createHttpClient() {
|
||||||
|
// let's create the right http client
|
||||||
|
if (thingConfig.isDevMode()) {
|
||||||
|
this.httpClient = new HttpClient(new SslContextFactory.Client(true));
|
||||||
|
} else {
|
||||||
|
this.httpClient = httpClientFactory.createHttpClient("somfy_" + thing.getUID().getId());
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
httpClient.start();
|
httpClient.start();
|
||||||
@ -157,12 +196,8 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
logger.debug("Cannot start http client for: {}", thing.getBridgeUID().getId(), e);
|
logger.debug("Cannot start http client for: {}", thing.getBridgeUID().getId(), e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Remove the WWWAuth protocol handler since Tahoma is not fully compliant
|
||||||
scheduler.execute(() -> {
|
httpClient.getProtocolHandlers().remove(WWWAuthenticationProtocolHandler.NAME);
|
||||||
login();
|
|
||||||
initPolling();
|
|
||||||
logger.debug("Initialize done...");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -214,9 +249,9 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reLoginNeeded = false;
|
reLoginNeeded = false;
|
||||||
|
cloudFallback = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
String urlParameters = "";
|
String urlParameters = "";
|
||||||
|
|
||||||
// if cozytouch, must use oauth server
|
// if cozytouch, must use oauth server
|
||||||
@ -239,31 +274,37 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
|
|
||||||
SomfyTahomaLoginResponse data = gson.fromJson(response.getContentAsString(),
|
SomfyTahomaLoginResponse data = gson.fromJson(response.getContentAsString(),
|
||||||
SomfyTahomaLoginResponse.class);
|
SomfyTahomaLoginResponse.class);
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
"Received invalid data (login)");
|
"Received invalid data (login)");
|
||||||
} else if (data.isSuccess()) {
|
} else if (!data.getErrorCode().isEmpty()) {
|
||||||
logger.debug("SomfyTahoma version: {}", data.getVersion());
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, data.getError());
|
||||||
|
if (data.getError().startsWith(TOO_MANY_REQUESTS)) {
|
||||||
|
setTooManyRequests();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (thingConfig.isDevMode()) {
|
||||||
|
initializeLocalMode();
|
||||||
|
}
|
||||||
|
|
||||||
String id = registerEvents();
|
String id = registerEvents();
|
||||||
if (id != null && !UNAUTHORIZED.equals(id)) {
|
if (id != null && !UNAUTHORIZED.equals(id)) {
|
||||||
eventsId = id;
|
eventsId = id;
|
||||||
logger.debug("Events id: {}", eventsId);
|
logger.debug("Events id: {}", eventsId);
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE,
|
||||||
|
isDevModeReady() ? "LAN mode" : cloudFallback ? "Cloud mode fallback" : "Cloud mode");
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Events id error: {}", id);
|
logger.debug("Events id error: {}", id);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
"Error logging in: " + data.getError());
|
"unable to register events");
|
||||||
if (data.getError().startsWith(TOO_MANY_REQUESTS)) {
|
|
||||||
setTooManyRequests();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (JsonSyntaxException e) {
|
} catch (JsonSyntaxException e) {
|
||||||
logger.debug("Received invalid data (login)", e);
|
logger.debug("Received invalid data (login)", e);
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data (login)");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data (login)");
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
if (isAuthenticationChallenge(e) || isOAuthGrantError(e)) {
|
if (isOAuthGrantError(e)) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
"Error logging in (check your credentials)");
|
"Error logging in (check your credentials)");
|
||||||
setTooManyRequests();
|
setTooManyRequests();
|
||||||
@ -280,11 +321,106 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDevModeReady() {
|
||||||
|
return thingConfig.isDevMode() && !localToken.isEmpty() && !cloudFallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeLocalMode() {
|
||||||
|
if (thingConfig.getIp().isEmpty() || thingConfig.getPin().isEmpty()) {
|
||||||
|
discoverGateway();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!thingConfig.getIp().isEmpty() && !thingConfig.getPin().isEmpty()) {
|
||||||
|
try {
|
||||||
|
if (thingConfig.getToken().isEmpty()) {
|
||||||
|
localToken = getNewLocalToken();
|
||||||
|
logger.debug("Local token retrieved");
|
||||||
|
activateLocalToken();
|
||||||
|
updateConfiguration();
|
||||||
|
} else {
|
||||||
|
localToken = thingConfig.getToken();
|
||||||
|
activateLocalToken();
|
||||||
|
}
|
||||||
|
logger.debug("Local mode initialized, waiting for cloud sync");
|
||||||
|
Thread.sleep(3000);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.debug("Interruption during local mode initialization, falling back to cloud mode", ex);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (ExecutionException | TimeoutException ex) {
|
||||||
|
logger.debug("Exception during local mode initialization, falling back to cloud mode", ex);
|
||||||
|
cloudFallback = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("Cannot switch to developer mode - gateway not found on LAN");
|
||||||
|
cloudFallback = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNewLocalToken() throws ExecutionException, InterruptedException, TimeoutException {
|
||||||
|
// Get list of local tokens
|
||||||
|
SomfyTahomaLocalToken[] tokens = invokeCallToURL(
|
||||||
|
CONFIG_URL + thingConfig.getPin() + LOCAL_TOKENS_URL + "devmode", "", HttpMethod.GET,
|
||||||
|
SomfyTahomaLocalToken[].class);
|
||||||
|
|
||||||
|
// Delete old OH tokens
|
||||||
|
for (SomfyTahomaLocalToken token : tokens) {
|
||||||
|
if (OPENHAB_TOKEN.equals(token.getLabel())) {
|
||||||
|
logger.debug("Deleting token: {}", token.getUuid());
|
||||||
|
sendDeleteToTahomaWithCookie(CONFIG_URL + thingConfig.getPin() + LOCAL_TOKENS_URL + token.getUuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new token
|
||||||
|
SomfyTahomaTokenReponse tokenResponse = invokeCallToURL(
|
||||||
|
CONFIG_URL + thingConfig.getPin() + LOCAL_TOKENS_URL + "generate", "", HttpMethod.GET,
|
||||||
|
SomfyTahomaTokenReponse.class);
|
||||||
|
|
||||||
|
return tokenResponse.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void discoverGateway() {
|
||||||
|
logger.debug("Starting mDNS discovery...");
|
||||||
|
JmDNS jmdns = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a JmDNS instance
|
||||||
|
jmdns = JmDNS.create(InetAddress.getLocalHost());
|
||||||
|
jmdns.addServiceListener("_kizboxdev._tcp.local.", new SomfyTahomaMDNSDiscoveryListener(this));
|
||||||
|
|
||||||
|
// Wait a bit
|
||||||
|
Thread.sleep(TAHOMA_TIMEOUT * 1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.debug("mDNS discovery interrupted", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("Exception during mDNS discovery", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jmdns != null) {
|
||||||
|
jmdns.unregisterAllServices();
|
||||||
|
try {
|
||||||
|
jmdns.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void activateLocalToken() throws ExecutionException, InterruptedException, TimeoutException {
|
||||||
|
String param = "{\"label\" : \"" + OPENHAB_TOKEN + "\",\"token\" : \"" + localToken
|
||||||
|
+ "\",\"scope\" : \"devmode\"}";
|
||||||
|
String response = sendPostToTahomaWithCookie(CONFIG_URL + thingConfig.getPin() + "/local/tokens", param);
|
||||||
|
logger.trace("Local token activation: {}", response);
|
||||||
|
}
|
||||||
|
|
||||||
private void setTooManyRequests() {
|
private void setTooManyRequests() {
|
||||||
logger.debug("Too many requests or bad credentials for the cloud portal, suspending activity for {} seconds",
|
if (!tooManyRequests) {
|
||||||
|
logger.debug(
|
||||||
|
"Too many requests or bad credentials for the cloud portal, suspending activity for {} seconds",
|
||||||
SUSPEND_TIME);
|
SUSPEND_TIME);
|
||||||
tooManyRequests = true;
|
tooManyRequests = true;
|
||||||
scheduler.schedule(this::enableLogin, SUSPEND_TIME, TimeUnit.SECONDS);
|
loginFuture = scheduler.schedule(this::enableLogin, SUSPEND_TIME, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable String registerEvents() {
|
private @Nullable String registerEvents() {
|
||||||
@ -302,6 +438,10 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<SomfyTahomaEvent> getEvents() {
|
private List<SomfyTahomaEvent> getEvents() {
|
||||||
|
if (eventsId.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
SomfyTahomaEvent[] response = invokeCallToURL(EVENTS_URL + eventsId + "/fetch", "", HttpMethod.POST,
|
SomfyTahomaEvent[] response = invokeCallToURL(EVENTS_URL + eventsId + "/fetch", "", HttpMethod.POST,
|
||||||
SomfyTahomaEvent[].class);
|
SomfyTahomaEvent[].class);
|
||||||
return response != null ? List.of(response) : List.of();
|
return response != null ? List.of(response) : List.of();
|
||||||
@ -331,11 +471,24 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
// cancel all scheduled retries
|
// cancel all scheduled retries
|
||||||
retryFutures.forEach(x -> x.cancel(false));
|
retryFutures.forEach(x -> x.cancel(false));
|
||||||
|
|
||||||
|
ScheduledFuture<?> localLoginFuture = loginFuture;
|
||||||
|
if (localLoginFuture != null) {
|
||||||
|
localLoginFuture.cancel(true);
|
||||||
|
loginFuture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClient localHttpClient = httpClient;
|
||||||
|
if (localHttpClient != null) {
|
||||||
try {
|
try {
|
||||||
httpClient.stop();
|
localHttpClient.stop();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.debug("Error during http client stopping", e);
|
logger.debug("Error during http client stopping", e);
|
||||||
}
|
}
|
||||||
|
httpClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean access data
|
||||||
|
localToken = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -351,16 +504,19 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
*/
|
*/
|
||||||
private void stopPolling() {
|
private void stopPolling() {
|
||||||
ScheduledFuture<?> localPollFuture = pollFuture;
|
ScheduledFuture<?> localPollFuture = pollFuture;
|
||||||
if (localPollFuture != null && !localPollFuture.isCancelled()) {
|
if (localPollFuture != null) {
|
||||||
localPollFuture.cancel(true);
|
localPollFuture.cancel(true);
|
||||||
|
pollFuture = null;
|
||||||
}
|
}
|
||||||
ScheduledFuture<?> localStatusFuture = statusFuture;
|
ScheduledFuture<?> localStatusFuture = statusFuture;
|
||||||
if (localStatusFuture != null && !localStatusFuture.isCancelled()) {
|
if (localStatusFuture != null) {
|
||||||
localStatusFuture.cancel(true);
|
localStatusFuture.cancel(true);
|
||||||
|
statusFuture = null;
|
||||||
}
|
}
|
||||||
ScheduledFuture<?> localReconciliationFuture = reconciliationFuture;
|
ScheduledFuture<?> localReconciliationFuture = reconciliationFuture;
|
||||||
if (localReconciliationFuture != null && !localReconciliationFuture.isCancelled()) {
|
if (localReconciliationFuture != null) {
|
||||||
localReconciliationFuture.cancel(true);
|
localReconciliationFuture.cancel(true);
|
||||||
|
reconciliationFuture = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +560,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
if (!device.getPlaceOID().isEmpty()) {
|
if (!device.getPlaceOID().isEmpty()) {
|
||||||
SomfyTahomaDevice newDevice = new SomfyTahomaDevice();
|
SomfyTahomaDevice newDevice = new SomfyTahomaDevice();
|
||||||
newDevice.setPlaceOID(device.getPlaceOID());
|
newDevice.setPlaceOID(device.getPlaceOID());
|
||||||
newDevice.setWidget(device.getWidget());
|
newDevice.getDefinition().setWidgetName(device.getDefinition().getWidgetName());
|
||||||
devicePlaces.put(device.getDeviceURL(), newDevice);
|
devicePlaces.put(device.getDeviceURL(), newDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -637,10 +793,10 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
|
|
||||||
private String sendMethodToTahomaWithCookie(String url, HttpMethod method, String urlParameters)
|
private String sendMethodToTahomaWithCookie(String url, HttpMethod method, String urlParameters)
|
||||||
throws InterruptedException, ExecutionException, TimeoutException {
|
throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
logger.trace("Sending {} to url: {} with data: {}", method.asString(), getApiFullUrl(url), urlParameters);
|
logger.debug("Sending {} to url: {} with data: {}", method.asString(), getApiFullUrl(url), urlParameters);
|
||||||
Request request = sendRequestBuilder(url, method);
|
Request request = sendRequestBuilder(url, method);
|
||||||
if (!urlParameters.isEmpty()) {
|
if (!urlParameters.isEmpty()) {
|
||||||
request = request.content(new StringContentProvider(urlParameters), "application/json;charset=UTF-8");
|
request = request.content(new StringContentProvider(urlParameters), "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentResponse response = request.send();
|
ContentResponse response = request.send();
|
||||||
@ -651,17 +807,44 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
|
|
||||||
if (response.getStatus() < 200 || response.getStatus() >= 300) {
|
if (response.getStatus() < 200 || response.getStatus() >= 300) {
|
||||||
logger.debug("Received unexpected status code: {}", response.getStatus());
|
logger.debug("Received unexpected status code: {}", response.getStatus());
|
||||||
|
if (response.getHeaders().contains(HttpHeader.CONTENT_TYPE)) {
|
||||||
|
if (response.getHeaders().getField(HttpHeader.CONTENT_TYPE).getValue()
|
||||||
|
.equalsIgnoreCase(MediaType.APPLICATION_JSON)) {
|
||||||
|
try {
|
||||||
|
SomfyTahomaError error = gson.fromJson(response.getContentAsString(), SomfyTahomaError.class);
|
||||||
|
throw new ExecutionException(error.getError(), null);
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new ExecutionException(
|
||||||
|
"Unknown http error " + response.getStatus() + " while attempting to send a message.", null);
|
||||||
}
|
}
|
||||||
return response.getContentAsString();
|
return response.getContentAsString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Request sendRequestBuilder(String subUrl, HttpMethod method) {
|
private Request sendRequestBuilder(String subUrl, HttpMethod method) {
|
||||||
|
return isLocalRequest(subUrl) ? sendRequestBuilderLocal(subUrl, method)
|
||||||
|
: sendRequestBuilderCloud(subUrl, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLocalRequest(String subUrl) {
|
||||||
|
return isDevModeReady() && !subUrl.startsWith(CONFIG_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Request sendRequestBuilderCloud(String subUrl, HttpMethod method) {
|
||||||
return httpClient.newRequest(getApiFullUrl(subUrl)).method(method)
|
return httpClient.newRequest(getApiFullUrl(subUrl)).method(method)
|
||||||
.header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en").header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate")
|
.header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en").header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate")
|
||||||
.header("X-Requested-With", "XMLHttpRequest").timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS)
|
.header("X-Requested-With", "XMLHttpRequest").timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS)
|
||||||
.agent(TAHOMA_AGENT);
|
.agent(TAHOMA_AGENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Request sendRequestBuilderLocal(String subUrl, HttpMethod method) {
|
||||||
|
return httpClient.newRequest(getApiFullUrl(subUrl)).method(method).accept("application/json")
|
||||||
|
.header(HttpHeader.AUTHORIZATION, "Bearer " + localToken);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the login for Cozytouch using OAUTH2 authorization.
|
* Performs the login for Cozytouch using OAUTH2 authorization.
|
||||||
*
|
*
|
||||||
@ -720,7 +903,9 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getApiFullUrl(String subUrl) {
|
private String getApiFullUrl(String subUrl) {
|
||||||
return "https://" + thingConfig.getCloudPortal() + API_BASE_URL + subUrl;
|
return isLocalRequest(subUrl)
|
||||||
|
? "https://" + thingConfig.getIp() + ":8443/enduser-mobile-web/1/enduserAPI/" + subUrl
|
||||||
|
: "https://" + thingConfig.getCloudPortal() + API_BASE_URL + subUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendCommand(String io, String command, String params, String url) {
|
public void sendCommand(String io, String command, String params, String url) {
|
||||||
@ -781,7 +966,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
if (device != null && !device.getPlaceOID().isEmpty()) {
|
if (device != null && !device.getPlaceOID().isEmpty()) {
|
||||||
devicePlaces.forEach((deviceUrl, devicePlace) -> {
|
devicePlaces.forEach((deviceUrl, devicePlace) -> {
|
||||||
if (device.getPlaceOID().equals(devicePlace.getPlaceOID())
|
if (device.getPlaceOID().equals(devicePlace.getPlaceOID())
|
||||||
&& device.getWidget().equals(devicePlace.getWidget())) {
|
&& device.getDefinition().getWidgetName().equals(devicePlace.getDefinition().getWidgetName())) {
|
||||||
sendCommand(deviceUrl, command, params, url);
|
sendCommand(deviceUrl, command, params, url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -832,6 +1017,7 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
private boolean reLogin() {
|
private boolean reLogin() {
|
||||||
logger.debug("Doing relogin");
|
logger.debug("Doing relogin");
|
||||||
reLoginNeeded = true;
|
reLoginNeeded = true;
|
||||||
|
localToken = "";
|
||||||
login();
|
login();
|
||||||
return ThingStatus.OFFLINE != thing.getStatus();
|
return ThingStatus.OFFLINE != thing.getStatus();
|
||||||
}
|
}
|
||||||
@ -850,28 +1036,53 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void forceGatewaySync() {
|
public void forceGatewaySync() {
|
||||||
|
// refresh is valid only if in a cloud mode
|
||||||
|
if (!thingConfig.isDevMode() || localToken.isEmpty()) {
|
||||||
invokeCallToURL(REFRESH_URL, "", HttpMethod.PUT, null);
|
invokeCallToURL(REFRESH_URL, "", HttpMethod.PUT, null);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SomfyTahomaStatus getTahomaStatus(String gatewayId) {
|
public SomfyTahomaStatus getTahomaStatus(String gatewayId) {
|
||||||
SomfyTahomaStatusResponse data = invokeCallToURL(GATEWAYS_URL + gatewayId, "", HttpMethod.GET,
|
SomfyTahomaStatusResponse status = null;
|
||||||
SomfyTahomaStatusResponse.class);
|
|
||||||
|
if (isDevModeReady()) {
|
||||||
|
// Local endpoint does not have a method for specific gateway
|
||||||
|
SomfyTahomaStatusResponse[] data = invokeCallToURL(GATEWAYS_URL, "", HttpMethod.GET,
|
||||||
|
SomfyTahomaStatusResponse[].class);
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
logger.debug("Tahoma status: {}", data.getConnectivity().getStatus());
|
for (SomfyTahomaStatusResponse gatewayStatus : data) {
|
||||||
logger.debug("Tahoma protocol version: {}", data.getConnectivity().getProtocolVersion());
|
if (gatewayStatus.getGatewayId().equals(gatewayId)) {
|
||||||
return data.getConnectivity();
|
status = gatewayStatus;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status = invokeCallToURL(GATEWAYS_URL + gatewayId, "", HttpMethod.GET, SomfyTahomaStatusResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != null) {
|
||||||
|
logger.debug("Tahoma status: {}", status.getConnectivity().getStatus());
|
||||||
|
logger.debug("Tahoma protocol version: {}", status.getConnectivity().getProtocolVersion());
|
||||||
|
return status.getConnectivity();
|
||||||
}
|
}
|
||||||
return new SomfyTahomaStatus();
|
return new SomfyTahomaStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAuthenticationChallenge(Exception ex) {
|
private boolean isTempBanned(Exception ex) {
|
||||||
String msg = ex.getMessage();
|
String msg = ex.getMessage();
|
||||||
return msg != null && msg.contains(AUTHENTICATION_CHALLENGE);
|
return msg != null && msg.contains(TEMPORARILY_BANNED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEventListenerTimeout(Exception ex) {
|
||||||
|
String msg = ex.getMessage();
|
||||||
|
return msg != null && msg.contains(EVENT_LISTENER_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOAuthGrantError(Exception ex) {
|
private boolean isOAuthGrantError(Exception ex) {
|
||||||
String msg = ex.getMessage();
|
String msg = ex.getMessage();
|
||||||
return msg != null && msg.contains(AUTHENTICATION_OAUTH_GRANT_ERROR);
|
return msg != null
|
||||||
|
&& (msg.contains(AUTHENTICATION_OAUTH_GRANT_ERROR) || msg.contains(AUTHENTICATION_OAUTH_INVALID_GRANT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -915,7 +1126,10 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
logger.debug("Received data: {} is not JSON", response, e);
|
logger.debug("Received data: {} is not JSON", response, e);
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data");
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
if (isAuthenticationChallenge(e)) {
|
if (isTempBanned(e)) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Temporarily banned");
|
||||||
|
setTooManyRequests();
|
||||||
|
} else if (isEventListenerTimeout(e)) {
|
||||||
reLogin();
|
reLogin();
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Cannot call url: {} with params: {}!", getApiFullUrl(url), urlParameters, e);
|
logger.debug("Cannot call url: {} with params: {}!", getApiFullUrl(url), urlParameters, e);
|
||||||
@ -930,4 +1144,22 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGatewayIPAddress(String gatewayIPAddress) {
|
||||||
|
thingConfig.setIp(gatewayIPAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGatewayPin(String gatewayPin) {
|
||||||
|
thingConfig.setPin(gatewayPin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateConfiguration() {
|
||||||
|
Configuration config = editConfiguration();
|
||||||
|
config.put("ip", thingConfig.getIp());
|
||||||
|
config.put("pin", thingConfig.getPin());
|
||||||
|
if (!localToken.isEmpty()) {
|
||||||
|
config.put("token", localToken);
|
||||||
|
}
|
||||||
|
updateConfiguration(config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,9 @@ public class SomfyTahomaLightSensorHandler extends SomfyTahomaBaseThingHandler {
|
|||||||
|
|
||||||
public SomfyTahomaLightSensorHandler(Thing thing) {
|
public SomfyTahomaLightSensorHandler(Thing thing) {
|
||||||
super(thing);
|
super(thing);
|
||||||
stateNames.put(LUMINANCE, "core:LuminanceState");
|
stateNames.put(LUMINANCE, LUMINANCE_STATE);
|
||||||
stateNames.put(SENSOR_DEFECT, SENSOR_DEFECT_STATE);
|
stateNames.put(SENSOR_DEFECT, SENSOR_DEFECT_STATE);
|
||||||
|
// override state type because the local server sends luminance in percent
|
||||||
|
cacheStateType(LUMINANCE_STATE, TYPE_DECIMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.somfytahoma.internal.handler;
|
package org.openhab.binding.somfytahoma.internal.handler;
|
||||||
|
|
||||||
import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.TEMPERATURE;
|
import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.*;
|
||||||
import static org.openhab.binding.somfytahoma.internal.SomfyTahomaBindingConstants.TYPE_DECIMAL;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
@ -29,9 +28,9 @@ public class SomfyTahomaTemperatureSensorHandler extends SomfyTahomaBaseThingHan
|
|||||||
|
|
||||||
public SomfyTahomaTemperatureSensorHandler(Thing thing) {
|
public SomfyTahomaTemperatureSensorHandler(Thing thing) {
|
||||||
super(thing);
|
super(thing);
|
||||||
stateNames.put(TEMPERATURE, "core:TemperatureState");
|
stateNames.put(TEMPERATURE, TEMPERATURE_STATE);
|
||||||
|
|
||||||
// override state type because the cloud sends both percent & decimal
|
// override state type because the cloud sends both percent & decimal
|
||||||
cacheStateType("core:TemperatureState", TYPE_DECIMAL);
|
cacheStateType(TEMPERATURE_STATE, TYPE_DECIMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,8 +27,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SomfyTahomaDevice {
|
public class SomfyTahomaDevice {
|
||||||
|
|
||||||
private String uiClass = "";
|
|
||||||
private String widget = "";
|
|
||||||
private String deviceURL = "";
|
private String deviceURL = "";
|
||||||
private String label = "";
|
private String label = "";
|
||||||
private String oid = "";
|
private String oid = "";
|
||||||
@ -49,18 +47,6 @@ public class SomfyTahomaDevice {
|
|||||||
return oid;
|
return oid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUiClass() {
|
|
||||||
return uiClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWidget() {
|
|
||||||
return widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWidget(String widget) {
|
|
||||||
this.widget = widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SomfyTahomaDeviceDefinition getDefinition() {
|
public SomfyTahomaDeviceDefinition getDefinition() {
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,22 @@ public class SomfyTahomaDeviceDefinition {
|
|||||||
return states;
|
return states;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String widgetName = "";
|
||||||
|
|
||||||
|
private String uiClass = "";
|
||||||
|
|
||||||
|
public String getWidgetName() {
|
||||||
|
return widgetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUiClass() {
|
||||||
|
return uiClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWidgetName(String widgetName) {
|
||||||
|
this.widgetName = widgetName;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.somfytahoma.internal.model;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SomfyTahomaError} is used to parse error from API server.
|
||||||
|
*
|
||||||
|
* @author Ondrej Pecta - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SomfyTahomaError {
|
||||||
|
|
||||||
|
private String error = "";
|
||||||
|
private String errorCode = "";
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorCode() {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.somfytahoma.internal.model;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SomfyTahomaLocalToken} is used to parse a local token.
|
||||||
|
*
|
||||||
|
* @author Ondrej Pecta - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SomfyTahomaLocalToken {
|
||||||
|
String uuid = "";
|
||||||
|
String label = "";
|
||||||
|
|
||||||
|
public String getUuid() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,8 @@ public class SomfyTahomaLoginResponse {
|
|||||||
private String version = "";
|
private String version = "";
|
||||||
private String error = "";
|
private String error = "";
|
||||||
|
|
||||||
|
private String errorCode = "";
|
||||||
|
|
||||||
public boolean isSuccess() {
|
public boolean isSuccess() {
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
@ -38,4 +40,8 @@ public class SomfyTahomaLoginResponse {
|
|||||||
public String getError() {
|
public String getError() {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getErrorCode() {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,9 +23,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SomfyTahomaStatusResponse {
|
public class SomfyTahomaStatusResponse {
|
||||||
|
|
||||||
|
private String gatewayId = "";
|
||||||
private SomfyTahomaStatus connectivity = new SomfyTahomaStatus();
|
private SomfyTahomaStatus connectivity = new SomfyTahomaStatus();
|
||||||
|
|
||||||
public SomfyTahomaStatus getConnectivity() {
|
public SomfyTahomaStatus getConnectivity() {
|
||||||
return connectivity;
|
return connectivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getGatewayId() {
|
||||||
|
return gatewayId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.somfytahoma.internal.model;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SomfyTahomaTokenReponse} holds information about generated token
|
||||||
|
*
|
||||||
|
* @author Ondrej Pecta - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SomfyTahomaTokenReponse {
|
||||||
|
private String token = "";
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -69,5 +69,35 @@
|
|||||||
<description>Specifies the delay in milliseconds between subsequent retries after a command failure</description>
|
<description>Specifies the delay in milliseconds between subsequent retries after a command failure</description>
|
||||||
<default>1000</default>
|
<default>1000</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
|
||||||
|
<parameter name="devMode" type="boolean" required="false">
|
||||||
|
<label>Developer mode</label>
|
||||||
|
<description>Enables the direct control of your devices over the lan using the local API endpoint. See
|
||||||
|
https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode</description>
|
||||||
|
<default>false</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
|
||||||
|
<parameter name="ip" type="text" required="false">
|
||||||
|
<label>Gateway IP</label>
|
||||||
|
<description>Local IP address of gateway. Relevant only if developer mode is enabled. If not provided, the binding
|
||||||
|
will try to autodetect it</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
|
||||||
|
<parameter name="pin" type="text" required="false">
|
||||||
|
<label>Gateway PIN</label>
|
||||||
|
<description>Gateway PIN in format ABCD-EFGH-IJKL. Relevant only if developer mode is enabled. If not provided, the
|
||||||
|
binding will try to autodetect it</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
|
||||||
|
<parameter name="token" type="text" required="false">
|
||||||
|
<label>Local token</label>
|
||||||
|
<description>Local token. Relevant only if developer mode is enabled. If not provided, the binding
|
||||||
|
will try to
|
||||||
|
generate it using the gateway IP and PIN</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
</config-description:config-descriptions>
|
</config-description:config-descriptions>
|
||||||
|
|||||||
@ -80,6 +80,14 @@ bridge-type.config.somfytahoma.bridge.retryDelay.label = Retry delay
|
|||||||
bridge-type.config.somfytahoma.bridge.retryDelay.description = Specifies the delay in milliseconds between subsequent retries after a command failure
|
bridge-type.config.somfytahoma.bridge.retryDelay.description = Specifies the delay in milliseconds between subsequent retries after a command failure
|
||||||
bridge-type.config.somfytahoma.bridge.statusTimeout.label = Status Timeout
|
bridge-type.config.somfytahoma.bridge.statusTimeout.label = Status Timeout
|
||||||
bridge-type.config.somfytahoma.bridge.statusTimeout.description = Specifies the timeout in seconds after which the status is got from the cloud
|
bridge-type.config.somfytahoma.bridge.statusTimeout.description = Specifies the timeout in seconds after which the status is got from the cloud
|
||||||
|
bridge-type.config.somfytahoma.bridge.devMode.label = Developer mode
|
||||||
|
bridge-type.config.somfytahoma.bridge.devMode.description = Enables the direct control of your devices over the lan using the local API endpoint. See https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode
|
||||||
|
bridge-type.config.somfytahoma.bridge.ip.label = Gateway IP
|
||||||
|
bridge-type.config.somfytahoma.bridge.ip.description = Local IP address of gateway. Relevant only if developer mode is enabled. If not provided, the binding will try to autodetect it
|
||||||
|
bridge-type.config.somfytahoma.bridge.pin.label = Gateway PIN
|
||||||
|
bridge-type.config.somfytahoma.bridge.pin.description = Gateway PIN in format ABCD-EFGH-IJKL. Relevant only if developer mode is enabled. If not provided, the binding will try to autodetect it
|
||||||
|
bridge-type.config.somfytahoma.bridge.token.label = Local token
|
||||||
|
bridge-type.config.somfytahoma.bridge.token.description = Local token. Relevant only if developer mode is enabled. If not provided, the binding will try to generate it using the gateway IP and PIN
|
||||||
thing-type.config.somfytahoma.device.url.label = Somfy Tahoma Device URL
|
thing-type.config.somfytahoma.device.url.label = Somfy Tahoma Device URL
|
||||||
thing-type.config.somfytahoma.device.url.description = The identifier of this Somfy device
|
thing-type.config.somfytahoma.device.url.description = The identifier of this Somfy device
|
||||||
thing-type.config.somfytahoma.gateway.id.label = Somfy Tahoma Gateway ID
|
thing-type.config.somfytahoma.gateway.id.label = Somfy Tahoma Gateway ID
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user