Skip to content

Commit 95ec85a

Browse files
authored
[salus] Add support for AWS (openhab#16807)
* AWS support Signed-off-by: Martin Grześlowski <[email protected]>
1 parent 280f5c5 commit 95ec85a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2270
-298
lines changed

bundles/org.openhab.binding.salus/NOTICE

+5
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,8 @@ checker-qual
2828
* License: GNU General Public License (GPL), version 2, with the classpath exception (https://checkerframework.org/manual/#license)
2929
* Project: https://checkerframework.org/
3030
* Source: https://github.com/typetools/checker-framework
31+
32+
aws-crt
33+
* License: Apache License 2.0
34+
* Project: https://github.com/awslabs/aws-crt-java
35+
* Source: https://github.com/awslabs/aws-crt-java

bundles/org.openhab.binding.salus/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ extensive experience, we accurately identify user needs and introduce products t
1010

1111
- **`salus-cloud-bridge`**: This bridge connects to Salus Cloud. Multiple bridges are supported for those with multiple
1212
accounts.
13+
- **`salus-aws-bridge`**: This bridge connects to AWS Salus Cloud. Multiple bridges are supported for those with multiple accounts.
1314
- **`salus-device`**: A generic Salus device that exposes all properties (as channels) from the Cloud without any
1415
modifications.
1516
- **`salus-it600-device`**: A temperature controller with extended capabilities.
@@ -31,6 +32,21 @@ assumed automatically based on the `oem_model`.
3132
| refreshInterval | integer (seconds) | Refresh time in seconds | 30 | no | yes |
3233
| propertiesRefreshInterval | integer (seconds) | How long device properties should be cached | 5 | no | yes |
3334

35+
### `salus-aws-bridge` Thing Configuration
36+
37+
| Name | Type | Description | Default | Required | Advanced |
38+
|---------------------------|-------------------|----------------------------------------------|----------------------------|----------|----------|
39+
| username | text | Username/email to log in to Salus Cloud | N/A | yes | no |
40+
| password | text | Password to log in to Salus Cloud | N/A | yes | no |
41+
| url | text | URL to Salus Cloud | https://eu.salusconnect.io | no | yes |
42+
| refreshInterval | integer (seconds) | Refresh time in seconds | 30 | no | yes |
43+
| propertiesRefreshInterval | integer (seconds) | How long device properties should be cached | 5 | no | yes |
44+
| userPoolId | text | | XGRz3CgoY | no | yes |
45+
| clientId | text | The app client ID | 4pk5efh3v84g5dav43imsv4fbj | no | yes |
46+
| region | text | Region with which the SDK should communicate | eu-central-1 | no | yes |
47+
| companyCode | text | | salus-eu | no | yes |
48+
| awsService | text | | a24u3z7zzwrtdl-ats | no | yes |
49+
3450
### `salus-device` and `salus-it600-device` Thing Configuration
3551

3652
| Name | Type | Description | Default | Required | Advanced |

bundles/org.openhab.binding.salus/pom.xml

+8
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@
3434
<scope>compile</scope>
3535
</dependency>
3636
<!-- END caffeine -->
37+
<!-- START AWS -->
38+
<dependency>
39+
<groupId>software.amazon.awssdk.crt</groupId>
40+
<artifactId>aws-crt</artifactId>
41+
<version>0.29.19</version>
42+
<scope>compile</scope>
43+
</dependency>
44+
<!-- END AWS -->
3745

3846
<dependency>
3947
<groupId>ch.qos.logback</groupId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Copyright (c) 2010-2024 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.salus.internal;
14+
15+
import java.util.SortedSet;
16+
17+
import org.eclipse.jdt.annotation.NonNullByDefault;
18+
import org.openhab.binding.salus.internal.rest.Device;
19+
import org.openhab.binding.salus.internal.rest.DeviceProperty;
20+
import org.openhab.binding.salus.internal.rest.exceptions.AuthSalusApiException;
21+
import org.openhab.binding.salus.internal.rest.exceptions.SalusApiException;
22+
23+
/**
24+
* The SalusApi class is responsible for interacting with a REST API to perform various operations related to the Salus
25+
* system. It handles authentication, token management, and provides methods to retrieve and manipulate device
26+
* information and properties.
27+
*
28+
* @author Martin Grześlowski - Initial contribution
29+
*/
30+
@NonNullByDefault
31+
public interface SalusApi {
32+
/**
33+
* Finds all available devices.
34+
*
35+
* @return A sorted set of Device objects representing the discovered devices.
36+
* @throws SalusApiException if an error occurs during device discovery.
37+
*/
38+
SortedSet<Device> findDevices() throws SalusApiException, AuthSalusApiException;
39+
40+
/**
41+
* Retrieves the properties of a specific device.
42+
*
43+
* @param dsn The Device Serial Number (DSN) identifying the device.
44+
* @return A sorted set of DeviceProperty objects representing the properties of the device.
45+
* @throws SalusApiException if an error occurs while retrieving device properties.
46+
*/
47+
SortedSet<DeviceProperty<?>> findDeviceProperties(String dsn) throws SalusApiException, AuthSalusApiException;
48+
49+
/**
50+
* Sets the value for a specific property of a device.
51+
*
52+
* @param dsn The Device Serial Number (DSN) identifying the device.
53+
* @param propertyName The name of the property to set.
54+
* @param value The new value for the property.
55+
* @return An Object representing the result of setting the property value.
56+
* @throws SalusApiException if an error occurs while setting the property value.
57+
*/
58+
Object setValueForProperty(String dsn, String propertyName, Object value)
59+
throws SalusApiException, AuthSalusApiException;
60+
}

bundles/org.openhab.binding.salus/src/main/java/org/openhab/binding/salus/internal/SalusBindingConstants.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ public class SalusBindingConstants {
3636
public static final ThingTypeUID SALUS_DEVICE_TYPE = new ThingTypeUID(BINDING_ID, "salus-device");
3737
public static final ThingTypeUID SALUS_IT600_DEVICE_TYPE = new ThingTypeUID(BINDING_ID, "salus-it600-device");
3838
public static final ThingTypeUID SALUS_SERVER_TYPE = new ThingTypeUID(BINDING_ID, "salus-cloud-bridge");
39+
public static final ThingTypeUID SALUS_AWS_TYPE = new ThingTypeUID(BINDING_ID, "salus-aws-bridge");
3940

4041
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(SALUS_DEVICE_TYPE,
41-
SALUS_IT600_DEVICE_TYPE, SALUS_SERVER_TYPE);
42+
SALUS_IT600_DEVICE_TYPE, SALUS_SERVER_TYPE, SALUS_AWS_TYPE);
4243

4344
public static class SalusCloud {
4445
public static final String DEFAULT_URL = "https://eu.salusconnect.io";

bundles/org.openhab.binding.salus/src/main/java/org/openhab/binding/salus/internal/SalusHandlerFactory.java

+39-10
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@
1212
*/
1313
package org.openhab.binding.salus.internal;
1414

15-
import static org.openhab.binding.salus.internal.SalusBindingConstants.SALUS_DEVICE_TYPE;
16-
import static org.openhab.binding.salus.internal.SalusBindingConstants.SALUS_IT600_DEVICE_TYPE;
17-
import static org.openhab.binding.salus.internal.SalusBindingConstants.SALUS_SERVER_TYPE;
18-
import static org.openhab.binding.salus.internal.SalusBindingConstants.SUPPORTED_THING_TYPES_UIDS;
15+
import static org.openhab.binding.salus.internal.SalusBindingConstants.*;
1916

17+
import java.util.Collections;
18+
import java.util.HashMap;
2019
import java.util.Hashtable;
20+
import java.util.Map;
2121

2222
import org.eclipse.jdt.annotation.NonNullByDefault;
2323
import org.eclipse.jdt.annotation.Nullable;
24-
import org.openhab.binding.salus.internal.discovery.CloudDiscovery;
25-
import org.openhab.binding.salus.internal.handler.CloudBridgeHandler;
24+
import org.openhab.binding.salus.internal.aws.handler.AwsCloudBridgeHandler;
25+
import org.openhab.binding.salus.internal.cloud.handler.CloudBridgeHandler;
26+
import org.openhab.binding.salus.internal.discovery.SalusDiscovery;
27+
import org.openhab.binding.salus.internal.handler.AbstractBridgeHandler;
2628
import org.openhab.binding.salus.internal.handler.DeviceHandler;
2729
import org.openhab.binding.salus.internal.handler.It600Handler;
2830
import org.openhab.core.config.discovery.DiscoveryService;
@@ -33,6 +35,7 @@
3335
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
3436
import org.openhab.core.thing.binding.ThingHandler;
3537
import org.openhab.core.thing.binding.ThingHandlerFactory;
38+
import org.osgi.framework.ServiceRegistration;
3639
import org.osgi.service.component.annotations.Activate;
3740
import org.osgi.service.component.annotations.Component;
3841
import org.osgi.service.component.annotations.Reference;
@@ -51,6 +54,8 @@ public class SalusHandlerFactory extends BaseThingHandlerFactory {
5154

5255
private final Logger logger = LoggerFactory.getLogger(SalusHandlerFactory.class);
5356
protected final @NonNullByDefault({}) HttpClientFactory httpClientFactory;
57+
private final Map<ThingHandler, ServiceRegistration<?>> discoveryServices = Collections
58+
.synchronizedMap(new HashMap<>());
5459

5560
@Activate
5661
public SalusHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
@@ -75,10 +80,18 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
7580
if (SALUS_SERVER_TYPE.equals(thingTypeUID)) {
7681
return newSalusCloudBridge(thing);
7782
}
83+
if (SALUS_AWS_TYPE.equals(thingTypeUID)) {
84+
return newSalusAwsBridge(thing);
85+
}
7886

7987
return null;
8088
}
8189

90+
@Override
91+
protected void removeHandler(ThingHandler thingHandler) {
92+
unregisterThingDiscovery(thingHandler);
93+
}
94+
8295
private ThingHandler newSalusDevice(Thing thing) {
8396
logger.debug("New Salus Device {}", thing.getUID().getId());
8497
return new DeviceHandler(thing);
@@ -91,12 +104,28 @@ private ThingHandler newIt600(Thing thing) {
91104

92105
private ThingHandler newSalusCloudBridge(Thing thing) {
93106
var handler = new CloudBridgeHandler((Bridge) thing, httpClientFactory);
94-
var cloudDiscovery = new CloudDiscovery(handler, handler, handler.getThing().getUID());
95-
registerThingDiscovery(cloudDiscovery);
107+
registerThingDiscovery(handler);
108+
return handler;
109+
}
110+
111+
private ThingHandler newSalusAwsBridge(Thing thing) {
112+
var handler = new AwsCloudBridgeHandler((Bridge) thing, httpClientFactory);
113+
registerThingDiscovery(handler);
96114
return handler;
97115
}
98116

99-
private synchronized void registerThingDiscovery(DiscoveryService discoveryService) {
100-
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>());
117+
private synchronized void registerThingDiscovery(AbstractBridgeHandler<?> handler) {
118+
var discoveryService = new SalusDiscovery(handler, handler.getThing().getUID());
119+
var serviceRegistration = bundleContext.registerService(DiscoveryService.class.getName(), discoveryService,
120+
new Hashtable<>());
121+
discoveryServices.put(handler, serviceRegistration);
122+
}
123+
124+
private synchronized void unregisterThingDiscovery(ThingHandler handler) {
125+
if (!discoveryServices.containsKey(handler)) {
126+
return;
127+
}
128+
var serviceRegistration = discoveryServices.get(handler);
129+
serviceRegistration.unregister();
101130
}
102131
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Copyright (c) 2010-2024 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.salus.internal.aws.handler;
14+
15+
import org.eclipse.jdt.annotation.NonNullByDefault;
16+
import org.openhab.binding.salus.internal.handler.AbstractBridgeConfig;
17+
18+
/**
19+
* @author Martin Grześlowski - Initial contribution
20+
*/
21+
@NonNullByDefault
22+
public class AwsCloudBridgeConfig extends AbstractBridgeConfig {
23+
private String userPoolId = "eu-central-1_XGRz3CgoY";
24+
private String identityPoolId = "60912c00-287d-413b-a2c9-ece3ccef9230";
25+
private String clientId = "4pk5efh3v84g5dav43imsv4fbj";
26+
private String region = "eu-central-1";
27+
private String companyCode = "salus-eu";
28+
private String awsService = "a24u3z7zzwrtdl-ats";
29+
30+
public AwsCloudBridgeConfig() {
31+
setUrl("https://service-api.eu.premium.salusconnect.io");
32+
}
33+
34+
public AwsCloudBridgeConfig(String username, String password, String url, long refreshInterval,
35+
long propertiesRefreshInterval, int maxHttpRetries, String userPoolId, String identityPoolId,
36+
String clientId, String region, String companyCode, String awsService) {
37+
super(username, password, url, refreshInterval, propertiesRefreshInterval, maxHttpRetries);
38+
this.userPoolId = userPoolId;
39+
this.identityPoolId = identityPoolId;
40+
this.clientId = clientId;
41+
this.region = region;
42+
this.companyCode = companyCode;
43+
this.awsService = awsService;
44+
if (url.isBlank()) {
45+
setUrl("https://service-api.eu.premium.salusconnect.io");
46+
}
47+
}
48+
49+
public String getUserPoolId() {
50+
return userPoolId;
51+
}
52+
53+
public void setUserPoolId(String userPoolId) {
54+
this.userPoolId = userPoolId;
55+
}
56+
57+
public String getIdentityPoolId() {
58+
return identityPoolId;
59+
}
60+
61+
public void setIdentityPoolId(String identityPoolId) {
62+
this.identityPoolId = identityPoolId;
63+
}
64+
65+
public String getClientId() {
66+
return clientId;
67+
}
68+
69+
public void setClientId(String clientId) {
70+
this.clientId = clientId;
71+
}
72+
73+
public String getRegion() {
74+
return region;
75+
}
76+
77+
public void setRegion(String region) {
78+
this.region = region;
79+
}
80+
81+
public String getCompanyCode() {
82+
return companyCode;
83+
}
84+
85+
public void setCompanyCode(String companyCode) {
86+
this.companyCode = companyCode;
87+
}
88+
89+
public String getAwsService() {
90+
return awsService;
91+
}
92+
93+
public void setAwsService(String awsService) {
94+
this.awsService = awsService;
95+
}
96+
97+
@Override
98+
public boolean isValid() {
99+
return super.isValid() && !userPoolId.isBlank() && !identityPoolId.isBlank() && !clientId.isBlank()
100+
&& !region.isBlank() && !companyCode.isBlank() && !awsService.isBlank();
101+
}
102+
103+
@Override
104+
public String toString() {
105+
return "AwsCloudBridgeConfig{" + //
106+
"userPoolId='" + userPoolId + '\'' + //
107+
"identityPoolId='" + identityPoolId + '\'' + //
108+
", clientId='" + clientId + '\'' + //
109+
", region='" + region + '\'' + //
110+
", companyCode='" + companyCode + '\'' + //
111+
", awsService='" + awsService + '\'' + //
112+
", username='" + username + '\'' + //
113+
", password='<SECRET>'" + //
114+
", url='" + url + '\'' + //
115+
", refreshInterval=" + refreshInterval + //
116+
", propertiesRefreshInterval=" + propertiesRefreshInterval + //
117+
", maxHttpRetries=" + maxHttpRetries + //
118+
'}';
119+
}
120+
}

0 commit comments

Comments
 (0)