added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path=".apt_generated">
<attributes>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path=".apt_generated_tests">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.iaqualink</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,121 @@
# iAquaLink Binding
This binding supports:
* Any iAquaLink based pool system
* Reading auxiliary, temperature, pump, chemistry and system values
* Controlling system, auxiliary, lighting, and temperature settings
## Binding Configuration
The binding requires the iAquaLink user name and password.
If you have more then one pool system registered to an account, you may optionally specify the pool serial ID/Number to use, otherwise the first pool controller will be used.
## Manual Thing Configuration
```
Thing iaqualink:controller:pool [ userName="user@domain.com", password="somepassword"]
```
## Channels
The following is a list of supported channels.
Auxiliary and OneTouch channels will be dynamically added depending on what a system reports as being supported.
Auxiliary channels that are of a number type represent lighting modes (typically 0-15).
Auxiliary channels that are dimmer types can set the light value in increments of 25 (0,25,50,750,100).
The Auxiliary channel type will be dynamically assigned based on the controller configuration.
Heater status can be OFF (0), Enabled/ON (3), or Heating (1).
| Channel Type ID | Item Type |
|---------------------|----------------------------|
| status | String |
| system_type | Number |
| temp_scale | String |
| spa_temp | Number:Temperature |
| pool_temp | Number:Temperature |
| air_temp | Number:Temperature |
| spa_set_point | Number:Temperature |
| pool_set_point | Number:Temperature |
| cover_pool | Switch |
| freeze_protection | Switch |
| spa_pump | Switch |
| pool_pump | Switch |
| spa_heater | Switch |
| pool_heater | Switch |
| solar_heater | Switch |
| spa_heater_status | Number |
| pool_heater_status | Number |
| solar_heater_status | Number |
| spa_salinity | Number |
| pool_salinity | Number |
| orp | Number |
| ph | Number |
| onetouch_1 | Switch |
| onetouch_n+1 | Switch |
| aux_1 | Switch or String or Dimmer |
| aux_n+1 | Switch or String or Dimmer |
### Color/Mood Auxiliary Channels
String auxiliary channels can control a variety of lighting moods/colors depending on what type of lighting system is installed.
The following is a table of aux_n channel values (String) to lighting set descriptions values.
The binding will automatically detect which color system is enabled and add the appropriate channel type with the following option labels.
Colors can be set, but only On or Off is reported back as the current state of the channel.
| String Value | jandy Color | Jandy LED Water Colors | Pentair SAm/SAL | Hayward Universal | Pentair intelliBrite |
|--------------|----------------|------------------------|-----------------|-------------------|----------------------|
| "off" | Off | Off | Off | Off | Off |
| "on" | On | On | On | On | On |
| "1" | Alpine White | Alpine White | White | Voodoo Lounge | SAM |
| "2" | Sky Blue | Sky Blue | Light Green | Deep Blue Sea | Party |
| "3" | Cobalt Blue | Cobalt Blue | Green | Afternoon Skies | Romance |
| "4" | Caribbean Blue | Caribbean Blue | Cyan | Emerald | Caribbean |
| "5" | Spring Green | Spring Green | Blue | Sangria | American |
| "6" | Emerald Green | Emerald Green | Lavender | Cloud White | Cal Sunset |
| "7" | Emerald Rose | Emerald Rose | Magenta | Twilight | Royal |
| "8" | Magenta | Magenta | Light Magenta | Tranquility | Blue |
| "9" | Garnet Red | Violet | Color Splash | Gemstone | Green |
| "10" | Violet | Slow Splash | | USA! | Red |
| "11" | Color Splash | Fast Splash | | Mardi Gras | White |
| "12" | | USA!!! | | Cool Caberet | Magenta |
| "13" | | Fat Tuesday | | | Hold |
| "14" | | Disco Tech | | | Recall |
## Sample Items
```
Group Group_AquaLink
String AquaLinkStatus "Status [%s]" (Group_AquaLink) {channel="iaqualink:controller:pool:status"}
Switch AquaLinkBoosterPump "Booster Pump" (Group_AquaLink) {channel="iaqualink:controller:pool:aux_1"}
Switch AquaLinkPoolLight "Pool Light" (Group_AquaLink) {channel="iaqualink:controller:pool:aux_2"}
Switch AquaLinkSpaLight "Spa Light" (Group_AquaLink) {channel="iaqualink:controller:pool:aux_3"}
Switch AquaLinkVanishingEdge "Vanishing Edge" (Group_AquaLink) {channel="iaqualink:controller:pool:aux_4"}
Switch AquaLinkAllOffOneTouch "All Off" (Group_AquaLink) {channel="iaqualink:controller:pool:onetouch_1"}
Switch AquaLinkSpaOneTouch "Spa Mode" (Group_AquaLink) {channel="iaqualink:controller:pool:onetouch_2"}
Switch AquaLinkCleanOneTouch "Clean Mode" (Group_AquaLink) {channel="iaqualink:controller:pool:onetouch_3"}
Switch AquaLinkPoolOneTouch "Pool Mode" (Group_AquaLink) {channel="iaqualink:controller:pool:onetouch_4"}
Number:Temperature AquaLinkSpaTemp "Spa Temperature [%d]" (Group_AquaLink) {channel="iaqualink:controller:pool:spa_temp"}
Number:Temperature AquaLinkPoolTemp "Pool Temperature [%d]" (Group_AquaLink) {channel="iaqualink:controller:pool:pool_temp"}
Number:Temperature AquaLinkAirTemp "Air Temperature [%d]" (Group_AquaLink) {channel="iaqualink:controller:pool:air_temp"}
Number:Temperature AquaLinkSpaSetpoint "Spa Setpoint [%d]" (Group_AquaLink) {channel="iaqualink:controller:pool:spa_set_point"}
Number:Temperature AquaLinkPoolSetpoint "Pool Setpoint [%d]" (Group_AquaLink) {channel="iaqualink:controller:pool:pool_set_point"}
Switch AquaLinkSpaPump "Spa Pump" (Group_AquaLink) {channel="iaqualink:controller:pool:spa_pump"}
Switch AquaLinkPoolPump"Pool Pump" (Group_AquaLink) {channel="iaqualink:controller:pool:pool_pump"}
Number AquaLinkSpaHeaterStatus "Spa Heater [%s]" (Group_AquaLink) {channel="iaqualink:controller:pool:spa_heater_status"}
Number AquaLinkPoolHeaterStatus "Pool Heater [%s]" (Group_AquaLink) {channel="iaqualink:controller:pool:pool_heater_status"}
Switch AquaLinkSpaHeater "Spa Heater" (Group_AquaLink) {channel="iaqualink:controller:pool:spa_heater"}
Switch AquaLinkPoolHeater "Pool Heater" (Group_AquaLink) {channel="iaqualink:controller:pool:pool_heater"}
```

View File

