Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iaqualink] Add Support for AWS MQTT based IAqualink devices, such as Zodiac Hydroxinator #17671

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions bundles/org.openhab.binding.iaqualink/pom.xml
Original file line number Diff line number Diff line change
@@ -1,17 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>4.3.0-SNAPSHOT</version>
</parent>

<artifactId>org.openhab.binding.iaqualink</artifactId>

<name>openHAB Add-ons :: Bundles :: iAquaLink Binding</name>

<properties>
<bnd.importpackage>!org.graalvm.nativeimage.hosted;!org.apache.tapestry5.json.*,!org.codehaus.jettison.json.*,!org.json.*,!com.fasterxml.jackson.*,!jakarta.json.*</bnd.importpackage>
</properties>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk.iotdevicesdk</groupId>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can join @clinique to get rid of this SDK. For details see: #16893 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did actually have a look at using an alternative MQTT client, because it was originally such as pita to get the CRT working. The challenges that I had were:

  • Should support MQTTv5
  • Should work over Websockets
  • Should support AWS Signature V4 authentication

I'm sure that it's possible, but I doubt that it's simple. I would also question the longevity and applicability of what we were to build, given that AWS appear happy to build their own custom clients and protocol. We'd need to maintain a parallel pure-Java implementation.

I am sympathetic to the arguments regarding packaging the CRT, however given that AWS have such a strong position in IOT I would expect many bindings to have to interface with AWS, and hence wonder if a better solution would be to create a CRT OSGi for all bindings to use (instead of package themselves)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative client to use would be the Paho MQTT client. It does look like it supports MQTTv5 over websockets, although not everything that AWS requires, for example: eclipse-paho/paho.mqtt.java#830, eclipse-paho/paho.mqtt.java#858 etc.

It should be coming, but it's not there yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I searched a long time but found no alternative to get logged in with HiveMQ. There are very rare informations available when you try to escape AWS SDK.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PAHO client seems abandoned. So there does not seem to be an alternative. I'm no expert in OSGI packages and if it would be usefull to add this SDK as OSGI package. Maybe @wborn can give some guidance?

Copy link
Contributor

@lsiepel lsiepel Nov 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding support for forward slashes to the hivemq client would probably be best, but i can't asses how much work that or even if that is possible at all. Maybe create an issue for the specific hivemq here: https://github.com/hivemq/hivemq-mqtt-client

Copy link
Contributor

@clinique clinique Nov 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can get all the tokens, the problem I have is constructing the signed URL to connect to the broker (over wss). This is for a sigV4 URL, you may be using another approach like mtls etc?

It's a customAuthorizer :

MqttClientConnection connection = AwsIotMqttConnectionBuilder.newDefaultBuilder()
.withClientId("WX/USER/%s/%s/%s".formatted(userId, MQTT_USERNAME, productUuid))
.withEndpoint(endpoint).withUsername(MQTT_USERNAME).withCleanSession(false).withKeepAliveSecs(300)
.withConnectionEventCallbacks(this).withWebsockets(true)
.withWebsocketHandshakeTransform(handshakeArgs -> {
              HttpRequest httpRequest = handshakeArgs.getHttpRequest();
              httpRequest.addHeader("x-amz-customauthorizer-name", AUTHORIZER_NAME);
              httpRequest.addHeader("x-amz-customauthorizer-signature", tok[2]);
              httpRequest.addHeader("jwt", tok[0] + "." + tok[1]);
              handshakeArgs.complete(httpRequest);
}).build();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clinique That is certainly different to my case; I do not have all those fields (such as a username).

Copy link
Contributor Author

