* [innogysmarthome] Bug-fix - NPE solved which could occur when devices without a corresponding device state were returned by the API. That happened with the virtual device "NotificationSender" which is unimportant for the binding.
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] JavaDoc fixed
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimizations (warnings fixed)
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code refactoring (InnogyClient class split to make it smaller)
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization ; Potential bug with the reachable attribute solved (it was set multiple times within a loop)
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Tests added
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Tests added
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code changed to not overwrite the reachable state provided by the API (when no corresponding message is available)
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Copyright notice updated
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
* [innogysmarthome] Code and performance optimization
Signed-off-by: Sven Strohschein <sven.strohschein@gmail.com>
Co-authored-by: Sven Strohschein <sven.strohschein@gmail.com>
*/
package org.openhab.binding.innogysmarthome.internal.client;
-import static org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants.*;
import static org.openhab.binding.innogysmarthome.internal.client.Constants.*;
import java.io.IOException;
import java.net.URI;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
-import org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants;
import org.openhab.binding.innogysmarthome.internal.client.entity.StatusResponse;
import org.openhab.binding.innogysmarthome.internal.client.entity.action.Action;
import org.openhab.binding.innogysmarthome.internal.client.entity.action.ShutterAction;
import org.openhab.binding.innogysmarthome.internal.client.entity.device.Gateway;
import org.openhab.binding.innogysmarthome.internal.client.entity.device.State;
import org.openhab.binding.innogysmarthome.internal.client.entity.error.ErrorResponse;
-import org.openhab.binding.innogysmarthome.internal.client.entity.link.Link;
import org.openhab.binding.innogysmarthome.internal.client.entity.location.Location;
import org.openhab.binding.innogysmarthome.internal.client.entity.message.Message;
import org.openhab.binding.innogysmarthome.internal.client.exception.ApiException;
this.httpClient = httpClient;
}
- /**
- * @return the bridgeInfo
- */
- public @Nullable Gateway getBridgeDetails() {
- return bridgeDetails;
- }
-
/**
* Gets the status
*
* the {@link #configVersion} is set.
*
* @throws SessionExistsException thrown, if a session already exists
- * @throws IOException
- * @throws ApiException
*/
public void refreshStatus() throws IOException, ApiException, AuthenticationException {
logger.debug("Get innogy SmartHome status...");
/**
* Executes a HTTP GET request with default headers and returns data as object of type T.
*
- * @param url
+ * @param url request URL
* @param clazz type of data to return
- * @return
- * @throws IOException
- * @throws AuthenticationException
- * @throws ApiException
+ * @return response content
*/
private <T> T executeGet(final String url, final Class<T> clazz)
throws IOException, AuthenticationException, ApiException {
/**
* Executes a HTTP GET request with default headers and returns data as List of type T.
*
- * @param url
+ * @param url request URL
* @param clazz array type of data to return as list
- * @throws IOException
- * @throws AuthenticationException
- * @throws ApiException
+ * @return response content (as a List)
*/
private <T> List<T> executeGetList(final String url, final Class<T[]> clazz)
throws IOException, AuthenticationException, ApiException {
/**
* Executes a HTTP POST request with the given {@link Action} as content.
*
- * @param url
- * @param action
- * @return
- * @throws IOException
- * @throws AuthenticationException
- * @throws ApiException
+ * @param url request URL
+ * @param action action to execute
*/
- private ContentResponse executePost(final String url, final Action action)
+ private void executePost(final String url, final Action action)
throws IOException, AuthenticationException, ApiException {
final String json = gson.toJson(action);
logger.debug("Action {} JSON: {}", action.getType(), json);
- return request(httpClient.newRequest(url).method(HttpMethod.POST)
+ request(httpClient.newRequest(url).method(HttpMethod.POST)
.content(new StringContentProvider(json), CONTENT_TYPE).accept(CONTENT_TYPE));
}
}
public AccessTokenResponse getAccessTokenResponse() throws AuthenticationException, IOException {
+ @Nullable
final AccessTokenResponse accessTokenResponse;
try {
accessTokenResponse = oAuthService.getAccessTokenResponse();
/**
* Handles errors from the {@link ContentResponse} and throws the following errors:
*
- * @param response
+ * @param response response
* @param uri uri of api call made
- * @throws SessionExistsException
- * @throws SessionNotFoundException
* @throws ControllerOfflineException thrown, if the innogy SmartHome controller (SHC) is offline.
- * @throws IOException
- * @throws ApiException
- * @throws AuthenticationException
*/
- private void handleResponseErrors(final ContentResponse response, final URI uri)
- throws IOException, ApiException, AuthenticationException {
+ private void handleResponseErrors(final ContentResponse response, final URI uri) throws IOException, ApiException {
String content = "";
switch (response.getStatus()) {
/**
* Sets a new state of a SwitchActuator.
- *
- * @param capabilityId
- * @param state
- * @throws IOException
- * @throws ApiException
*/
public void setSwitchActuatorState(final String capabilityId, final boolean state)
throws IOException, ApiException, AuthenticationException {
/**
* Sets the dimmer level of a DimmerActuator.
- *
- * @param capabilityId
- * @param dimLevel
- * @throws IOException
- * @throws ApiException
*/
public void setDimmerActuatorState(final String capabilityId, final int dimLevel)
throws IOException, ApiException, AuthenticationException {
/**
* Sets the roller shutter level of a RollerShutterActuator.
- *
- * @param capabilityId
- * @param rollerShutterLevel
- * @throws IOException
- * @throws ApiException
- * @throws AuthenticationException
*/
public void setRollerShutterActuatorState(final String capabilityId, final int rollerShutterLevel)
throws IOException, ApiException, AuthenticationException {
/**
* Starts or stops moving a RollerShutterActuator
- *
- * @param capabilityId
- * @param rollerShutterAction
- * @throws IOException
- * @throws ApiException
- * @throws AuthenticationException
*/
public void setRollerShutterAction(final String capabilityId,
final ShutterAction.ShutterActions rollerShutterAction)
/**
* Sets a new state of a VariableActuator.
- *
- * @param capabilityId
- * @param state
- * @throws IOException
- * @throws ApiException
*/
public void setVariableActuatorState(final String capabilityId, final boolean state)
throws IOException, ApiException, AuthenticationException {
/**
* Sets the point temperature.
- *
- * @param capabilityId
- * @param pointTemperature
- * @throws IOException
- * @throws ApiException
*/
public void setPointTemperatureState(final String capabilityId, final double pointTemperature)
throws IOException, ApiException, AuthenticationException {
/**
* Sets the operation mode to "Auto" or "Manu".
- *
- * @param capabilityId
- * @param autoMode
- * @throws IOException
- * @throws ApiException
*/
public void setOperationMode(final String capabilityId, final boolean autoMode)
throws IOException, ApiException, AuthenticationException {
/**
* Sets the alarm state.
- *
- * @param capabilityId
- * @param alarmState
- * @throws IOException
- * @throws ApiException
*/
public void setAlarmActuatorState(final String capabilityId, final boolean alarmState)
throws IOException, ApiException, AuthenticationException {
/**
* Load the device and returns a {@link List} of {@link Device}s..
*
+ * @param deviceIds Ids of the devices to return
* @return List of Devices
- * @throws IOException
- * @throws ApiException
*/
- public List<Device> getDevices() throws IOException, ApiException, AuthenticationException {
+ public List<Device> getDevices(Collection<String> deviceIds)
+ throws IOException, ApiException, AuthenticationException {
logger.debug("Loading innogy devices...");
- return executeGetList(API_URL_DEVICE, Device[].class);
+ List<Device> devices = executeGetList(API_URL_DEVICE, Device[].class);
+ return devices.stream().filter(d -> deviceIds.contains(d.getId())).collect(Collectors.toList());
}
/**
* Loads the {@link Device} with the given deviceId.
- *
- * @param deviceId
- * @return
- * @throws IOException
- * @throws ApiException
*/
public Device getDeviceById(final String deviceId) throws IOException, ApiException, AuthenticationException {
logger.debug("Loading device with id {}...", deviceId);
return executeGet(API_URL_DEVICE_ID.replace("{id}", deviceId), Device.class);
}
- /**
- * Returns a {@link List} of all {@link Device}s with the full configuration details, {@link Capability}s and
- * states. Calling this may take a while...
- *
- * @return
- * @throws IOException
- * @throws ApiException
- */
- public List<Device> getFullDevices() throws IOException, ApiException, AuthenticationException {
- // LOCATIONS
- final List<Location> locationList = getLocations();
- final Map<String, Location> locationMap = new HashMap<>();
- for (final Location l : locationList) {
- locationMap.put(l.getId(), l);
- }
-
- // CAPABILITIES
- final List<Capability> capabilityList = getCapabilities();
- final Map<String, Capability> capabilityMap = new HashMap<>();
- for (final Capability c : capabilityList) {
- capabilityMap.put(c.getId(), c);
- }
-
- // CAPABILITY STATES
- final List<CapabilityState> capabilityStateList = getCapabilityStates();
- final Map<String, CapabilityState> capabilityStateMap = new HashMap<>();
- for (final CapabilityState cs : capabilityStateList) {
- capabilityStateMap.put(cs.getId(), cs);
- }
-
- // DEVICE STATES
- final List<DeviceState> deviceStateList = getDeviceStates();
- final Map<String, DeviceState> deviceStateMap = new HashMap<>();
- for (final DeviceState es : deviceStateList) {
- deviceStateMap.put(es.getId(), es);
- }
-
- // MESSAGES
- final List<Message> messageList = getMessages();
- final Map<String, List<Message>> deviceMessageMap = new HashMap<>();
- for (final Message m : messageList) {
- if (m.getDevices() != null && !m.getDevices().isEmpty()) {
- final String deviceId = m.getDevices().get(0).replace("/device/", "");
- List<Message> ml;
- if (deviceMessageMap.containsKey(deviceId)) {
- ml = deviceMessageMap.get(deviceId);
- } else {
- ml = new ArrayList<>();
- }
- ml.add(m);
- deviceMessageMap.put(deviceId, ml);
- }
- }
-
- // DEVICES
- final List<Device> deviceList = getDevices();
- for (final Device d : deviceList) {
- if (InnogyBindingConstants.BATTERY_POWERED_DEVICES.contains(d.getType())) {
- d.setIsBatteryPowered(true);
- }
-
- // location
- d.setLocation(locationMap.get(d.getLocationId()));
- final HashMap<String, Capability> deviceCapabilityMap = new HashMap<>();
-
- // capabilities and their states
- for (final String cl : d.getCapabilityLinkList()) {
- final Capability c = capabilityMap.get(Link.getId(cl));
- final String capabilityId = c.getId();
- final CapabilityState capabilityState = capabilityStateMap.get(capabilityId);
- c.setCapabilityState(capabilityState);
- deviceCapabilityMap.put(capabilityId, c);
- }
- d.setCapabilityMap(deviceCapabilityMap);
-
- // device states
- d.setDeviceState(deviceStateMap.get(d.getId()));
-
- // messages
- if (deviceMessageMap.containsKey(d.getId())) {
- d.setMessageList(deviceMessageMap.get(d.getId()));
- for (final Message m : d.getMessageList()) {
- switch (m.getType()) {
- case Message.TYPE_DEVICE_LOW_BATTERY:
- d.setLowBattery(true);
- d.setLowBatteryMessageId(m.getId());
- break;
- }
- }
- }
- }
-
- return deviceList;
- }
-
- /**
- * Returns the {@link Device} with the given deviceId with full configuration details, {@link Capability}s and
- * states. Calling this may take a little bit longer...
- *
- * @param deviceId
- * @return
- * @throws IOException
- * @throws ApiException
- */
- public Device getFullDeviceById(final String deviceId) throws IOException, ApiException, AuthenticationException {
- // LOCATIONS
- final List<Location> locationList = getLocations();
- final Map<String, Location> locationMap = new HashMap<>();
- for (final Location l : locationList) {
- locationMap.put(l.getId(), l);
- }
-
- // CAPABILITIES FOR DEVICE
- final List<Capability> capabilityList = getCapabilitiesForDevice(deviceId);
- final Map<String, Capability> capabilityMap = new HashMap<>();
- for (final Capability c : capabilityList) {
- capabilityMap.put(c.getId(), c);
- }
-
- // CAPABILITY STATES
- final List<CapabilityState> capabilityStateList = getCapabilityStates();
- final Map<String, CapabilityState> capabilityStateMap = new HashMap<>();
- for (final CapabilityState cs : capabilityStateList) {
- capabilityStateMap.put(cs.getId(), cs);
- }
-
- // DEVICE STATE
- final State state = getDeviceStateByDeviceId(deviceId);
- final DeviceState deviceState = new DeviceState();
- deviceState.setId(deviceId);
- deviceState.setState(state);
-
- // MESSAGES
- final List<Message> messageList = getMessages();
- final List<Message> ml = new ArrayList<>();
- final String deviceIdPath = "/device/" + deviceId;
-
- for (final Message m : messageList) {
- logger.trace("Message Type {} with ID {}", m.getType(), m.getId());
- if (m.getDevices() != null && !m.getDevices().isEmpty()) {
- for (final String li : m.getDevices()) {
- if (deviceIdPath.equals(li)) {
- ml.add(m);
- }
- }
- }
- }
-
- // DEVICE
- final Device d = getDeviceById(deviceId);
- if (BATTERY_POWERED_DEVICES.contains(d.getType())) {
- d.setIsBatteryPowered(true);
- d.setLowBattery(false);
- }
-
- // location
- d.setLocation(locationMap.get(d.getLocationId()));
-
- // capabilities and their states
- final HashMap<String, Capability> deviceCapabilityMap = new HashMap<>();
- for (final String cl : d.getCapabilityLinkList()) {
-
- final Capability c = capabilityMap.get(Link.getId(cl));
- c.setCapabilityState(capabilityStateMap.get(c.getId()));
- deviceCapabilityMap.put(c.getId(), c);
-
- }
- d.setCapabilityMap(deviceCapabilityMap);
-
- // device states
- d.setDeviceState(deviceState);
-
- // messages
- if (!ml.isEmpty()) {
- d.setMessageList(ml);
- for (final Message m : d.getMessageList()) {
- switch (m.getType()) {
- case Message.TYPE_DEVICE_LOW_BATTERY:
- d.setLowBattery(true);
- d.setLowBatteryMessageId(m.getId());
- break;
- }
- }
- }
-
- return d;
- }
-
/**
* Loads the states for all {@link Device}s.
- *
- * @return
- * @throws IOException
- * @throws ApiException
*/
public List<DeviceState> getDeviceStates() throws IOException, ApiException, AuthenticationException {
logger.debug("Loading device states...");
/**
* Loads the device state for the given deviceId.
- *
- * @param deviceId
- * @return
- * @throws IOException
- * @throws ApiException
*/
public State getDeviceStateByDeviceId(final String deviceId)
throws IOException, ApiException, AuthenticationException {
* Loads the locations and returns a {@link List} of {@link Location}s.
*
* @return a List of Devices
- * @throws IOException
- * @throws ApiException
*/
public List<Location> getLocations() throws IOException, ApiException, AuthenticationException {
logger.debug("Loading locations...");
* Loads and returns a {@link List} of {@link Capability}s for the given deviceId.
*
* @param deviceId the id of the {@link Device}
- * @return
- * @throws IOException
- * @throws ApiException
+ * @return capabilities of the device
*/
public List<Capability> getCapabilitiesForDevice(final String deviceId)
throws IOException, ApiException, AuthenticationException {
/**
* Loads and returns a {@link List} of all {@link Capability}s.
- *
- * @return
- * @throws IOException
- * @throws ApiException
*/
public List<Capability> getCapabilities() throws IOException, ApiException, AuthenticationException {
logger.debug("Loading capabilities...");
/**
* Loads and returns a {@link List} of all {@link Capability}States.
- *
- * @return
- * @throws IOException
- * @throws ApiException
*/
public List<CapabilityState> getCapabilityStates() throws IOException, ApiException, AuthenticationException {
logger.debug("Loading capability states...");
/**
* Returns a {@link List} of all {@link Message}s.
- *
- * @return
- * @throws IOException
- * @throws ApiException
*/
public List<Message> getMessages() throws IOException, ApiException, AuthenticationException {
logger.debug("Loading messages...");
public String getConfigVersion() {
return configVersion;
}
-
- /**
- * @param configVersion the configVersion to set
- */
- public void setConfigVersion(final String configVersion) {
- this.configVersion = configVersion;
- }
}
import static org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants.*;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
+import java.util.*;
import org.openhab.binding.innogysmarthome.internal.client.entity.capability.Capability;
import org.openhab.binding.innogysmarthome.internal.client.entity.location.Location;
protected static final String PROTOCOL_ID_VIRTUAL = "Virtual";
protected static final String PROTOCOL_ID_WMBUS = "wMBus";
- public static final List<String> EMPTY_CAPABILITY_LINK_LIST = new ArrayList<>();
-
/**
* Unique id for the device, always available in model.
*/
private DeviceConfig config;
- /**
- * Contains a list of the device capabilities.
- *
- * Optional.
- */
- @SerializedName("capabilities")
- private List<String> capabilityLinkList;
+ private List<String> capabilities;
- private HashMap<String, Capability> capabilityMap;
+ private Map<String, Capability> capabilityMap;
private DeviceState deviceState;
private List<Message> messageList;
private boolean lowBattery;
- /**
- * Stores the message id, that contains the low battery state. This is needed to identify the device, when the
- * message
- * with that id is deleted (thus low battery state is false again).
- */
- private String lowBatteryMessageId;
/**
* Stores, if the {@link Device} is battery powered.
/**
* @return the capabilityList
*/
- public List<String> getCapabilityLinkList() {
- if (capabilityLinkList != null) {
- return capabilityLinkList;
- } else {
- return EMPTY_CAPABILITY_LINK_LIST;
- }
+ public List<String> getCapabilities() {
+ return Objects.requireNonNullElse(capabilities, Collections.emptyList());
}
/**
* @param capabilityList the capabilityList to set
*/
- public void setCapabilityList(List<String> capabilityList) {
- this.capabilityLinkList = capabilityList;
+ public void setCapabilities(List<String> capabilityList) {
+ this.capabilities = capabilityList;
}
/**
* @param capabilityMap the capabilityMap to set
*/
- public void setCapabilityMap(HashMap<String, Capability> capabilityMap) {
+ public void setCapabilityMap(Map<String, Capability> capabilityMap) {
this.capabilityMap = capabilityMap;
}
/**
* @return the capabilityMap
*/
- public HashMap<String, Capability> getCapabilityMap() {
+ public Map<String, Capability> getCapabilityMap() {
return this.capabilityMap;
}
}
/**
- * @param locationList the locationList to set
+ * @param locationLink the locationList to set
*/
public void setLocation(String locationLink) {
this.locationLink = locationLink;
*/
public void setMessageList(List<Message> messageList) {
this.messageList = messageList;
-
- for (final Message m : messageList) {
- setLowBattery(Message.TYPE_DEVICE_LOW_BATTERY.equals(m.getType()));
- setReachable(!Message.TYPE_DEVICE_UNREACHABLE.equals(m.getType()));
+ applyMessageList(messageList);
+ }
+
+ private void applyMessageList(List<Message> messageList) {
+ if (messageList != null && !messageList.isEmpty()) {
+ boolean isUnreachableMessageFound = false;
+ boolean isLowBatteryMessageFound = false;
+ for (final Message message : messageList) {
+ switch (message.getType()) {
+ case Message.TYPE_DEVICE_UNREACHABLE:
+ isUnreachableMessageFound = true;
+ break;
+ case Message.TYPE_DEVICE_LOW_BATTERY:
+ isLowBatteryMessageFound = true;
+ break;
+ }
+ }
+ if (isUnreachableMessageFound) {
+ setReachable(false); // overwrite only when there is a corresponding message (to keep the state of the
+ // API in other cases)
+ }
+ if (isLowBatteryMessageFound) {
+ setLowBattery(true); // overwrite only when there is a corresponding message (to keep the state of the
+ // API in other cases)
+ }
}
}
*
* @param isReachable
*/
- public void setReachable(boolean isReachable) {
+ private void setReachable(boolean isReachable) {
if (getDeviceState().hasIsReachableState()) {
getDeviceState().setReachable(isReachable);
}
*
* @param hasLowBattery
*/
- public void setLowBattery(boolean hasLowBattery) {
+ private void setLowBattery(boolean hasLowBattery) {
this.lowBattery = hasLowBattery;
}
return lowBattery;
}
- public String getLowBatteryMessageId() {
- return this.lowBatteryMessageId;
- }
-
- public void setLowBatteryMessageId(String messageId) {
- this.lowBatteryMessageId = messageId;
- }
-
/**
* Returns true, if the {@link Device} is battery powered.
*
import org.openhab.binding.innogysmarthome.internal.listener.DeviceStatusListener;
import org.openhab.binding.innogysmarthome.internal.listener.EventListener;
import org.openhab.binding.innogysmarthome.internal.manager.DeviceStructureManager;
+import org.openhab.binding.innogysmarthome.internal.manager.FullDeviceManager;
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.auth.client.oauth2.OAuthClientService;
if (checkOnAuthCode()) {
final InnogyClient localClient = createInnogyClient(oAuthService, httpClient);
client = localClient;
- deviceStructMan = new DeviceStructureManager(localClient);
+ deviceStructMan = new DeviceStructureManager(createFullDeviceManager(localClient));
oAuthService.addAccessTokenRefreshListener(this);
registerDeviceStatusListener(InnogyBridgeHandler.this);
scheduleRestartClient(false);
return scheduler;
}
+ FullDeviceManager createFullDeviceManager(InnogyClient client) {
+ return new FullDeviceManager(client);
+ }
+
InnogyClient createInnogyClient(final OAuthClientService oAuthService, final HttpClient httpClient) {
return new InnogyClient(oAuthService, httpClient);
}
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
-import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
boolean deviceChanged = false;
final String linkedCapabilityId = event.getSourceId();
- HashMap<String, Capability> capabilityMap = device.getCapabilityMap();
+ Map<String, Capability> capabilityMap = device.getCapabilityMap();
Capability capability = capabilityMap.get(linkedCapabilityId);
logger.trace("Loaded Capability {}, {} with id {}, device {} from device id {}", capability.getType(),
capability.getName(), capability.getId(), capability.getDeviceLink(), device.getId());
private final Logger logger = LoggerFactory.getLogger(DeviceStructureManager.class);
- private final InnogyClient client;
+ private final FullDeviceManager deviceManager;
private final Map<String, Device> deviceMap;
private final Map<String, Device> capabilityIdToDeviceMap;
private String bridgeDeviceId = "";
/**
* Constructs the {@link DeviceStructureManager}.
*
- * @param client the {@link InnogyClient}
+ * @param deviceManager the {@link FullDeviceManager}
*/
- public DeviceStructureManager(InnogyClient client) {
- this.client = client;
+ public DeviceStructureManager(FullDeviceManager deviceManager) {
+ this.deviceManager = deviceManager;
deviceMap = Collections.synchronizedMap(new HashMap<>());
capabilityIdToDeviceMap = new ConcurrentHashMap<>();
}
public void refreshDevices() throws IOException, ApiException, AuthenticationException {
deviceMap.clear();
capabilityIdToDeviceMap.clear();
- List<Device> devices = client.getFullDevices();
+ List<Device> devices = deviceManager.getFullDevices();
for (Device d : devices) {
handleRefreshedDevice(d);
}
*/
public void refreshDevice(String deviceId) throws IOException, ApiException, AuthenticationException {
logger.trace("Refreshing Device with id '{}'", deviceId);
- Device d = client.getFullDeviceById(deviceId);
+ Device d = deviceManager.getFullDeviceById(deviceId);
handleRefreshedDevice(d);
}
getDeviceMap().put(device.getId(), device);
}
- for (String cl : device.getCapabilityLinkList()) {
+ for (String cl : device.getCapabilities()) {
capabilityIdToDeviceMap.put(Link.getId(cl), device);
}
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.innogysmarthome.internal.manager;
+
+import static org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants.BATTERY_POWERED_DEVICES;
+
+import java.io.IOException;
+import java.util.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.innogysmarthome.internal.client.InnogyClient;
+import org.openhab.binding.innogysmarthome.internal.client.entity.capability.Capability;
+import org.openhab.binding.innogysmarthome.internal.client.entity.capability.CapabilityState;
+import org.openhab.binding.innogysmarthome.internal.client.entity.device.Device;
+import org.openhab.binding.innogysmarthome.internal.client.entity.device.DeviceState;
+import org.openhab.binding.innogysmarthome.internal.client.entity.link.Link;
+import org.openhab.binding.innogysmarthome.internal.client.entity.location.Location;
+import org.openhab.binding.innogysmarthome.internal.client.entity.message.Message;
+import org.openhab.binding.innogysmarthome.internal.client.exception.ApiException;
+import org.openhab.binding.innogysmarthome.internal.client.exception.AuthenticationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Sven Strohschein - Initial contribution (but only created by refactoring the InnogyClient class)
+ */
+@NonNullByDefault
+public class FullDeviceManager {
+
+ private final Logger logger = LoggerFactory.getLogger(FullDeviceManager.class);
+
+ private final InnogyClient client;
+
+ public FullDeviceManager(InnogyClient client) {
+ this.client = client;
+ }
+
+ /**
+ * Returns a {@link List} of all {@link Device}s with the full configuration details, {@link Capability}s and
+ * states. Calling this may take a while...
+ */
+ public List<Device> getFullDevices() throws IOException, ApiException, AuthenticationException {
+
+ final Map<String, Location> locationMap = createLocationMap(client);
+ final Map<String, Capability> capabilityMap = createCapabilityMap(client);
+ final Map<String, DeviceState> deviceStateMap = createDeviceStateMap(client);
+ final Map<String, List<Message>> messageMap = createMessageMap(client);
+
+ final List<Device> deviceList = client.getDevices(deviceStateMap.keySet());
+ for (final Device device : deviceList) {
+ final String deviceId = device.getId();
+ initializeDevice(device, deviceStateMap.get(deviceId), locationMap, capabilityMap,
+ getMessageList(device, messageMap));
+ }
+ return deviceList;
+ }
+
+ /**
+ * Returns the {@link Device} with the given deviceId with full configuration details, {@link Capability}s and
+ * states. Calling this may take a little bit longer...
+ */
+ public Device getFullDeviceById(final String deviceId) throws IOException, ApiException, AuthenticationException {
+ final Map<String, Location> locationMap = createLocationMap(client);
+ final Map<String, Capability> capabilityMap = createCapabilityMap(deviceId, client);
+ final List<Message> messageMap = createMessageMap(deviceId, client);
+
+ final DeviceState deviceState = new DeviceState();
+ deviceState.setId(deviceId);
+ deviceState.setState(client.getDeviceStateByDeviceId(deviceId));
+
+ final Device device = client.getDeviceById(deviceId);
+ initializeDevice(device, deviceState, locationMap, capabilityMap, messageMap);
+ return device;
+ }
+
+ private void initializeDevice(Device device, @Nullable DeviceState deviceState, Map<String, Location> locationMap,
+ Map<String, Capability> capabilityMap, List<Message> messageList) {
+
+ device.setDeviceState(deviceState);
+
+ if (isBatteryPowered(device)) {
+ device.setIsBatteryPowered(true);
+ }
+
+ device.setLocation(locationMap.get(device.getLocationId()));
+
+ device.setCapabilityMap(createDeviceCapabilityMap(device, capabilityMap));
+
+ device.setMessageList(messageList);
+ }
+
+ private static boolean isBatteryPowered(Device device) {
+ return BATTERY_POWERED_DEVICES.contains(device.getType());
+ }
+
+ private List<Message> getMessageList(Device device, Map<String, List<Message>> messageMap) {
+ return Objects.requireNonNullElse(messageMap.get(device.getId()), Collections.emptyList());
+ }
+
+ private static Map<String, Location> createLocationMap(InnogyClient client)
+ throws IOException, ApiException, AuthenticationException {
+ final List<Location> locationList = client.getLocations();
+ final Map<String, Location> locationMap = new HashMap<>(locationList.size());
+ for (final Location location : locationList) {
+ locationMap.put(location.getId(), location);
+ }
+ return locationMap;
+ }
+
+ private static Map<String, CapabilityState> createCapabilityStateMap(InnogyClient client)
+ throws IOException, ApiException, AuthenticationException {
+ final List<CapabilityState> capabilityStateList = client.getCapabilityStates();
+ final Map<String, CapabilityState> capabilityStateMap = new HashMap<>(capabilityStateList.size());
+ for (final CapabilityState capabilityState : capabilityStateList) {
+ capabilityStateMap.put(capabilityState.getId(), capabilityState);
+ }
+ return capabilityStateMap;
+ }
+
+ private static Map<String, Capability> createCapabilityMap(InnogyClient client)
+ throws IOException, ApiException, AuthenticationException {
+
+ final Map<String, CapabilityState> capabilityStateMap = createCapabilityStateMap(client);
+ final List<Capability> capabilityList = client.getCapabilities();
+
+ return initializeCapabilities(capabilityStateMap, capabilityList);
+ }
+
+ private static Map<String, Capability> createCapabilityMap(String deviceId, InnogyClient client)
+ throws IOException, ApiException, AuthenticationException {
+
+ final Map<String, CapabilityState> capabilityStateMap = createCapabilityStateMap(client);
+ final List<Capability> capabilityList = client.getCapabilitiesForDevice(deviceId);
+
+ return initializeCapabilities(capabilityStateMap, capabilityList);
+ }
+
+ private static Map<String, Capability> initializeCapabilities(Map<String, CapabilityState> capabilityStateMap,
+ List<Capability> capabilityList) {
+ final Map<String, Capability> capabilityMap = new HashMap<>(capabilityList.size());
+ for (final Capability capability : capabilityList) {
+ String capabilityId = capability.getId();
+
+ CapabilityState capabilityState = capabilityStateMap.get(capabilityId);
+ capability.setCapabilityState(capabilityState);
+
+ capabilityMap.put(capabilityId, capability);
+ }
+ return capabilityMap;
+ }
+
+ private static Map<String, Capability> createDeviceCapabilityMap(Device device,
+ Map<String, Capability> capabilityMap) {
+
+ final HashMap<String, Capability> deviceCapabilityMap = new HashMap<>();
+ for (final String capabilityValue : device.getCapabilities()) {
+ final Capability capability = capabilityMap.get(Link.getId(capabilityValue));
+ final String capabilityId = capability.getId();
+ deviceCapabilityMap.put(capabilityId, capability);
+ }
+ return deviceCapabilityMap;
+ }
+
+ private static Map<String, DeviceState> createDeviceStateMap(InnogyClient client)
+ throws IOException, ApiException, AuthenticationException {
+ final List<DeviceState> deviceStateList = client.getDeviceStates();
+ final Map<String, DeviceState> deviceStateMap = new HashMap<>(deviceStateList.size());
+ for (final DeviceState deviceState : deviceStateList) {
+ deviceStateMap.put(deviceState.getId(), deviceState);
+ }
+ return deviceStateMap;
+ }
+
+ private List<Message> createMessageMap(String deviceId, InnogyClient client)
+ throws IOException, ApiException, AuthenticationException {
+ final List<Message> messages = client.getMessages();
+ final List<Message> messageList = new ArrayList<>();
+ final String deviceIdPath = "/device/" + deviceId;
+
+ for (final Message message : messages) {
+ logger.trace("Message Type {} with ID {}", message.getType(), message.getId());
+ if (message.getDevices() != null && !message.getDevices().isEmpty()) {
+ for (final String li : message.getDevices()) {
+ if (deviceIdPath.equals(li)) {
+ messageList.add(message);
+ }
+ }
+ }
+ }
+ return messageList;
+ }
+
+ private static Map<String, List<Message>> createMessageMap(InnogyClient client)
+ throws IOException, ApiException, AuthenticationException {
+ final List<Message> messageList = client.getMessages();
+ final Map<String, List<Message>> deviceMessageMap = new HashMap<>();
+ for (final Message message : messageList) {
+ if (message.getDevices() != null && !message.getDevices().isEmpty()) {
+ final String deviceId = message.getDevices().get(0).replace("/device/", "");
+ List<Message> ml;
+ if (deviceMessageMap.containsKey(deviceId)) {
+ ml = deviceMessageMap.get(deviceId);
+ } else {
+ ml = new ArrayList<>();
+ }
+ ml.add(message);
+ deviceMessageMap.put(deviceId, ml);
+ }
+ }
+ return deviceMessageMap;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.innogysmarthome.internal.client.entity.device;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.innogysmarthome.internal.client.entity.message.Message;
+import org.openhab.binding.innogysmarthome.internal.client.entity.state.BooleanState;
+
+/**
+ * @author Sven Strohschein - Initial contribution
+ */
+public class DeviceTest {
+
+ @Test
+ public void testSetMessageListLowBatteryMessage() {
+ Device device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(Collections.singletonList(createMessage(Message.TYPE_DEVICE_LOW_BATTERY)));
+
+ assertTrue(device.isReachable());
+ assertTrue(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListUnreachableMessage() {
+ Device device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(Collections.singletonList(createMessage(Message.TYPE_DEVICE_UNREACHABLE)));
+
+ assertFalse(device.isReachable());
+ assertFalse(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListResetByEmpty() {
+ Device device = createDevice();
+
+ assertNull(device.getMessageList());
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ List<Message> messages = Arrays.asList(createMessage(Message.TYPE_DEVICE_LOW_BATTERY),
+ createMessage(Message.TYPE_DEVICE_UNREACHABLE));
+ device.setMessageList(messages);
+
+ assertEquals(messages, device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+
+ device.setMessageList(Collections.emptyList());
+
+ // Nothing should get changed.
+ // New messages are only set in real-life when the device is refreshed with new data of the API.
+ // Therefore the data of the API should be kept / not overwritten when no corresponding messages are available.
+ assertEquals(Collections.emptyList(), device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListResetByNULL() {
+ Device device = createDevice();
+
+ assertNull(device.getMessageList());
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ List<Message> messages = Arrays.asList(createMessage(Message.TYPE_DEVICE_LOW_BATTERY),
+ createMessage(Message.TYPE_DEVICE_UNREACHABLE));
+ device.setMessageList(messages);
+
+ assertEquals(messages, device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+
+ device.setMessageList(null);
+
+ // Nothing should get changed.
+ // New messages are only set in real-life when the device is refreshed with new data of the API.
+ // Therefore the data of the API should be kept / not overwritten when no corresponding messages are available.
+ assertNull(device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListResetByUnimportantMessage() {
+ Device device = createDevice();
+
+ assertNull(device.getMessageList());
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ List<Message> messages = Arrays.asList(createMessage(Message.TYPE_DEVICE_LOW_BATTERY),
+ createMessage(Message.TYPE_DEVICE_UNREACHABLE));
+ device.setMessageList(messages);
+
+ assertEquals(messages, device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+
+ messages = Collections.singletonList(createMessage("UNKNOWN"));
+ device.setMessageList(messages);
+
+ // Nothing should get changed.
+ // New messages are only set in real-life when the device is refreshed with new data of the API.
+ // Therefore the data of the API should be kept / not overwritten when no corresponding messages are available.
+ assertEquals(messages, device.getMessageList());
+ assertFalse(device.isReachable());
+ assertTrue(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListUnimportantMessage() {
+ Device device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(Collections.singletonList(createMessage("UNKNOWN")));
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+ }
+
+ private Message createMessage(String messageType) {
+ Message message = new Message();
+ message.setType(messageType);
+ return message;
+ }
+
+ @Test
+ public void testSetMessageListNULL() {
+ Device device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(null);
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+ }
+
+ @Test
+ public void testSetMessageListEmpty() {
+ Device device = createDevice();
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+
+ device.setMessageList(Collections.emptyList());
+
+ assertTrue(device.isReachable());
+ assertFalse(device.hasLowBattery());
+ }
+
+ private static Device createDevice() {
+ BooleanState isReachableState = new BooleanState();
+ isReachableState.setValue(true);
+
+ State state = new State();
+ state.setIsReachable(isReachableState);
+
+ DeviceState deviceState = new DeviceState();
+ deviceState.setState(state);
+
+ Device device = new Device();
+ device.setDeviceState(deviceState);
+ return device;
+ }
+}
import org.openhab.binding.innogysmarthome.internal.client.InnogyClient;
import org.openhab.binding.innogysmarthome.internal.client.entity.device.Device;
import org.openhab.binding.innogysmarthome.internal.client.entity.device.DeviceConfig;
+import org.openhab.binding.innogysmarthome.internal.manager.FullDeviceManager;
import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.config.core.Configuration;
private class InnogyBridgeHandlerAccessible extends InnogyBridgeHandler {
private final InnogyClient innogyClientMock;
+ private final FullDeviceManager fullDeviceManagerMock;
private final ScheduledExecutorService schedulerMock;
private int executionCount;
private int directExecutionCount;
bridgeDevice.setConfig(new DeviceConfig());
innogyClientMock = mock(InnogyClient.class);
- when(innogyClientMock.getFullDevices()).thenReturn(Collections.singletonList(bridgeDevice));
+ fullDeviceManagerMock = mock(FullDeviceManager.class);
+ when(fullDeviceManagerMock.getFullDevices()).thenReturn(Collections.singletonList(bridgeDevice));
schedulerMock = mock(ScheduledExecutorService.class);
return directExecutionCount;
}
+ @Override
+ @NonNull
+ FullDeviceManager createFullDeviceManager(@NonNull InnogyClient client) {
+ return fullDeviceManagerMock;
+ }
+
@Override
@NonNull
InnogyClient createInnogyClient(@NonNull OAuthClientService oAuthService, @NonNull HttpClient httpClient) {