@@ -0,0 +1,17 @@
<?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 http://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>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.iaqualink</artifactId>
<name>openHAB Add-ons :: Bundles :: iAquaLink Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.iaqualink-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-iaqualink" description="iAqualink Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.iaqualink/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.type.ChannelTypeUID;
/**
* The {@link IAqualinkBindingConstants} class defines common constants, which are used
* across the whole binding.
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
public class IAqualinkBindingConstants {
public static final String BINDING_ID = "iaqualink";
public static final String CHANNEL_TYPE_AUX_SWITCH = "aux-switch";
public static final String CHANNEL_TYPE_AUX_DIMMER = "aux-dimmer";
public static final String CHANNEL_TYPE_AUX_JANDYCOLOR = "aux-jandycolor";
public static final String CHANNEL_TYPE_AUX_PENTAIRSAM = "aux-pentairsam";
public static final String CHANNEL_TYPE_AUX_JANDYLED = "aux-jandyled";
public static final String CHANNEL_TYPE_AUX_PENTAIRIB = "aux-pentairib";
public static final String CHANNEL_TYPE_AUX_HAYWARD = "aux-hayward";
public static final String CHANNEL_TYPE_ONETOUCH = "onetouch";
public static final ThingTypeUID IAQUALINK_DEVICE_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "controller");
public static final ChannelTypeUID CHANNEL_TYPE_UID_ONETOUCH = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_ONETOUCH);
public static final ChannelTypeUID CHANNEL_TYPE_UID_AUX_SWITCH = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_AUX_SWITCH);
public static final ChannelTypeUID CHANNEL_TYPE_UID_AUX_DIMMER = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_AUX_DIMMER);
public static final ChannelTypeUID CHANNEL_TYPE_UID_AUX_JANDYCOLOR = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_AUX_JANDYCOLOR);
public static final ChannelTypeUID CHANNEL_TYPE_UID_AUX_PENTAIRSAM = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_AUX_PENTAIRSAM);
public static final ChannelTypeUID CHANNEL_TYPE_UID_AUX_JANDYLED = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_AUX_JANDYLED);
public static final ChannelTypeUID CHANNEL_TYPE_UID_AUX_PENTAIRIB = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_AUX_PENTAIRIB);
public static final ChannelTypeUID CHANNEL_TYPE_UID_AUX_HAYWARD = new ChannelTypeUID(BINDING_ID,
CHANNEL_TYPE_AUX_HAYWARD);
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal;
import static org.openhab.binding.iaqualink.internal.IAqualinkBindingConstants.IAQUALINK_DEVICE_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.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link IAqualinkHandlerFactory} is responsible for creating things and
* thing handlers.
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.iaqualink")
public class IAqualinkHandlerFactory extends BaseThingHandlerFactory {
private final HttpClient httpClient;
@Activate
public IAqualinkHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
this.httpClient = httpClientFactory.getCommonHttpClient();
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return IAQUALINK_DEVICE_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);
}
return null;
}
}

View File

@@ -0,0 +1,474 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.api;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import javax.ws.rs.core.UriBuilder;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.StringContentProvider;
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.model.AccountInfo;
import org.openhab.binding.iaqualink.internal.api.model.Auxiliary;
import org.openhab.binding.iaqualink.internal.api.model.Device;
import org.openhab.binding.iaqualink.internal.api.model.Home;
import org.openhab.binding.iaqualink.internal.api.model.OneTouch;
import org.openhab.binding.iaqualink.internal.api.model.SignIn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
/**
* IAqualink HTTP Client
*
* The {@link IAqualinkClient} provides basic HTTP commands to control and monitor a iAquaLink
* based system.
*
* GSON is used to provide custom deserialization on the JSON results. These results
* unfortunately are not returned as normalized JSON objects and require complex deserialization
* handlers.
*
* @author Dan Cunningham - Initial contribution
*
*/
@NonNullByDefault
public class IAqualinkClient {
private final Logger logger = LoggerFactory.getLogger(IAqualinkClient.class);
private static final String HEADER_AGENT = "iAqualink/98 CFNetwork/978.0.7 Darwin/18.6.0";
private static final String HEADER_ACCEPT = "*/*";
private static final String HEADER_ACCEPT_LANGUAGE = "en-us";
private static final String HEADER_ACCEPT_ENCODING = "br, gzip, deflate";
private static final String SUPPORT_URL = "https://support.iaqualink.com";
private static final String IAQUALINK_BASE_URL = "https://p-api.iaqualink.net/v1/mobile/session.json";
private Gson gson = new GsonBuilder().registerTypeAdapter(Home.class, new HomeDeserializer())
.registerTypeAdapter(OneTouch[].class, new OneTouchDeserializer())
.registerTypeAdapter(Auxiliary[].class, new AuxDeserializer())
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
private Gson gsonInternal = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
private HttpClient httpClient;
@SuppressWarnings("serial")
public class NotAuthorizedException extends Exception {
public NotAuthorizedException(String message) {
super(message);
}
}
/**
*
* @param httpClient
*/
public IAqualinkClient(HttpClient httpClient) {
this.httpClient = httpClient;
}
/**
* Initial login to service
*
* @param username
* @param password
* @param apiKey
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
public AccountInfo login(@Nullable String username, @Nullable String password, @Nullable String apiKey)
throws IOException, NotAuthorizedException {
String signIn = gson.toJson(new SignIn(apiKey, username, password)).toString();
try {
ContentResponse response = httpClient.newRequest(SUPPORT_URL + "/users/sign_in.json")
.method(HttpMethod.POST).content(new StringContentProvider(signIn), "application/json").send();
if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
throw new NotAuthorizedException(response.getReason());
}
if (response.getStatus() != HttpStatus.OK_200) {
throw new IOException(response.getReason());
}
return gson.fromJson(response.getContentAsString(), AccountInfo.class);
} catch (InterruptedException | TimeoutException | ExecutionException e) {
throw new IOException(e);
}
}
/**
* List all devices (pools) registered to an account
*
* @param apiKey
* @param token
* @param id
* @return {@link Device[]}
* @throws IOException
* @throws NotAuthorizedException
*/
public Device[] getDevices(@Nullable String apiKey, @Nullable String token, int id)
throws IOException, NotAuthorizedException {
return getAqualinkObject(UriBuilder.fromUri(SUPPORT_URL + "/devices.json"). //
queryParam("api_key", apiKey). //
queryParam("authentication_token", token). //
queryParam("user_id", id).build(), Device[].class);
}
/**
* Retrieves the HomeScreen
*
* @param serialNumber
* @param sessionId
* @return {@link Home}
* @throws IOException
* @throws NotAuthorizedException
*/
public Home getHome(@Nullable String serialNumber, @Nullable String sessionId)
throws IOException, NotAuthorizedException {
return homeScreenCommand(serialNumber, sessionId, "get_home");
}
/**
* Retrieves {@link OneTouch[]} macros
*
* @param serialNumber
* @param sessionId
* @return {@link OneTouch[]}
* @throws IOException
* @throws NotAuthorizedException
*/
public OneTouch[] getOneTouch(@Nullable String serialNumber, @Nullable String sessionId)
throws IOException, NotAuthorizedException {
return oneTouchCommand(serialNumber, sessionId, "get_onetouch");
}
/**
* Retrieves {@link Auxiliary[]} devices
*
* @param serialNumber
* @param sessionId
* @return {@link Auxiliary[]}
* @throws IOException
* @throws NotAuthorizedException
*/
public Auxiliary[] getAux(@Nullable String serial, @Nullable String sessionID)
throws IOException, NotAuthorizedException {
return auxCommand(serial, sessionID, "get_devices");
}
/**
* Sends a HomeScreen Set command
*
* @param serial
* @param sessionID
* @param homeElementID
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
public Home homeScreenSetCommand(@Nullable String serial, @Nullable String sessionID, String homeElementID)
throws IOException, NotAuthorizedException {
return homeScreenCommand(serial, sessionID, "set_" + homeElementID);
}
/**
* Sends an Auxiliary Set command
*
* @param serial
* @param sessionID
* @param auxID
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
public Auxiliary[] auxSetCommand(@Nullable String serial, @Nullable String sessionID, String auxID)
throws IOException, NotAuthorizedException {
return auxCommand(serial, sessionID, "set_" + auxID);
}
/**
* Sends an Auxiliary light command
*
* @param serialNumber
* @param sessionId
* @param command
* @param lightValue
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
public Auxiliary[] lightCommand(@Nullable String serial, @Nullable String sessionID, String auxID,
String lightValue, String subType) throws IOException, NotAuthorizedException {
return getAqualinkObject(baseURI(). //
queryParam("aux", auxID). //
queryParam("command", "set_light"). //
queryParam("light", lightValue). //
queryParam("serial", serial). //
queryParam("subtype", subType). //
queryParam("sessionID", sessionID).build(), Auxiliary[].class);
}
/**
* Sends a Auxiliary dimmer command
*
* @param serialNumber
* @param sessionId
* @param auxId
* @param lightValue
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
public Auxiliary[] dimmerCommand(@Nullable String serial, @Nullable String sessionID, String auxID, String level)
throws IOException, NotAuthorizedException {
return getAqualinkObject(baseURI().queryParam("aux", auxID). //
queryParam("command", "set_dimmer"). //
queryParam("level", level). //
queryParam("serial", serial). //
queryParam("sessionID", sessionID).build(), Auxiliary[].class);
}
/**
* Sets the Spa Temperature Setpoint
*
* @param serialNumber
* @param sessionId
* @param spaSetpoint
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
public Home setSpaTemp(@Nullable String serial, @Nullable String sessionID, float spaSetpoint)
throws IOException, NotAuthorizedException {
return getAqualinkObject(baseURI(). //
queryParam("command", "set_temps"). //
queryParam("temp1", spaSetpoint). //
queryParam("serial", serial). //
queryParam("sessionID", sessionID).build(), Home.class);
}
/**
* Sets the Pool Temperature Setpoint
*
* @param serialNumber
* @param sessionId
* @param poolSetpoint
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
public Home setPoolTemp(@Nullable String serial, @Nullable String sessionID, float poolSetpoint)
throws IOException, NotAuthorizedException {
return getAqualinkObject(baseURI(). //
queryParam("command", "set_temps"). //
queryParam("temp2", poolSetpoint). //
queryParam("serial", serial). //
queryParam("sessionID", sessionID).build(), Home.class);
}
/**
* Sends a OneTouch set command
*
* @param serial
* @param sessionID
* @param oneTouchID
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
public OneTouch[] oneTouchSetCommand(@Nullable String serial, @Nullable String sessionID, String oneTouchID)
throws IOException, NotAuthorizedException {
return oneTouchCommand(serial, sessionID, "set_" + oneTouchID);
}
private Home homeScreenCommand(@Nullable String serial, @Nullable String sessionID, String command)
throws IOException, NotAuthorizedException {
return getAqualinkObject(baseURI().queryParam("command", command). //
queryParam("serial", serial). //
queryParam("sessionID", sessionID).build(), Home.class);
}
private Auxiliary[] auxCommand(@Nullable String serial, @Nullable String sessionID, String command)
throws IOException, NotAuthorizedException {
return getAqualinkObject(baseURI(). //
queryParam("command", command). //
queryParam("serial", serial). //
queryParam("sessionID", sessionID).build(), Auxiliary[].class);
}
private OneTouch[] oneTouchCommand(@Nullable String serial, @Nullable String sessionID, String command)
throws IOException, NotAuthorizedException {
return getAqualinkObject(baseURI().queryParam("command", command). //
queryParam("serial", serial). //
queryParam("sessionID", sessionID).build(), OneTouch[].class);
}
private UriBuilder baseURI() {
return UriBuilder.fromUri(IAQUALINK_BASE_URL).queryParam("actionID", "command");
}
/**
*
* @param <T>
* @param url
* @param typeOfT
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
private <T> T getAqualinkObject(URI uri, Type typeOfT) throws IOException, NotAuthorizedException {
return gson.fromJson(getRequest(uri), typeOfT);
}
/**
*
* @param url
* @return
* @throws IOException
* @throws NotAuthorizedException
*/
private String getRequest(URI uri) throws IOException, NotAuthorizedException {
try {
logger.trace("Trying {}", uri);
ContentResponse response = httpClient.newRequest(uri).method(HttpMethod.GET) //
.agent(HEADER_AGENT) //
.header(HttpHeader.ACCEPT_LANGUAGE, HEADER_ACCEPT_LANGUAGE) //
.header(HttpHeader.ACCEPT_ENCODING, HEADER_ACCEPT_ENCODING) //
.header(HttpHeader.ACCEPT, HEADER_ACCEPT) //
.send();
logger.trace("Response {}", response);
if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
throw new NotAuthorizedException(response.getReason());
}
if (response.getStatus() != HttpStatus.OK_200) {
throw new IOException(response.getReason());
}
return response.getContentAsString();
} catch (InterruptedException | TimeoutException | ExecutionException | JsonParseException e) {
throw new IOException(e);
}
}
/////////////// .........Here be dragons...../////////////////////////
class HomeDeserializer implements JsonDeserializer<Home> {
@Override
public Home deserialize(@Nullable JsonElement json, @Nullable Type typeOfT,
@Nullable JsonDeserializationContext context) throws JsonParseException {
if (json == null) {
throw new JsonParseException("No JSON");
}
JsonObject jsonObject = json.getAsJsonObject();
JsonArray homeScreen = jsonObject.getAsJsonArray("home_screen");
JsonObject home = new JsonObject();
JsonObject serializedMap = new JsonObject();
if (homeScreen != null) {
homeScreen.forEach(element -> {
element.getAsJsonObject().entrySet().forEach(entry -> {
home.add(entry.getKey(), entry.getValue());
serializedMap.add(entry.getKey(), entry.getValue());
});
});
home.add("serialized_map", serializedMap);
return gsonInternal.fromJson(home, Home.class);
}
throw new JsonParseException("Invalid structure for Home class");
}
}
class OneTouchDeserializer implements JsonDeserializer<OneTouch[]> {
@Override
public OneTouch[] deserialize(@Nullable JsonElement json, @Nullable Type typeOfT,
@Nullable JsonDeserializationContext context) throws JsonParseException {
if (json == null) {
throw new JsonParseException("No JSON");
}
JsonObject jsonObject = json.getAsJsonObject();
JsonArray oneTouchScreen = jsonObject.getAsJsonArray("onetouch_screen");
List<OneTouch> list = new ArrayList<>();
if (oneTouchScreen != null) {
oneTouchScreen.forEach(oneTouchScreenElement -> {
oneTouchScreenElement.getAsJsonObject().entrySet().forEach(oneTouchScreenEntry -> {
if (oneTouchScreenEntry.getKey().startsWith("onetouch_")) {
JsonArray oneTouchArray = oneTouchScreenEntry.getValue().getAsJsonArray();
if (oneTouchArray != null) {
JsonObject oneTouchJson = new JsonObject();
oneTouchJson.add("name", new JsonPrimitive(oneTouchScreenEntry.getKey()));
oneTouchArray.forEach(arrayElement -> {
arrayElement.getAsJsonObject().entrySet().forEach(oneTouchEntry -> {
oneTouchJson.add(oneTouchEntry.getKey(), oneTouchEntry.getValue());
});
});
list.add(gsonInternal.fromJson(oneTouchJson, OneTouch.class));
}
}
});
});
}
return list.toArray(new OneTouch[list.size()]);
}
}
class AuxDeserializer implements JsonDeserializer<Auxiliary[]> {
@Override
public Auxiliary[] deserialize(@Nullable JsonElement json, @Nullable Type typeOfT,
@Nullable JsonDeserializationContext context) throws JsonParseException {
if (json == null) {
throw new JsonParseException("No JSON");
}
JsonObject jsonObject = json.getAsJsonObject();
JsonArray auxScreen = jsonObject.getAsJsonArray("devices_screen");
List<Auxiliary> list = new ArrayList<>();
if (auxScreen != null) {
auxScreen.forEach(auxElement -> {
auxElement.getAsJsonObject().entrySet().forEach(auxScreenEntry -> {
if (auxScreenEntry.getKey().startsWith("aux_")) {
JsonArray auxArray = auxScreenEntry.getValue().getAsJsonArray();
if (auxArray != null) {
JsonObject auxJson = new JsonObject();
auxJson.add("name", new JsonPrimitive(auxScreenEntry.getKey()));
auxArray.forEach(arrayElement -> {
arrayElement.getAsJsonObject().entrySet().forEach(auxEntry -> {
auxJson.add(auxEntry.getKey(), auxEntry.getValue());
});
});
list.add(gsonInternal.fromJson(auxJson, Auxiliary.class));
}
}
});
});
}
return list.toArray(new Auxiliary[list.size()]);
}
}
}