@jpg0 jpg0 Nov 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lsiepel I've spent a little more time on this and my conclusion is that we have a few choices:

  • Ship the AWS SDK in the bundle. Possibly it can be shaved down a bit when shading, although given that much of it is JNI I'm not expecting much.
  • Ship the AWS SDK as a shared bundle.
  • Go with Hive: this means that we'd need to get a change into Hive, which unfortunately is down to an issue with the JDK URLEncoder, which doesn't escape forward slashes (or + signs). No chance of getting this changed there, and I'm afraid that Hive may be reluctant to deviate from it's behaviour. Seems some people say that escaping isn't required and others (e.g. Amazon) say that it is. The problem arises due to the signature calculation which is based on the URI. Whilst Hive allows you to pass a String, it puts in into a URI object (then gets it back out), which means that you cannot provide a raw querystring. Pre-encoded query string is decoded hivemq/hivemq-mqtt-client#643
  • Go with Paho: Whilst I have it working, it comes with 2 caveats: 1. It relies on a commit which whilst is about 4 years old, does not yet appear in an official release. 2. Paho seems somewhat abandoned, hence the first problem.

I also had a quick look to see if I could find any other suitable libraries. There is nothing in Java, the only thing close I found was https://github.com/davidepianca98/KMQTT/ which is Kotlin. Personally I'd love to see Kotlin shipped with openHAB, however that's another very heavy bundle.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like @wborn's advice regarding the SDK. wdyt about adding it as a OSGi bundle?

<artifactId>aws-iot-device-sdk</artifactId>
<version>1.21.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk.crt</groupId>
<artifactId>aws-crt</artifactId>
<version>0.31.3</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.9.0</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>accessors-smart</artifactId>
<version>2.5.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>2.5.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* across the whole binding.
*
* @author Dan Cunningham - Initial contribution
* @author Jonathan Gilbert - Added V2 channel types
*/
@NonNullByDefault
public class IAqualinkBindingConstants {
Expand All @@ -36,7 +37,10 @@ public class IAqualinkBindingConstants {

public static final String CHANNEL_TYPE_ONETOUCH = "onetouch";

public static final String CHANNEL_TYPE_SCHEDULE_TIME = "schedule-time";

public static final ThingTypeUID IAQUALINK_DEVICE_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "controller");
public static final ThingTypeUID IAQUALINK_DEVICE_V2_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "controllerV2");

public static final ChannelTypeUID CHANNEL_TYPE_UID_ONETOUCH = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_ONETOUCH);
Expand All @@ -54,4 +58,6 @@ public class IAqualinkBindingConstants {
CHANNEL_TYPE_AUX_PENTAIRIB);
public static final ChannelTypeUID CHANNEL_TYPE_UID_AUX_HAYWARD = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_AUX_HAYWARD);
public static final ChannelTypeUID CHANNEL_TYPE_UID_SCHEDULE_TIME = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_SCHEDULE_TIME);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
package org.openhab.binding.iaqualink.internal;

import static org.openhab.binding.iaqualink.internal.IAqualinkBindingConstants.IAQUALINK_DEVICE_THING_TYPE_UID;
import static org.openhab.binding.iaqualink.internal.IAqualinkBindingConstants.IAQUALINK_DEVICE_V2_THING_TYPE_UID;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.iaqualink.internal.handler.IAqualinkHandler;
import org.openhab.binding.iaqualink.internal.v1.handler.IAqualinkHandler;
import org.openhab.binding.iaqualink.internal.v2.IAqualinkV2Handler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
Expand All @@ -33,6 +35,7 @@
* thing handlers.
*
* @author Dan Cunningham - Initial contribution
* @author Jonathan Gilbert - Added V2 handler
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.iaqualink")
Expand All @@ -47,14 +50,17 @@ public IAqualinkHandlerFactory(@Reference final HttpClientFactory httpClientFact

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return IAQUALINK_DEVICE_THING_TYPE_UID.equals(thingTypeUID);
return IAQUALINK_DEVICE_THING_TYPE_UID.equals(thingTypeUID)
|| IAQUALINK_DEVICE_V2_THING_TYPE_UID.equals(thingTypeUID);
}

