[somfytahoma] New method to send a command to several devices in the same place (#10347)

* [somfytahoma] New method to send a command to several devices in the same place

Also include the place into the inbox label (discovery)

Signed-off-by: Laurent Garnier <lg.hc@free.fr>

* Review comment: remove unnecessary method

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo 2021-03-29 22:00:40 +02:00 committed by GitHub
parent bd56fd8e5d
commit 62a0e7ac02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 234 additions and 49 deletions

View File

@ -24,7 +24,13 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.somfytahoma.internal.handler.SomfyTahomaBridgeHandler;
import org.openhab.binding.somfytahoma.internal.model.*;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaActionGroup;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaDevice;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaGateway;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaRootPlace;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaSetup;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaState;
import org.openhab.binding.somfytahoma.internal.model.SomfyTahomaSubPlace;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
@ -41,20 +47,21 @@ import org.slf4j.LoggerFactory;
* action groups associated with your TahomaLink cloud account.
*
* @author Ondrej Pecta - Initial contribution
* @author Laurent Garnier - Include the place into the inbox label (when defined for the device)
*/
@NonNullByDefault
public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, ThingHandlerService {
private static final int DISCOVERY_TIMEOUT_SEC = 10;
private static final int DISCOVERY_REFRESH_SEC = 3600;
private final Logger logger = LoggerFactory.getLogger(SomfyTahomaItemDiscoveryService.class);
private @Nullable SomfyTahomaBridgeHandler bridgeHandler;
private @Nullable ScheduledFuture<?> discoveryJob;
private static final int DISCOVERY_TIMEOUT_SEC = 10;
private static final int DISCOVERY_REFRESH_SEC = 3600;
public SomfyTahomaItemDiscoveryService() {
super(DISCOVERY_TIMEOUT_SEC);
logger.debug("Creating discovery service");
@ -124,7 +131,7 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
}
for (SomfyTahomaDevice device : setup.getDevices()) {
discoverDevice(device);
discoverDevice(device, setup);
}
for (SomfyTahomaGateway gw : setup.getGateways()) {
gatewayDiscovered(gw);
@ -144,105 +151,106 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
}
}
private void discoverDevice(SomfyTahomaDevice device) {
private void discoverDevice(SomfyTahomaDevice device, SomfyTahomaSetup setup) {
logger.debug("url: {}", device.getDeviceURL());
String place = getPlaceLabel(setup, device.getPlaceOID());
switch (device.getUiClass()) {
case CLASS_AWNING:
// widget: PositionableHorizontalAwning
deviceDiscovered(device, THING_TYPE_AWNING);
deviceDiscovered(device, THING_TYPE_AWNING, place);
break;
case CLASS_CONTACT_SENSOR:
// widget: ContactSensor
deviceDiscovered(device, THING_TYPE_CONTACTSENSOR);
deviceDiscovered(device, THING_TYPE_CONTACTSENSOR, place);
break;
case CLASS_CURTAIN:
deviceDiscovered(device, THING_TYPE_CURTAIN);
deviceDiscovered(device, THING_TYPE_CURTAIN, place);
break;
case CLASS_EXTERIOR_SCREEN:
// widget: PositionableScreen
deviceDiscovered(device, THING_TYPE_EXTERIORSCREEN);
deviceDiscovered(device, THING_TYPE_EXTERIORSCREEN, place);
break;
case CLASS_EXTERIOR_VENETIAN_BLIND:
// widget: PositionableExteriorVenetianBlind
deviceDiscovered(device, THING_TYPE_EXTERIORVENETIANBLIND);
deviceDiscovered(device, THING_TYPE_EXTERIORVENETIANBLIND, place);
break;
case CLASS_GARAGE_DOOR:
deviceDiscovered(device, THING_TYPE_GARAGEDOOR);
deviceDiscovered(device, THING_TYPE_GARAGEDOOR, place);
break;
case CLASS_LIGHT:
if ("DimmerLight".equals(device.getWidget())) {
// widget: DimmerLight
deviceDiscovered(device, THING_TYPE_DIMMER_LIGHT);
deviceDiscovered(device, THING_TYPE_DIMMER_LIGHT, place);
} else {
// widget: TimedOnOffLight
// widget: StatefulOnOffLight
deviceDiscovered(device, THING_TYPE_LIGHT);
deviceDiscovered(device, THING_TYPE_LIGHT, place);
}
break;
case CLASS_LIGHT_SENSOR:
deviceDiscovered(device, THING_TYPE_LIGHTSENSOR);
deviceDiscovered(device, THING_TYPE_LIGHTSENSOR, place);
break;
case CLASS_OCCUPANCY_SENSOR:
// widget: OccupancySensor
deviceDiscovered(device, THING_TYPE_OCCUPANCYSENSOR);
deviceDiscovered(device, THING_TYPE_OCCUPANCYSENSOR, place);
break;
case CLASS_ON_OFF:
// widget: StatefulOnOff
deviceDiscovered(device, THING_TYPE_ONOFF);
deviceDiscovered(device, THING_TYPE_ONOFF, place);
break;
case CLASS_ROLLER_SHUTTER:
if (isSilentRollerShutter(device)) {
// widget: PositionableRollerShutterWithLowSpeedManagement
deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER_SILENT);
deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER_SILENT, place);
} else if (isUnoRollerShutter(device)) {
// widget: PositionableRollerShutterUno
deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER_UNO);
deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER_UNO, place);
} else {
// widget: PositionableRollerShutter
// widget: PositionableTiltedRollerShutter
deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER);
deviceDiscovered(device, THING_TYPE_ROLLERSHUTTER, place);
}
break;
case CLASS_SCREEN:
// widget: PositionableTiltedScreen
deviceDiscovered(device, THING_TYPE_SCREEN);
deviceDiscovered(device, THING_TYPE_SCREEN, place);
break;
case CLASS_SMOKE_SENSOR:
// widget: SmokeSensor
deviceDiscovered(device, THING_TYPE_SMOKESENSOR);
deviceDiscovered(device, THING_TYPE_SMOKESENSOR, place);
break;
case CLASS_VENETIAN_BLIND:
deviceDiscovered(device, THING_TYPE_VENETIANBLIND);
deviceDiscovered(device, THING_TYPE_VENETIANBLIND, place);
break;
case CLASS_WINDOW:
// widget: PositionableTiltedWindow
deviceDiscovered(device, THING_TYPE_WINDOW);
deviceDiscovered(device, THING_TYPE_WINDOW, place);
break;
case CLASS_ALARM:
if (device.getDeviceURL().startsWith("internal:")) {
// widget: TSKAlarmController
deviceDiscovered(device, THING_TYPE_INTERNAL_ALARM);
deviceDiscovered(device, THING_TYPE_INTERNAL_ALARM, place);
} else if ("MyFoxAlarmController".equals(device.getWidget())) {
// widget: MyFoxAlarmController
deviceDiscovered(device, THING_TYPE_MYFOX_ALARM);
deviceDiscovered(device, THING_TYPE_MYFOX_ALARM, place);
} else {
deviceDiscovered(device, THING_TYPE_EXTERNAL_ALARM);
deviceDiscovered(device, THING_TYPE_EXTERNAL_ALARM, place);
}
break;
case CLASS_POD:
if (hasState(device, CYCLIC_BUTTON_STATE)) {
deviceDiscovered(device, THING_TYPE_POD);
deviceDiscovered(device, THING_TYPE_POD, place);
}
break;
case CLASS_HEATING_SYSTEM:
if ("SomfyThermostat".equals(device.getWidget())) {
deviceDiscovered(device, THING_TYPE_THERMOSTAT);
deviceDiscovered(device, THING_TYPE_THERMOSTAT, place);
} else if ("ValveHeatingTemperatureInterface".equals(device.getWidget())) {
deviceDiscovered(device, THING_TYPE_VALVE_HEATING_SYSTEM);
deviceDiscovered(device, THING_TYPE_VALVE_HEATING_SYSTEM, place);
} else if (isOnOffHeatingSystem(device)) {
deviceDiscovered(device, THING_TYPE_ONOFF_HEATING_SYSTEM);
deviceDiscovered(device, THING_TYPE_ONOFF_HEATING_SYSTEM, place);
} else if (isZwaveHeatingSystem(device)) {
deviceDiscovered(device, THING_TYPE_ZWAVE_HEATING_SYSTEM);
deviceDiscovered(device, THING_TYPE_ZWAVE_HEATING_SYSTEM, place);
} else {
logUnsupportedDevice(device);
}
@ -250,57 +258,57 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
case CLASS_EXTERIOR_HEATING_SYSTEM:
if ("DimmerExteriorHeating".equals(device.getWidget())) {
// widget: DimmerExteriorHeating
deviceDiscovered(device, THING_TYPE_EXTERIOR_HEATING_SYSTEM);
deviceDiscovered(device, THING_TYPE_EXTERIOR_HEATING_SYSTEM, place);
} else {
logUnsupportedDevice(device);
}
break;
case CLASS_HUMIDITY_SENSOR:
if (hasState(device, WATER_DETECTION_STATE)) {
deviceDiscovered(device, THING_TYPE_WATERSENSOR);
deviceDiscovered(device, THING_TYPE_WATERSENSOR, place);
} else {
// widget: RelativeHumiditySensor
deviceDiscovered(device, THING_TYPE_HUMIDITYSENSOR);
deviceDiscovered(device, THING_TYPE_HUMIDITYSENSOR, place);
}
case CLASS_DOOR_LOCK:
// widget: UnlockDoorLockWithUnknownPosition
deviceDiscovered(device, THING_TYPE_DOOR_LOCK);
deviceDiscovered(device, THING_TYPE_DOOR_LOCK, place);
break;
case CLASS_PERGOLA:
deviceDiscovered(device, THING_TYPE_PERGOLA);
deviceDiscovered(device, THING_TYPE_PERGOLA, place);
break;
case CLASS_WINDOW_HANDLE:
// widget: ThreeWayWindowHandle
deviceDiscovered(device, THING_TYPE_WINDOW_HANDLE);
deviceDiscovered(device, THING_TYPE_WINDOW_HANDLE, place);
break;
case CLASS_TEMPERATURE_SENSOR:
// widget: TemperatureSensor
deviceDiscovered(device, THING_TYPE_TEMPERATURESENSOR);
deviceDiscovered(device, THING_TYPE_TEMPERATURESENSOR, place);
break;
case CLASS_GATE:
deviceDiscovered(device, THING_TYPE_GATE);
deviceDiscovered(device, THING_TYPE_GATE, place);
break;
case CLASS_ELECTRICITY_SENSOR:
if (hasEnergyConsumption(device)) {
deviceDiscovered(device, THING_TYPE_ELECTRICITYSENSOR);
deviceDiscovered(device, THING_TYPE_ELECTRICITYSENSOR, place);
} else {
logUnsupportedDevice(device);
}
break;
case CLASS_DOCK:
// widget: Dock
deviceDiscovered(device, THING_TYPE_DOCK);
deviceDiscovered(device, THING_TYPE_DOCK, place);
break;
case CLASS_SIREN:
deviceDiscovered(device, THING_TYPE_SIREN);
deviceDiscovered(device, THING_TYPE_SIREN, place);
break;
case CLASS_ADJUSTABLE_SLATS_ROLLER_SHUTTER:
deviceDiscovered(device, THING_TYPE_ADJUSTABLE_SLATS_ROLLERSHUTTER);
deviceDiscovered(device, THING_TYPE_ADJUSTABLE_SLATS_ROLLERSHUTTER, place);
break;
case CLASS_CAMERA:
if (hasMyfoxShutter(device)) {
// widget: MyFoxSecurityCamera
deviceDiscovered(device, THING_TYPE_MYFOX_CAMERA);
deviceDiscovered(device, THING_TYPE_MYFOX_CAMERA, place);
} else {
logUnsupportedDevice(device);
}
@ -315,6 +323,18 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
}
}
private @Nullable String getPlaceLabel(SomfyTahomaSetup setup, String oid) {
SomfyTahomaRootPlace root = setup.getRootPlace();
if (!oid.isEmpty() && root != null) {
for (SomfyTahomaSubPlace place : root.getSubPlaces()) {
if (oid.equals(place.getOid())) {
return place.getLabel();
}
}
}
return null;
}
private boolean isStateLess(SomfyTahomaDevice device) {
return device.getStates().isEmpty() || (device.getStates().size() == 1 && hasState(device, STATUS_STATE));
}
@ -366,8 +386,12 @@ public class SomfyTahomaItemDiscoveryService extends AbstractDiscoveryService
return device.getDefinition().getCommands().stream().anyMatch(cmd -> command.equals(cmd.getCommandName()));
}
private void deviceDiscovered(SomfyTahomaDevice device, ThingTypeUID thingTypeUID) {
deviceDiscovered(device.getLabel(), device.getDeviceURL(), device.getOid(), thingTypeUID,
private void deviceDiscovered(SomfyTahomaDevice device, ThingTypeUID thingTypeUID, @Nullable String place) {
String label = device.getLabel();
if (place != null && !place.isBlank()) {
label += " (" + place + ")";
}
deviceDiscovered(label, device.getDeviceURL(), device.getOid(), thingTypeUID,
hasState(device, RSSI_LEVEL_STATE));
}

View File

@ -179,6 +179,17 @@ public abstract class SomfyTahomaBaseThingHandler extends BaseThingHandler {
}
}
protected void sendCommandToSameDevicesInPlace(String cmd) {
sendCommandToSameDevicesInPlace(cmd, "[]");
}
protected void sendCommandToSameDevicesInPlace(String cmd, String param) {
SomfyTahomaBridgeHandler handler = getBridgeHandler();
if (handler != null) {
handler.sendCommandToSameDevicesInPlace(url, cmd, param, EXEC_URL + "apply");
}
}
protected void refresh(String channel) {
SomfyTahomaBridgeHandler handler = getBridgeHandler();
String stateName = stateNames.get(channel);

View File

@ -126,6 +126,8 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
*/
private String eventsId = "";
private Map<String, SomfyTahomaDevice> devicePlaces = new HashMap<>();
private ExpiringCache<List<SomfyTahomaDevice>> cachedDevices = new ExpiringCache<>(Duration.ofSeconds(30),
this::getDevices);
@ -345,13 +347,19 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
}
public @Nullable SomfyTahomaSetup getSetup() {
return invokeCallToURL(TAHOMA_API_URL + "setup", "", HttpMethod.GET, SomfyTahomaSetup.class);
SomfyTahomaSetup setup = invokeCallToURL(TAHOMA_API_URL + "setup", "", HttpMethod.GET, SomfyTahomaSetup.class);
if (setup != null) {
saveDevicePlaces(setup.getDevices());
}
return setup;
}
public List<SomfyTahomaDevice> getDevices() {
SomfyTahomaDevice[] response = invokeCallToURL(SETUP_URL + "devices", "", HttpMethod.GET,
SomfyTahomaDevice[].class);
return response != null ? List.of(response) : List.of();
List<SomfyTahomaDevice> devices = response != null ? List.of(response) : List.of();
saveDevicePlaces(devices);
return devices;
}
public synchronized @Nullable SomfyTahomaDevice getCachedDevice(String url) {
@ -364,6 +372,18 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
return null;
}
private void saveDevicePlaces(List<SomfyTahomaDevice> devices) {
devicePlaces.clear();
for (SomfyTahomaDevice device : devices) {
if (!device.getPlaceOID().isEmpty()) {
SomfyTahomaDevice newDevice = new SomfyTahomaDevice();
newDevice.setPlaceOID(device.getPlaceOID());
newDevice.setWidget(device.getWidget());
devicePlaces.put(device.getDeviceURL(), newDevice);
}
}
}
private void getTahomaUpdates() {
logger.debug("Getting Tahoma Updates...");
if (ThingStatus.OFFLINE == thing.getStatus() && !reLogin()) {
@ -648,6 +668,20 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
}, thingConfig.getRetryDelay(), TimeUnit.MILLISECONDS));
}
public void sendCommandToSameDevicesInPlace(String io, String command, String params, String url) {
SomfyTahomaDevice device = devicePlaces.get(io);
if (device != null && !device.getPlaceOID().isEmpty()) {
devicePlaces.forEach((deviceUrl, devicePlace) -> {
if (device.getPlaceOID().equals(devicePlace.getPlaceOID())
&& device.getWidget().equals(devicePlace.getWidget())) {
sendCommand(deviceUrl, command, params, url);
}
});
} else {
sendCommand(io, command, params, url);
}
}
private String getThingLabelByURL(String io) {
Thing th = getThingByDeviceUrl(io);
if (th != null) {

View File

@ -35,6 +35,7 @@ public class SomfyTahomaDevice {
private SomfyTahomaDeviceDefinition definition = new SomfyTahomaDeviceDefinition();
private List<SomfyTahomaState> states = new ArrayList<>();
private List<SomfyTahomaState> attributes = new ArrayList<>();
private String placeOID = "";
public String getLabel() {
return label;
@ -56,6 +57,10 @@ public class SomfyTahomaDevice {
return widget;
}
public void setWidget(String widget) {
this.widget = widget;
}
public SomfyTahomaDeviceDefinition getDefinition() {
return definition;
}
@ -67,4 +72,12 @@ public class SomfyTahomaDevice {
public List<SomfyTahomaState> getAttributes() {
return attributes;
}
public String getPlaceOID() {
return placeOID;
}
public void setPlaceOID(String placeOID) {
this.placeOID = placeOID;
}
}

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somfytahoma.internal.model;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SomfyTahomaRootPlace} holds information about all rooms bound
* to TahomaLink account.
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public class SomfyTahomaRootPlace {
private String label = "";
private int type;
private String oid = "";
private List<SomfyTahomaSubPlace> subPlaces = new ArrayList<>();
public String getLabel() {
return label;
}
public int getType() {
return type;
}
public String getOid() {
return oid;
}
public List<SomfyTahomaSubPlace> getSubPlaces() {
return subPlaces;
}
}

View File

@ -16,12 +16,14 @@ import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link SomfyTahomaSetup} holds information about devices bound
* to TahomaLink account.
*
* @author Ondrej Pecta - Initial contribution
* @author Laurent Garnier - Add rooms data
*/
@NonNullByDefault
public class SomfyTahomaSetup {
@ -30,6 +32,8 @@ public class SomfyTahomaSetup {
private List<SomfyTahomaGateway> gateways = new ArrayList<>();
private @Nullable SomfyTahomaRootPlace rootPlace;
public List<SomfyTahomaDevice> getDevices() {
return devices;
}
@ -37,4 +41,8 @@ public class SomfyTahomaSetup {
public List<SomfyTahomaGateway> getGateways() {
return gateways;
}
public @Nullable SomfyTahomaRootPlace getRootPlace() {
return rootPlace;
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.somfytahoma.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link SomfyTahomaSubPlace} holds information about a room bound
* to TahomaLink account.
*
* @author Laurent Garnier - Initial contribution
*/
@NonNullByDefault
public class SomfyTahomaSubPlace {
private String label = "";
private int type;
private String metadata = "";
private String oid = "";
public String getLabel() {
return label;
}
public int getType() {
return type;
}
public String getMetadata() {
return metadata;
}
public String getOid() {
return oid;
}
}