View File

@@ -0,0 +1,212 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.api.model;
/**
* Account Info Object
*
* @author Dan Cunningham - Initial contribution
*
*/
public class AccountInfo {
private Integer id;
private String email;
private String createdAt;
private String updatedAt;
private Object timeZone;
private String firstName;
private String lastName;
private String address1;
private String address2;
private String city;
private String state;
private String postalCode;
private String country;
private String phone;
private Boolean optIn1;
private Boolean optIn2;
private String authenticationToken;
private String role;
private String sessionId;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
public String getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(String updatedAt) {
this.updatedAt = updatedAt;
}
public Object getTimeZone() {
return timeZone;
}
public void setTimeZone(Object timeZone) {
this.timeZone = timeZone;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAddress1() {
return address1;
}
public void setAddress1(String address1) {
this.address1 = address1;
}
public String getAddress2() {
return address2;
}
public void setAddress2(String address2) {
this.address2 = address2;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Boolean getOptIn1() {
return optIn1;
}
public void setOptIn1(Boolean optIn1) {
this.optIn1 = optIn1;
}
public Boolean getOptIn2() {
return optIn2;
}
public void setOptIn2(Boolean optIn2) {
this.optIn2 = optIn2;
}
public String getAuthenticationToken() {
return authenticationToken;
}
public void setAuthenticationToken(String authenticationToken) {
this.authenticationToken = authenticationToken;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.api.model;
/**
* Auxiliary devices.
*
* @author Dan Cunningham - Initial contribution
*
*/
public class Auxiliary {
private String state;
private String label;
private String icon;
private String type;
private String subtype;
private String name;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSubtype() {
return subtype;
}
public void setSubtype(String subtype) {
this.subtype = subtype;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,142 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.api.model;
/**
* Device refers to a iAqualink Pool Controller.
*
* @author Dan Cunningham - Initial contribution
*
*/
public class Device {
private Integer id;
private String serialNumber;
private String createdAt;
private String updatedAt;
private String name;
private String deviceType;
private Object ownerId;
private Boolean updating;
private Object firmwareVersion;
private Object targetFirmwareVersion;
private Object updateFirmwareStartAt;
private Object lastActivityAt;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
public String getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(String updatedAt) {
this.updatedAt = updatedAt;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDeviceType() {
return deviceType;
}
public void setDeviceType(String deviceType) {
this.deviceType = deviceType;
}
public Object getOwnerId() {
return ownerId;
}
public void setOwnerId(Object ownerId) {
this.ownerId = ownerId;
}
public Boolean getUpdating() {
return updating;
}
public void setUpdating(Boolean updating) {
this.updating = updating;
}
public Object getFirmwareVersion() {
return firmwareVersion;
}
public void setFirmwareVersion(Object firmwareVersion) {
this.firmwareVersion = firmwareVersion;
}
public Object getTargetFirmwareVersion() {
return targetFirmwareVersion;
}
public void setTargetFirmwareVersion(Object targetFirmwareVersion) {
this.targetFirmwareVersion = targetFirmwareVersion;
}
public Object getUpdateFirmwareStartAt() {
return updateFirmwareStartAt;
}
public void setUpdateFirmwareStartAt(Object updateFirmwareStartAt) {
this.updateFirmwareStartAt = updateFirmwareStartAt;
}
public Object getLastActivityAt() {
return lastActivityAt;
}
public void setLastActivityAt(Object lastActivityAt) {
this.lastActivityAt = lastActivityAt;
}
}

View File

@@ -0,0 +1,230 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.api.model;
import java.util.Map;
/**
* {@link Home} refers to the "Home" screen of a pool controller.
*
* @author Dan Cunningham - Initial contribution
*
*/
public class Home {
private String status;
private String response;
private String systemType;
private String tempScale;
private String spaTemp;
private String poolTemp;
private String airTemp;
private String spaSetPoint;
private String poolSetPoint;
private String coverPool;
private String freezeProtection;
private String spaPump;
private String poolPump;
private String spaHeater;
private String poolHeater;
private String solarHeater;
private String spaSalinity;
private String poolSalinity;
private String orp;
private String ph;
private Map<String, String> serializedMap;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
public String getSystemType() {
return systemType;
}
public void setSystemType(String systemType) {
this.systemType = systemType;
}
public String getTempScale() {
return tempScale;
}
public void setTempScale(String tempScale) {
this.tempScale = tempScale;
}
public String getSpaTemp() {
return spaTemp;
}
public void setSpaTemp(String spaTemp) {
this.spaTemp = spaTemp;
}
public String getPoolTemp() {
return poolTemp;
}
public void setPoolTemp(String poolTemp) {
this.poolTemp = poolTemp;
}
public String getAirTemp() {
return airTemp;
}
public void setAirTemp(String airTemp) {
this.airTemp = airTemp;
}
public String getSpaSetPoint() {
return spaSetPoint;
}
public void setSpaSetPoint(String spaSetPoint) {
this.spaSetPoint = spaSetPoint;
}
public String getPoolSetPoint() {
return poolSetPoint;
}
public void setPoolSetPoint(String poolSetPoint) {
this.poolSetPoint = poolSetPoint;
}
public String getCoverPool() {
return coverPool;
}
public void setCoverPool(String coverPool) {
this.coverPool = coverPool;
}
public String getFreezeProtection() {
return freezeProtection;
}
public void setFreezeProtection(String freezeProtection) {
this.freezeProtection = freezeProtection;
}
public String getSpaPump() {
return spaPump;
}
public void setSpaPump(String spaPump) {
this.spaPump = spaPump;
}
public String getPoolPump() {
return poolPump;
}
public void setPoolPump(String poolPump) {
this.poolPump = poolPump;
}
public String getSpaHeater() {
return spaHeater;
}
public void setSpaHeater(String spaHeater) {
this.spaHeater = spaHeater;
}
public String getPoolHeater() {
return poolHeater;
}
public void setPoolHeater(String poolHeater) {
this.poolHeater = poolHeater;
}
public String getSolarHeater() {
return solarHeater;
}
public void setSolarHeater(String solarHeater) {
this.solarHeater = solarHeater;
}
public String getSpaSalinity() {
return spaSalinity;
}
public void setSpaSalinity(String spaSalinity) {
this.spaSalinity = spaSalinity;
}
public String getPoolSalinity() {
return poolSalinity;
}
public void setPoolSalinity(String poolSalinity) {
this.poolSalinity = poolSalinity;
}
public String getOrp() {
return orp;
}
public void setOrp(String orp) {
this.orp = orp;
}
public String getPh() {
return ph;
}
public void setPh(String ph) {
this.ph = ph;
}
public Map<String, String> getSerializedMap() {
return serializedMap;
}
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.api.model;
/**
* OneTouch Macros.
*
* @author Dan Cunningham - Initial contribution
*
*/
public class OneTouch {
private String status;
private String state;
private String label;
private String name;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.api.model;
/**
* Object used to login to service.
*
* @author Dan Cunningham - Initial contribution
*
*/
public class SignIn {
private String apiKey;
private String email;
private String password;
public SignIn(String apiKey, String email, String password) {
super();
this.apiKey = apiKey;
this.email = email;
this.password = password;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.config;
/**
* Configuration properties for connecting to a iAqualink Account
*
* @author Dan Cunningham - Initial contribution
*
*/
public class IAqualinkConfiguration {
/**
* user to us when connecting to the account
*/
public String userName;
/**
* password to us when connecting to the account
*/
public String password;
/**
* Option serialId of the pool controller to connect to, only useful if you have more then one controller
*/
public String serialId;
/**
* fixed API key provided by iAqualink clients (Android, IOS) , unknown if this will change in the future.
*/
public String apiKey;
/**
* Rate we poll for new data
*/
public int refresh;
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.iaqualink.internal.IAqualinkBindingConstants;
import org.openhab.core.thing.type.ChannelTypeUID;
/**
* AuxiliaryType maps iAquaLink Auxiliary Devices to binding channel types
*
* @author Dan Cunningham - Initial contribution
*
*/
@NonNullByDefault
public enum AuxiliaryType {
SWITCH("0", "0", IAqualinkBindingConstants.CHANNEL_TYPE_UID_AUX_SWITCH),
DIMMER("1", "NA", IAqualinkBindingConstants.CHANNEL_TYPE_UID_AUX_DIMMER),
JANDYCOLOR("2", "1", IAqualinkBindingConstants.CHANNEL_TYPE_UID_AUX_JANDYCOLOR),
PENTAIRSAM("2", "2", IAqualinkBindingConstants.CHANNEL_TYPE_UID_AUX_PENTAIRSAM),
JANDYLED("2", "4", IAqualinkBindingConstants.CHANNEL_TYPE_UID_AUX_JANDYLED),
PENTAIRIB("2", "5", IAqualinkBindingConstants.CHANNEL_TYPE_UID_AUX_PENTAIRIB),
HAYWARD("2", "6", IAqualinkBindingConstants.CHANNEL_TYPE_UID_AUX_HAYWARD);
private String type;
private String subType;
private ChannelTypeUID channelTypeUID;
AuxiliaryType(String type, String subType, ChannelTypeUID channelTypeUID) {
this.type = type;
this.subType = subType;
this.channelTypeUID = channelTypeUID;
}
public String getSubType() {
return subType;
}
public String getType() {
return type;
}
public ChannelTypeUID getChannelTypeUID() {
return channelTypeUID;
}
public static AuxiliaryType fromSubType(String subType) {
for (AuxiliaryType at : AuxiliaryType.values()) {
if (at.subType.equals(subType)) {
return at;
}
}
return AuxiliaryType.SWITCH;
}
public static AuxiliaryType fromChannelTypeUID(@Nullable ChannelTypeUID channelTypeUID) {
for (AuxiliaryType at : AuxiliaryType.values()) {
if (at.channelTypeUID.equals(channelTypeUID)) {
return at;
}
}
return AuxiliaryType.SWITCH;
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Heater States returned by iAquaLink API
*
* @author Dan Cunningham - Initial contribution
*
*/
@NonNullByDefault
public enum HeaterState {
OFF("0", "off"),
HEATING("1", "on"),
ENABLED("3", "enabled");
String value;
String label;
HeaterState(String value, String label) {
this.value = value;
this.label = label;
}
public String getLabel() {
return label;
}
public static @Nullable HeaterState fromValue(String value) {
for (HeaterState state : HeaterState.values()) {
if (state.value.equals(value)) {
return state;
}
}
return null;
}
}

View File

@@ -0,0 +1,554 @@
/**
* Copyright (c) 2010-2020 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.iaqualink.internal.handler;
import static org.openhab.core.library.unit.ImperialUnits.FAHRENHEIT;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.Unit;
import javax.measure.quantity.Temperature;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
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.model.AccountInfo;
import org.openhab.binding.iaqualink.internal.api.model.Auxiliary;
import org.openhab.binding.iaqualink.internal.api.model.Device;
import org.openhab.binding.iaqualink.internal.api.model.Home;
import org.openhab.binding.iaqualink.internal.api.model.OneTouch;
import org.openhab.binding.iaqualink.internal.config.IAqualinkConfiguration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* iAquaLink Control Binding
*
* iAquaLink controllers allow remote access to Jandy/Zodiac pool systems. This
* binding allows openHAB to both monitor and control a pool system through
* these controllers.
*
* The {@link IAqualinkHandler} is responsible for handling commands, which
* are sent to one of the channels.
*
* @author Dan Cunningham - Initial contribution
*/
@NonNullByDefault
public class IAqualinkHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(IAqualinkHandler.class);
/**
* Minimum amount of time we can poll for updates
*/
private static final int MIN_REFRESH_SECONDS = 5;
/**
* Minimum amount of time we can poll after a command
*/
private static final int COMMAND_REFRESH_SECONDS = 5;
/**
* Default iAqulink key used by existing clients in the marketplace
*/
private static final String DEFAULT_API_KEY = "EOOEMOW4YR6QNB07";
/**
* Local cache of iAqualink states
*/
private Map<String, State> stateMap = Collections.synchronizedMap(new HashMap<>());
/**
* Our poll rate
*/
private int refresh;
/**
* fixed API key provided by iAqualink clients (Android, IOS), unknown if this will change in the future.
*/
private @Nullable String apiKey;
/**
* Optional serial number of the pool controller to connect to, only useful if you have more then one controller
*/
private @Nullable String serialNumber;
/**
* Server issued sessionId
*/
private @Nullable String sessionId;
/**
* When we first connect we will dynamically create channels based on what the controller is configured for
*/
private boolean firstRun;
/**
* Future to poll for updated
*/
private @Nullable ScheduledFuture<?> pollFuture;
/**
* The client interface to the iAqualink Service
*/
private IAqualinkClient client;
/**
* Temperature unit, will be set based on user setting
*/
private Unit<Temperature> temperatureUnit = CELSIUS;
/**
* Constructs a new {@link IAqualinkHandler}
*
* @param thing
* @param httpClient
*/
public IAqualinkHandler(Thing thing, HttpClient httpClient) {
super(thing);
client = new IAqualinkClient(httpClient);
}
@Override
public void initialize() {
// don't hold up initialize
scheduler.schedule(this::configure, 0, TimeUnit.SECONDS);
}
@Override
public void dispose() {
logger.debug("Handler disposed.");
clearPolling();
}
@Override
public void channelLinked(ChannelUID channelUID) {
// clear our cached value so the new channel gets updated on the next poll
stateMap.remove(channelUID.getAsString());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("handleCommand channel: {} command: {}", channelUID, command);
if (getThing().getStatus() != ThingStatus.ONLINE) {
logger.warn("Controller is not ONLINE and is not responding to commands");
return;
}
clearPolling();
String channelName = channelUID.getIdWithoutGroup();
// remove the current state to ensure we send an update
stateMap.remove(channelUID.getAsString());
try {
if (command instanceof RefreshType) {
logger.debug("Channel {} state has been cleared", channelName);
} else if (channelName.startsWith("aux_")) {
// Auxiliary Commands
String auxId = channelName.replaceFirst("aux_", "");
if (command instanceof PercentType) {
client.dimmerCommand(serialNumber, sessionId, auxId, command.toString());
} else if (command instanceof StringType) {
String cmd = "off".equals(command.toString()) ? "0"
: "on".equals(command.toString()) ? "1" : command.toString();
client.lightCommand(serialNumber, sessionId, auxId, cmd,
AuxiliaryType.fromChannelTypeUID(getChannelTypeUID(channelUID)).getSubType());
} else if (command instanceof OnOffType) {
// these are toggle commands and require we have the current state to turn on/off
Auxiliary[] auxs = client.getAux(serialNumber, sessionId);
Optional<Auxiliary> optional = Arrays.stream(auxs).filter(o -> o.getName().equals(channelName))
.findFirst();
if (optional.isPresent()) {
if (toState(channelName, "Switch", optional.get().getState()) != command) {
client.auxSetCommand(serialNumber, sessionId, channelName);
}
}
}
} else if (channelName.endsWith("_set_point")) {
// Set Point Commands
if ("spa_set_point".equals(channelName)) {
BigDecimal value = commandToRoundedTemperature(command, temperatureUnit);
if (value != null) {
client.setSpaTemp(serialNumber, sessionId, value.floatValue());
}
} else if ("pool_set_point".equals(channelName)) {
BigDecimal value = commandToRoundedTemperature(command, temperatureUnit);
if (value != null) {
client.setPoolTemp(serialNumber, sessionId, value.floatValue());
}
}
} else if (command instanceof OnOffType) {
// these are toggle commands and require we have the current state to turn on/off
if (channelName.startsWith("onetouch_")) {
OneTouch[] ota = client.getOneTouch(serialNumber, sessionId);
Optional<OneTouch> optional = Arrays.stream(ota).filter(o -> o.getName().equals(channelName))
.findFirst();
if (optional.isPresent()) {
if (toState(channelName, "Switch", optional.get().getState()) != command) {
logger.debug("Sending command {} to {}", command, channelName);
client.oneTouchSetCommand(serialNumber, sessionId, channelName);
}
}
} else if (channelName.endsWith("heater") || channelName.endsWith("pump")) {
String value = client.getHome(serialNumber, sessionId).getSerializedMap().get(channelName);
if (toState(channelName, "Switch", value) != command) {
logger.debug("Sending command {} to {}", command, channelName);
client.homeScreenSetCommand(serialNumber, sessionId, channelName);
}
}
}
initPolling(COMMAND_REFRESH_SECONDS);
} catch (IOException e) {
logger.debug("Exception executing command", e);
initPolling(COMMAND_REFRESH_SECONDS);
} catch (NotAuthorizedException e) {
logger.debug("Authorization Exception sending command", e);
configure();
}
}
/**
* Configures this thing
*/
private void configure() {
clearPolling();
firstRun = true;
IAqualinkConfiguration configuration = getConfig().as(IAqualinkConfiguration.class);
String username = configuration.userName;
String password = configuration.password;
String confSerialId = configuration.serialId;
String confApiKey = configuration.apiKey;
if (StringUtils.isNotBlank(confApiKey)) {
this.apiKey = confApiKey;
} else {
this.apiKey = DEFAULT_API_KEY;
}
this.refresh = Math.max(configuration.refresh, MIN_REFRESH_SECONDS);
try {
AccountInfo accountInfo = client.login(username, password, apiKey);
sessionId = accountInfo.getSessionId();
if (sessionId == null) {
throw new IOException("Response from controller not valid");
}
logger.debug("SessionID {}", sessionId);
Device[] devices = client.getDevices(apiKey, accountInfo.getAuthenticationToken(), accountInfo.getId());
if (devices.length == 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No registered devices found");
return;
}
if (StringUtils.isNotBlank(confSerialId)) {
serialNumber = confSerialId.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();
if (!Arrays.stream(devices).anyMatch(device -> device.getSerialNumber().equals(serialNumber))) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"No Device for given serialId found");
return;
}
} else {
serialNumber = devices[0].getSerialNumber();
}
initPolling(COMMAND_REFRESH_SECONDS);
} catch (IOException e) {
logger.debug("Could not connect to service {}", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (NotAuthorizedException e) {
logger.debug("Credentials not valid");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Credentials not valid");
}
}
/**
* Starts/Restarts polling with an initial delay. This allows changes in the poll cycle for when commands are sent
* and we need to poll sooner then the next refresh cycle.
*/
private synchronized void initPolling(int initialDelay) {
clearPolling();
pollFuture = scheduler.scheduleWithFixedDelay(this::pollController, initialDelay, refresh, TimeUnit.SECONDS);
}
/**
* Stops/clears this thing's polling future
*/
private void clearPolling() {
ScheduledFuture<?> localFuture = pollFuture;
if (isFutureValid(localFuture)) {
if (localFuture != null) {
localFuture.cancel(true);
}
}
}
private boolean isFutureValid(@Nullable ScheduledFuture<?> future) {
return future != null && !future.isCancelled();
}
/**
* Poll the controller for updates.
*/
private void pollController() {
ScheduledFuture<?> localFuture = pollFuture;
try {
Home home = client.getHome(serialNumber, sessionId);
if ("Error".equals(home.getResponse())) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Service reports controller status as: " + home.getStatus());
return;
}
Map<String, String> map = home.getSerializedMap();
if (map != null) {
temperatureUnit = "F".equalsIgnoreCase(map.get("temp_scale")) ? FAHRENHEIT : CELSIUS;
map.forEach((k, v) -> {
updatedState(k, v);
if (k.endsWith("_heater")) {
HeaterState hs = HeaterState.fromValue(v);
updatedState(k + "_status", hs == null ? null : hs.getLabel());
}
});
}
OneTouch[] oneTouches = client.getOneTouch(serialNumber, sessionId);
Auxiliary[] auxes = client.getAux(serialNumber, sessionId);
if (firstRun) {
firstRun = false;
updateChannels(auxes, oneTouches);
}
for (OneTouch ot : oneTouches) {
updatedState(ot.getName(), ot.getState());
}
for (Auxiliary aux : auxes) {
switch (aux.getType()) {
// dimmer uses subType for value
case "1":
updatedState(aux.getName(), aux.getSubtype());
break;
// Color lights do not report the color value, only on/off
case "2":
updatedState(aux.getName(), "0".equals(aux.getState()) ? "off" : "on");
break;
// all else are switches
default:
updatedState(aux.getName(), aux.getState());
}
}
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
} catch (IOException e) {
// poller will continue to run, set offline until next run
logger.debug("Exception polling", e);
if (isFutureValid(localFuture)) {
// only valid futures should set state, otherwise this exception was do to being canceled.
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
} catch (NotAuthorizedException e) {
// if are creds are not valid, we need to try re authorizing again
logger.debug("Authorization Exception during polling", e);
clearPolling();
configure();
}
}
/**
* Update an channels state only if the value of the channel has changed since our last poll.
*
* @param name
* @param value
*/
private void updatedState(String name, @Nullable String value) {
logger.trace("updatedState {} : {}", name, value);
Channel channel = getThing().getChannel(name);
if (channel != null) {
State state = toState(name, channel.getAcceptedItemType(), value);
State oldState = stateMap.put(channel.getUID().getAsString(), state);
if (!state.equals(oldState)) {
logger.trace("updating channel {} with state {} (old state {})", channel.getUID(), state, oldState);
updateState(channel.getUID(), state);
}
}
}
/**
* Converts a {@link String} value to a {@link State} for a given
* {@link String} accepted type
*
* @param itemType
* @param value
* @return {@link State}
*/
private State toState(String name, @Nullable String type, @Nullable String value) {
try {
// @nullable checker does not recognize isBlank as checking null here, so must use == null to make happy
if (value == null || StringUtils.isBlank(value)) {
return UnDefType.UNDEF;
}
if (type == null) {
return StringType.valueOf(value);
}
switch (type) {
case "Number:Temperature":
return new QuantityType<>(Float.parseFloat(value), temperatureUnit);
case "Number":
return new DecimalType(value);
case "Dimmer":
return new PercentType(value);
case "Switch":
return Integer.parseInt(value) > 0 ? OnOffType.ON : OnOffType.OFF;
default:
return StringType.valueOf(value);
}
} catch (NumberFormatException e) {
return UnDefType.UNDEF;
}
}
/**
* Creates channels based on what is supported by the controller.
*/
private void updateChannels(Auxiliary[] auxes, OneTouch[] oneTouches) {
List<Channel> channels = new ArrayList<>(getThing().getChannels());
for (Auxiliary aux : auxes) {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), aux.getName());
logger.debug("Add channel Aux Name: {} Label: {} Type: {} Subtype: {}", aux.getName(), aux.getLabel(),
aux.getType(), aux.getSubtype());
switch (aux.getType()) {
case "1":
addNewChannelToList(channels, channelUID, "Dimmer",
IAqualinkBindingConstants.CHANNEL_TYPE_UID_AUX_DIMMER, aux.getLabel());
break;
case "2": {
addNewChannelToList(channels, channelUID, "String",
AuxiliaryType.fromSubType(aux.getSubtype()).getChannelTypeUID(), aux.getLabel());
}
break;
default:
addNewChannelToList(channels, channelUID, "Switch",
IAqualinkBindingConstants.CHANNEL_TYPE_UID_AUX_SWITCH, aux.getLabel());
}
}
for (OneTouch oneTouch : oneTouches) {
if ("0".equals(oneTouch.getStatus())) {
// OneTouch is not enabled
continue;
}
ChannelUID channelUID = new ChannelUID(getThing().getUID(), oneTouch.getName());
addNewChannelToList(channels, channelUID, "Switch", IAqualinkBindingConstants.CHANNEL_TYPE_UID_ONETOUCH,
oneTouch.getLabel());
}
ThingBuilder thingBuilder = editThing();
thingBuilder.withChannels(channels);
updateThing(thingBuilder.build());
}
/**
* Adds a channel to the list of channels if the channel does not exist or is of a different type
*
*/
private void addNewChannelToList(List<Channel> list, ChannelUID channelUID, String itemType,
ChannelTypeUID channelType, String label) {
// if there is no entry, add it
if (!list.stream().anyMatch(c -> c.getUID().equals(channelUID))) {
list.add(ChannelBuilder.create(channelUID, itemType).withType(channelType).withLabel(label).build());
} else if (list.removeIf(c -> c.getUID().equals(channelUID) && !channelType.equals(c.getChannelTypeUID()))) {
// this channel uid exists, but has a different type so remove and add our new one
list.add(ChannelBuilder.create(channelUID, itemType).withType(channelType).withLabel(label).build());
}
}
/**
* inspired by the openHAB Nest thermostat binding
*/
@SuppressWarnings("unchecked")
private @Nullable BigDecimal commandToRoundedTemperature(Command command, Unit<Temperature> unit)
throws IllegalArgumentException {
QuantityType<Temperature> quantity;
if (command instanceof QuantityType) {
quantity = (QuantityType<Temperature>) command;
} else {
quantity = new QuantityType<>(new BigDecimal(command.toString()), unit);
}
QuantityType<Temperature> temparatureQuantity = quantity.toUnit(unit);
if (temparatureQuantity == null) {
return null;
}
BigDecimal value = temparatureQuantity.toBigDecimal();
BigDecimal increment = CELSIUS == unit ? new BigDecimal("0.5") : new BigDecimal("1");
BigDecimal divisor = value.divide(increment, 0, RoundingMode.HALF_UP);
return divisor.multiply(increment);
}
private ChannelTypeUID getChannelTypeUID(ChannelUID channelUID) {
Channel channel = getThing().getChannel(channelUID.getId());
Objects.requireNonNull(channel);
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
Objects.requireNonNull(channelTypeUID);
return channelTypeUID;
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="iaqualink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>iAquaLink Binding</name>
<description>This is the binding for a iAquaLink pool controller.</description>
<author>Dan Cunningham</author>
</binding:binding>

View File

@@ -0,0 +1,331 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="iaqualink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="controller">
<label>iAquaLink Pool Controller</label>
<description>A iAquaLink pool control thing represents a iAquaLink pool controller for Jandy/Zodiac systems
</description>
<channels>
<channel id="status" typeId="status"/>
<channel id="system_type" typeId="system_type"/>
<channel id="temp_scale" typeId="temperature_scale"/>
<channel id="spa_temp" typeId="temperature">
<label>Spa Temperature</label>
<description>The current temperature of the spa</description>
</channel>
<channel id="pool_temp" typeId="temperature">
<label>Pool Temperature</label>
<description>The current temperature of the pool</description>
</channel>
<channel id="air_temp" typeId="temperature">
<label>Air Temperature</label>
<description>The current outside temperature</description>
</channel>
<channel id="spa_set_point" typeId="setpoint">
<label>Spa Setpoint</label>
<description>The spa setpoint</description>
</channel>
<channel id="pool_set_point" typeId="setpoint">
<label>Pool Setpoint</label>
<description>The pool setpoint</description>
</channel>
<channel id="cover_pool" typeId="equipment-switch">
<label>Cover Pool</label>
<description>Pool covering</description>
</channel>
<channel id="freeze_protection" typeId="equipment-switch">
<label>Freeze Protection</label>
<description>Freeze protection</description>
</channel>
<channel id="spa_pump" typeId="equipment-switch">
<label>Spa Pump</label>
<description>Spa pump</description>
</channel>
<channel id="pool_pump" typeId="equipment-switch">
<label>Pool Pump</label>
<description>Pool pump</description>
</channel>
<channel id="spa_heater_status" typeId="equipment-heater">
<label>Spa Heater Status</label>
<description>Spa heater status</description>
</channel>
<channel id="spa_heater" typeId="equipment-switch">
<label>Spa Heater Switch</label>
<description>Spa heater switch</description>
</channel>
<channel id="pool_heater_status" typeId="equipment-heater">
<label>Pool Heater Status</label>
<description>Pool heater status</description>
</channel>
<channel id="pool_heater" typeId="equipment-switch">
<label>Pool Heater Switch</label>
<description>Pool heater Switch</description>
</channel>
<channel id="solar_heater_status" typeId="equipment-heater">
<label>Solar Heater Status</label>
<description>Solar heater status</description>
</channel>
<channel id="solar_heater" typeId="equipment-switch">
<label>Solar Heater Switch</label>
<description>Solar heater switch</description>
</channel>
<channel id="spa_salinity" typeId="chemical">
<label>Spa Salinity</label>
<description>Spa Salinity</description>
</channel>
<channel id="pool_salinity" typeId="chemical">
<label>Pool Salinity</label>
<description>Pool Salinity</description>
</channel>
<channel id="orp" typeId="chemical">
<label>Orp</label>
<description>Orp</description>
</channel>
<channel id="ph" typeId="chemical">
<label>PH</label>
<description>PH</description>
</channel>
</channels>
<config-description>
<parameter name="userName" type="text" required="true">
<label>User Name</label>
<description>The user name to use when connecting to a iAqualink Account</description>
</parameter>
<parameter name="password" type="text" required="true">
<context>password</context>
<label>Password</label>
<description>The password to use when connecting to a iAqualink Account</description>
</parameter>
<parameter name="refresh" type="integer" required="true">
<label>Refresh Interval</label>
<description>Specifies the refresh interval in seconds</description>
<default>30</default>
</parameter>
<parameter name="serialId" type="text" required="false">
<label>Serial Number</label>
<description>Optionally specify the serial number of the controller which can be found on the iAquaLink Owner's
Center. This is only useful if you have more then one controller (pool) associated with your account. Leave blank
to have the first controller used.
</description>
</parameter>
<parameter name="apiKey" type="text" required="false">
<label>API Key</label>
<description>Optionally specify the API key used for access. This is only useful for debugging or if the API key is
changed by the vendor
</description>
<default>EOOEMOW4YR6QNB07</default>
</parameter>
</config-description>
</thing-type>
<!-- System Channels -->
<channel-type id="status">
<item-type>String</item-type>
<label>Status</label>
<description>The status of the iAqualink connection</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="system_type" advanced="true">
<item-type>String</item-type>
<label>System Type</label>
<description> System Type</description>
<state readOnly="true"/>
</channel-type>
<!-- Equipment and Aux Channels -->
<channel-type id="aux-switch">
<item-type>Switch</item-type>
<label>Auxiliary Switch</label>
<description>The current state of auxiliary channel</description>
</channel-type>
<channel-type id="aux-dimmer">
<item-type>Dimmer</item-type>
<label>Auxiliary Dimmer</label>
<description>The current state of auxiliary channel</description>
<state step="25"></state>
</channel-type>
<channel-type id="aux-jandycolor">
<item-type>String</item-type>
<label>Jandy Color Lighting</label>
<description>Jandy Color Lighting Channel</description>
<state>
<options>
<option value="off">Off</option>
<option value="on">On</option>
<option value="1">Alpine White</option>
<option value="2">Sky Blue</option>
<option value="3">Cobalt Blue</option>
<option value="4">Caribbean Blue</option>
<option value="5">Spring Green</option>
<option value="6">Emerald Green</option>
<option value="7">Emerald Rose</option>
<option value="8">Magenta</option>
<option value="9">Garnet Red</option>
<option value="10">Violet</option>
<option value="11">Color Splash</option>
</options>
</state>
</channel-type>
<channel-type id="aux-pentairsam">
<item-type>String</item-type>
<label>Pentair SAm/SAL Lighting</label>
<description>Pentair SAm/SAL Lighting Channel</description>
<state>
<options>
<option value="off">Off</option>
<option value="on">On</option>
<option value="1">White</option>
<option value="2">Light Green</option>
<option value="3">Green</option>
<option value="4">Cyan</option>
<option value="5">Blue</option>
<option value="6">Lavender</option>
<option value="7">Magenta</option>
<option value="8">Light Magenta</option>
<option value="9">Color Splash</option>
</options>
</state>
</channel-type>
<channel-type id="aux-jandyled">
<item-type>String</item-type>
<label>Jandy Led Water Colors Lighting</label>
<description>Jandy Led Water Colors Lighting Channel</description>
<state>
<options>
<option value="off">Off</option>
<option value="on">On</option>
<option value="1">Alpine White</option>
<option value="2">Sky Blue</option>
<option value="3">Cobalt Blue</option>
<option value="4">Caribbean Blue</option>
<option value="5">Spring Green</option>
<option value="6">Emerald Green</option>
<option value="7">Emerald Rose</option>
<option value="8">Magenta</option>
<option value="9">Violet</option>
<option value="10">Slow Splash</option>
<option value="11">Fast Splash</option>
<option value="12">USA!!!</option>
<option value="13">Fat Tuesday</option>
<option value="14">Disco Tech</option>
</options>
</state>
</channel-type>
<channel-type id="aux-pentairib">
<item-type>String</item-type>
<label>Pentair intelliBrite Lighting</label>
<description>Pentair intelliBrite Lighting Channel</description>
<state>
<options>
<option value="off">Off</option>
<option value="on">On</option>
<option value="1">SAM</option>
<option value="2">Party</option>
<option value="3">Romance</option>
<option value="4">Caribbean</option>
<option value="5">American</option>
<option value="6">Cal Sunset</option>
<option value="7">Royal</option>
<option value="8">Blue</option>
<option value="9">Green</option>
<option value="10">Red</option>
<option value="11">White</option>
<option value="12">Magenta</option>
<option value="13">Hold</option>
<option value="14">Recall</option>
</options>
</state>
</channel-type>
<channel-type id="aux-hayward">
<item-type>String</item-type>
<label>Hayward Universal Lighting</label>
<description>Hayward Universal Lighting Channel</description>
<state>
<options>
<option value="off">Off</option>
<option value="on">On</option>
<option value="1">Voodoo Lounge</option>
<option value="2">Deep Blue Sea</option>
<option value="3">Afternoon Skies</option>
<option value="4">Emerald</option>
<option value="5">Sangria</option>
<option value="6">Cloud White</option>
<option value="7">Twilight</option>
<option value="8">Tranquility</option>
<option value="9">Gemstone</option>
<option value="10">USA!</option>
<option value="11">Mardi Gras</option>
<option value="12">Cool Caberet</option>
</options>
</state>
</channel-type>
<channel-type id="onetouch">
<item-type>Switch</item-type>
<label>OneTouch</label>
<description>OneTouch commands</description>
</channel-type>
<channel-type id="equipment-switch">
<item-type>Switch</item-type>
<label>Equipment Switch</label>
<description>The current state of a equipment switch</description>
</channel-type>
<channel-type id="equipment-heater">
<item-type>String</item-type>
<label>Heater</label>
<description>The current state of the heater</description>
<state readOnly="true">
<options>
<option value="off">Off</option>
<option value="heating">Heating</option>
<option value="enabled">Enabled</option>
</options>
</state>
</channel-type>
<channel-type id="setpoint">
<item-type>Number:Temperature</item-type>
<label>Setpoint</label>
<category>Temperature</category>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="temperature_scale" advanced="true">
<item-type>String</item-type>
<label>Temperature Units</label>
<description>The selected units for temperature (C or F)</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="chemical">
<item-type>Number</item-type>
<label>Chemical</label>
<category>Other</category>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>