@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (IAQUALINK_DEVICE_THING_TYPE_UID.equals(thingTypeUID)) {
return new IAqualinkHandler(thing, httpClient);
} else if (IAQUALINK_DEVICE_V2_THING_TYPE_UID.equals(thingTypeUID)) {
return new IAqualinkV2Handler(thing, httpClient);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.api;
package org.openhab.binding.iaqualink.internal.v1.api;

import java.io.IOException;
import java.lang.reflect.Type;
Expand All @@ -31,12 +31,12 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.iaqualink.internal.api.dto.AccountInfo;
import org.openhab.binding.iaqualink.internal.api.dto.Auxiliary;
import org.openhab.binding.iaqualink.internal.api.dto.Device;
import org.openhab.binding.iaqualink.internal.api.dto.Home;
import org.openhab.binding.iaqualink.internal.api.dto.OneTouch;
import org.openhab.binding.iaqualink.internal.api.dto.SignIn;
import org.openhab.binding.iaqualink.internal.v1.api.dto.AccountInfo;
import org.openhab.binding.iaqualink.internal.v1.api.dto.Auxiliary;
import org.openhab.binding.iaqualink.internal.v1.api.dto.Device;
import org.openhab.binding.iaqualink.internal.v1.api.dto.Home;
import org.openhab.binding.iaqualink.internal.v1.api.dto.OneTouch;
import org.openhab.binding.iaqualink.internal.v1.api.dto.SignIn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -343,7 +343,7 @@ private UriBuilder baseURI() {
/**
*
* @param <T>
* @param url
* @param uri
* @param typeOfT
* @return
* @throws IOException
Expand All @@ -355,7 +355,7 @@ private <T> T getAqualinkObject(URI uri, Type typeOfT) throws IOException, NotAu

/**
*
* @param url
* @param uri
* @return
* @throws IOException
* @throws NotAuthorizedException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.api.dto;
package org.openhab.binding.iaqualink.internal.v1.api.dto;

/**
* Account Info Object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.api.dto;
package org.openhab.binding.iaqualink.internal.v1.api.dto;

/**
* Auxiliary devices.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.api.dto;
package org.openhab.binding.iaqualink.internal.v1.api.dto;

/**
* Device refers to an iAqualink Pool Controller.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.api.dto;
package org.openhab.binding.iaqualink.internal.v1.api.dto;

import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.api.dto;
package org.openhab.binding.iaqualink.internal.v1.api.dto;

/**
* OneTouch Macros.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.api.dto;
package org.openhab.binding.iaqualink.internal.v1.api.dto;

/**
* Object used to login to service.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.handler;
package org.openhab.binding.iaqualink.internal.v1.handler;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.handler;
package org.openhab.binding.iaqualink.internal.v1.handler;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.iaqualink.internal.handler;
package org.openhab.binding.iaqualink.internal.v1.handler;

import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
Expand All @@ -36,14 +36,14 @@
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.iaqualink.internal.IAqualinkBindingConstants;
import org.openhab.binding.iaqualink.internal.api.IAqualinkClient;
import org.openhab.binding.iaqualink.internal.api.IAqualinkClient.NotAuthorizedException;
import org.openhab.binding.iaqualink.internal.api.dto.AccountInfo;
import org.openhab.binding.iaqualink.internal.api.dto.Auxiliary;
import org.openhab.binding.iaqualink.internal.api.dto.Device;
import org.openhab.binding.iaqualink.internal.api.dto.Home;
import org.openhab.binding.iaqualink.internal.api.dto.OneTouch;
import org.openhab.binding.iaqualink.internal.config.IAqualinkConfiguration;
import org.openhab.binding.iaqualink.internal.v1.api.IAqualinkClient;
import org.openhab.binding.iaqualink.internal.v1.api.IAqualinkClient.NotAuthorizedException;
import org.openhab.binding.iaqualink.internal.v1.api.dto.AccountInfo;
import org.openhab.binding.iaqualink.internal.v1.api.dto.Auxiliary;
import org.openhab.binding.iaqualink.internal.v1.api.dto.Device;
import org.openhab.binding.iaqualink.internal.v1.api.dto.Home;
import org.openhab.binding.iaqualink.internal.v1.api.dto.OneTouch;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
Expand Down
Loading