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,32 @@
<?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="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.pentair</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,219 @@
# Pentair Pool
This is an openHAB binding for a Pentair Pool System.
It is based on combined efforts of many on the internet in reverse-engineering the proprietary Pentair protocol (see References section).
The binding was developed and tested on a system with a Pentair EasyTouch controller, but should operate with other Pentair systems.
## Hardware Setup
> REQUISITE DISCLAIMER: CONNECTING 3RD PARTY DEVICES TO THE PENTAIR SYSTEM BUS COULD CAUSE SERIOUS DAMAGE TO THE SYSTEM SHOULD SOMETHING MALFUNCTION. IT IS NOT ENDORSED BY PENTAIR AND COULD VOID WARRENTY. IF YOU DECIDE TO USE THIS BINDING TO INTERFACE TO A PENTAIR CONTROLLER, THE AUTHOR(S) CAN NOT BE HELD RESPONSIBLE.
This binding requires an adapter to interface to the Pentair system bus.
This bus/wire runs between the Pentair control system, indoor control panels, IntelliFlo pumps, etc.
It is a standard RS-485 bus running at 9600,8N1 so any RS-485 adapter should work and you should be able to buy one for under $30.
Pentair does not publish any information on the protocol so this binding was developed using the great reverse-engineering efforts of others made available on the internet.
I have cited sevearl of those in the References section below.
### Connecting adapter to your system
A USB or serial RS-485 interface or IP based interface can be used to interface to the Pentair system bus.
The binding includes 2 different bridge Things depending on which type of interface you use, serial_bridge or ip_bridge.
If your openHAB system is physically located far from your Pentair equipment or indoor control panel, you can use a Raspberry Pi or other computer to redirect USB/serial port traffic over the internet using a program called ser2sock (see Reference section).
An example setup would run the following command: "ser2sock -p 10000 -s /dev/ttyUSB1 -b 9600 -d".
Note: This is the setup utlized for the majority of my testing of this binding.
Note: If you are on a Linux system, the framework may not see a symbolically linked device (i.e. /dev/ttyRS485).
To use a symbolically linked device, add the following line to /etc/default/openhab2, `EXTRA_JAVA_OPTS="-Dgnu.io.rxtx.SerialPorts=/dev/ttyRS485"`
Once you have the interface connected to your system, it is best to test basic connectivity.
Note the protocol is a binary protocol (not ASCII text based) and in order to view the communication packets, one must use a program capable of a binary/HEX mode.
If connected properly, you will see a periodic traffic with packets staring with FF00FFA5.
This is the preamble for Pentairs communication packet.
After you see this traffic, you can proceed to configuring the Pentair binding in openHAB.
#### USB/Serial interface
For a USB/Serial interface, you can use most terminal emulators. For Linux, you can use minicom with the following options: `minicom -H -D /dev/ttyUSB1 -b 9600`
#### IP interface
For an IP based interface (or utilizing ser2sock) on a Linux system, you can use nc command with the following options: `nc localhost 10000 | xxd`
### Pentair Controller panel configuration
In order for the Pentair EasyTouch controller to receive commands from this binding, you may need to enable "Spa-side" remote on the controller itself.
## Supported Things
This binding supports the following thing types:
| Thing | Thing Type | Description |
| --------------- | :--------: | --------------------------------------- |
| ip_bridge | Bridge | A TCP network RS-485 bridge device. |
| serial_bridge | Bridge | A USB or serial RS-485 device. |
| EasyTouch | Thing | Pentiar EasyTouch pool controller. |
| Intelliflo Pump | Thing | Pentair Intelliflo variable speed pump. |
| Intellichlor | Thing | Pentair Intellichlor chlorinator. |
## Binding Configuration
There are no overall binding configurations that need to be set up as all configuration is done at the "Thing" level.
## Thing Configuration
Pentair things can be configured either through the online Paper UI configuration, or manually through a 'pentair.thing' configuration file.
The following table shows the available configuration parameters for each thing.
| Thing | Configuration Parameters |
| ------------- | ------------------------------------------------------------ |
| ip_bridge | address - IP address for the RS-485 adapter - Required. |
| | port - TCP port for the RS-485 adapter - Not Required - default = 10000. |
| | id - ID to use when communicating on Pentair control bus - default = 34. |
| serial_bridge | serialPort - Serial port for the IT-100s bridge - Required. |
| | baud - Baud rate of the IT-100 bridge - Not Required - default = 9600. |
| | pollPeriod - Period of time in minutes between the poll command being sent to the IT-100 bridge - Not Required - default=1. |
| | id - ID to use when communciating on Pentair control bus - default = 34. |
Currently automatic discovery is not supported and the binding requires configuration via the Paper UI or a file in the conf/things folder.
Here is an example of a thing configuration file called 'pentair.things':
```
Bridge pentair:ip_bridge:1 [ address="192.168.1.202", port=10001 ] {
easytouch main [ id=16 ]
intelliflo pump1 [ id=96 ]
intellichlor ic40
}
```
For a serial bridge you would use a configuration similar to this, again saved as 'pentair.things':
```
Bridge pentair:serial_bridge:1 [ serialPort="/dev/ttyUSB0" ] {
easytouch main [ id=16 ]
intelliflo pump1 [ id=96 ]
intellichlor ic40
}
```
## Channels
Pentair things support a variety of channels as seen below in the following table:
| Channel | Item Type | Description |
| -------------------- | --------- | ------------------------------------------------------------ |
| EasyTouch Controller | | |
| pooltemp | Number | Current pool temperature (readonly) |
| spatemp | Number | Current spa temperature (readonly) |
| airtemp | Number | Current air temperature (readonly) |
| solartemp | Number | Current solar temperature (readonly) |
| poolheatmode | Number | Current heat mode setting for pool (readonly): 0=Off, 1=Heater, 2=Solar Preferred, 3=Solar |
| poolheatmodestr | String | Current heat mode setting for pool in string form (readonly) |
| spaheatmode | Number | Current heat mode setting for spa (readonly): 0=Off, 1=Heater, 2=Solar Preferred, 3=Solar |
| spaheatmodestr | String | Current heat mode setting for spa in string form (readonly)> |
| poolsetpoint | Number | Current pool temperature set point |
| spasetpoint | Number | Current spa temperature set point |
| heatactive | Number | Heater mode is active |
| pool | Switch | Primary pool mode |
| spa | Switch | Spa mode |
| aux1 | Switch | Aux1 mode |
| aux2 | Switch | Aux2 mode |
| aux3 | Switch | Aux3 mode |
| aux4 | Switch | Aux4 mode |
| aux5 | Switch | Aux5 mode |
| aux6 | Switch | Aux6 mode |
| aux7 | Switch | Aux7 mode |
| feature1 | Switch | Feature1 mode |
| feature2 | Switch | Feature2 mode |
| feature3 | Switch | Feature3 mode |
| feature4 | Switch | Feature4 mode |
| feature5 | Switch | Feature5 mode |
| feature6 | Switch | Feature6 mode |
| feature7 | Switch | Feature7 mode |
| feature8 | Switch | Feature8 mode |
| IntelliChlor | | |
| saltoutput | Number | Current salt output % (readonly) |
| salinity | Number | Salinity (ppm) (readonly) |
| IntelliFlo Pump | | |
| run | Number | Pump running (readonly) |
| drivestate | Number | Pump drivestate (readonly) |
| mode | Number | Pump mode (readonly) |
| rpm | Number | Pump RPM (readonly) |
| power | Number | Pump power in Watts (readonly) |
| error | Number | Pump error (readonly) |
| ppc | Number | Pump PPC? (readonly) |
## Full Example
The following is an example of an item file (pentair.items), you can change the °F to °C if you are using metric temperature units:
```
Group gPool "Pool"
Number Pool_Temp "Pool Temp [%.1f °F]" <temperature> (gPool) { channel = "pentair:easytouch:1:main:pooltemp" }
Number Spa_Temp "Spa Temp [%.1f °F]" <temperature> (gPool) { channel = "pentair:easytouch:1:main:spatemp" }
Number Air_Temp "Air Temp [%.1f °F]" <temperature> (gPool) { channel = "pentair:easytouch:1:main:airtemp" }
Number Solar_Temp "Solar Temp [%.1f °F]" <temperature> (gPool) { channel = "pentair:easytouch:1:main:solartemp" }
Number PoolHeatMode "Pool Heat Mode [%d]" (gPool) { channel="pentair:easytouch:1:main:poolheatmode" }
String PoolHeatModeStr "Pool Heat Mode [%s]" (gPool) { channel="pentair:easytouch:1:main:poolheatmodestr" }
Number SpaHeatMode "Spa Heat Mode [%d]" (gPool) { channel="pentair:easytouch:1:main:spaheatmode" }
String SpaHeatModeStr "Spa Heat Mode [%s]" (gPool) { channel="pentair:easytouch:1:main:spaheatmodestr" }
Number PoolSetPoint "Pool Set Point [%.1f °F]" <temperature> (gPool) { channel="pentair:easytouch:1:main:poolsetpoint" }
Number SpaSetPoint "Spa Set Point [%.1f °F]" <temperature> (gPool) { channel="pentair:easytouch:1:main:spasetpoint" }
Number HeatActive "Heat Active [%d]" (gPool) { channel="pentair:easytouch:1:main:heatactive" }
Switch Mode_Spa "Spa Mode" (gPool) { channel = "pentair:easytouch:1:main:spa" }
Switch Mode_Pool "Pool Mode" (gPool) { channel = "pentair:easytouch:1:main:pool" }
Switch Mode_PoolLight "Pool Light" (gPool) { channel = "pentair:easytouch:1:main:aux1" }
Switch Mode_SpaLight "Spa Light" (gPool) { channel = "pentair:easytouch:1:main:aux2" }
Switch Mode_Jets "Jets" (gPool) { channel = "pentair:easytouch:1:main:aux3" }
Switch Mode_Boost "Boost Mode" (gPool) { channel = "pentair:easytouch:1:main:aux4" }
Switch Mode_Aux5 "Aux5 Mode" (gPool) { channel = "pentair:easytouch:1:main:aux5" }
Switch Mode_Aux6 "Aux6 Mode" (gPool) { channel = "pentair:easytouch:1:main:aux6" }
Switch Mode_Aux7 "Aux7 Mode" (gPool) { channel = "pentair:easytouch:1:main:aux7" }
Switch Mode_Spillway "Spillway Mode" (gPool) { channel = "pentair:easytouch:1:main:feature1" }
Number SaltOutput "Salt Output [%d%%]" (gPool) { channel = "pentair:intellichlor:1:ic40:saltoutput" }
Number Salinity "Salinity [%d ppm]" (gPool) { channel = "pentair:intellichlor:1:ic40:salinity" }
Switch Pump_Run "Pump running [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:run" }
Number Pump_DriveState "Pump drivestate [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:drivestate" }
Number Pump_Mode "Pump Mode [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:mode" }
Number Pump_RPM "Pump RPM [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:rpm" }
Number Pump_Power "Pump Power [%d W]" (gPool) { channel = "pentair:intelliflo:1:pump1:power" }
Number Pump_Error "Pump Error [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:error" }
Number Pump_PPC "Pump PPC [%d]" (gPool) { channel = "pentair:intelliflo:1:pump1:ppc" }
```
Here is an example of a complete sitemap, saved as `pentair.sitemap`. Adjust the temperature values for metric if so desired.
```
sitemap pool label="Pool stuff" {
Frame label="Pool" {
Switch item=Mode_Pool
Switch item=Mode_PoolLight
Text item=Pool_Temp valuecolor=[>82="red",>77="orange",<=77="blue"]
Setpoint item=PoolSetPoint minValue=85 maxValue=103 step=1.0
Group item=gPool label="Advanced"
}
Frame label="Spa" {
Switch item=Mode_Spa
Switch item=Mode_SpaLight
Switch item=Mode_Jets
Text item=Pool_Temp valuecolor=[>82="red",>77="orange",<=77="blue"]
Setpoint item=SpaSetPoint minValue=85 maxValue=103 step=1.0
}
}
```
## References
Setting up RS485 and basic protocol - https://www.sdyoung.com/home/decoding-the-pentair-easytouch-rs-485-protocol/
ser2sock GitHub - https://github.com/nutechsoftware/ser2sock
## Future Enhancements
- Add automatic discovery of devices on RS-485
- Add in IntelliBrite light color selection (need to capture protocol on system that has this)
- Add direct control of pump (non read-only channels)
- Fix heat active - not working on my system.

View File

@@ -0,0 +1,21 @@
<?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.pentair</artifactId>
<name>openHAB Add-ons :: Bundles :: Pentair Binding</name>
<properties>
<bnd.importpackage>gnu.io;version="[3.12,6)"</bnd.importpackage>
</properties>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.pentair-${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-pentair" description="Pentair Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-serial</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.pentair/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,108 @@
/**
* 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.pentair.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link PentairBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class PentairBindingConstants {
public static final String BINDING_ID = "pentair";
// List of Bridge Types
public static final String IP_BRIDGE = "ip_bridge";
public static final String SERIAL_BRIDGE = "serial_bridge";
// List of all Device Types
public static final String EASYTOUCH = "easytouch";
public static final String INTELLIFLO = "intelliflo";
public static final String INTELLICHLOR = "intellichlor";
// List of all Bridge Thing Type UIDs
public static final ThingTypeUID IP_BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, IP_BRIDGE);
public static final ThingTypeUID SERIAL_BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, SERIAL_BRIDGE);
// List of all Thing Type UIDs
public static final ThingTypeUID INTELLIFLO_THING_TYPE = new ThingTypeUID(BINDING_ID, INTELLIFLO);
public static final ThingTypeUID EASYTOUCH_THING_TYPE = new ThingTypeUID(BINDING_ID, EASYTOUCH);
public static final ThingTypeUID INTELLICHLOR_THING_TYPE = new ThingTypeUID(BINDING_ID, INTELLICHLOR);
// List of all Channel ids
public static final String EASYTOUCH_POOLTEMP = "pooltemp";
public static final String EASYTOUCH_SPATEMP = "spatemp";
public static final String EASYTOUCH_AIRTEMP = "airtemp";
public static final String EASYTOUCH_SOLARTEMP = "solartemp";
public static final String EASYTOUCH_SPAHEATMODE = "spaheatmode";
public static final String EASYTOUCH_SPAHEATMODESTR = "spaheatmodestr";
public static final String EASYTOUCH_POOLHEATMODE = "poolheatmode";
public static final String EASYTOUCH_POOLHEATMODESTR = "poolheatmodestr";
public static final String EASYTOUCH_HEATACTIVE = "heatactive";
public static final String EASYTOUCH_POOLSETPOINT = "poolsetpoint";
public static final String EASYTOUCH_SPASETPOINT = "spasetpoint";
public static final String EASYTOUCH_POOL = "pool";
public static final String EASYTOUCH_SPA = "spa";
public static final String EASYTOUCH_AUX1 = "aux1";
public static final String EASYTOUCH_AUX2 = "aux2";
public static final String EASYTOUCH_AUX3 = "aux3";
public static final String EASYTOUCH_AUX4 = "aux4";
public static final String EASYTOUCH_AUX5 = "aux5";
public static final String EASYTOUCH_AUX6 = "aux6";
public static final String EASYTOUCH_AUX7 = "aux7";
public static final String EASYTOUCH_FEATURE1 = "feature1";
public static final String EASYTOUCH_FEATURE2 = "feature2";
public static final String EASYTOUCH_FEATURE3 = "feature3";
public static final String EASYTOUCH_FEATURE4 = "feature4";
public static final String EASYTOUCH_FEATURE5 = "feature5";
public static final String EASYTOUCH_FEATURE6 = "feature6";
public static final String EASYTOUCH_FEATURE7 = "feature7";
public static final String EASYTOUCH_FEATURE8 = "feature8";
public static final String INTELLICHLOR_SALTOUTPUT = "saltoutput";
public static final String INTELLICHLOR_SALINITY = "salinity";
public static final String INTELLIFLO_RUN = "run";
public static final String INTELLIFLO_MODE = "mode";
public static final String INTELLIFLO_DRIVESTATE = "drivestate";
public static final String INTELLIFLO_POWER = "power";
public static final String INTELLIFLO_RPM = "rpm";
public static final String INTELLIFLO_PPC = "ppc";
public static final String INTELLIFLO_ERROR = "error";
public static final String INTELLIFLO_TIMER = "timer";
public static final String DIAG = "diag";
// Custom Properties
public static final String PROPERTY_ADDRESS = "localhost";
public static final Integer PROPERTY_PORT = 10000;
// Set of all supported Thing Type UIDs
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(IP_BRIDGE_THING_TYPE, SERIAL_BRIDGE_THING_TYPE, EASYTOUCH_THING_TYPE,
INTELLIFLO_THING_TYPE, INTELLICHLOR_THING_TYPE).collect(Collectors.toSet()));
}

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.pentair.internal;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import org.openhab.binding.pentair.internal.handler.PentairEasyTouchHandler;
import org.openhab.binding.pentair.internal.handler.PentairIPBridgeHandler;
import org.openhab.binding.pentair.internal.handler.PentairIntelliChlorHandler;
import org.openhab.binding.pentair.internal.handler.PentairIntelliFloHandler;
import org.openhab.binding.pentair.internal.handler.PentairSerialBridgeHandler;
import org.openhab.core.thing.Bridge;
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.Component;
/**
* The {@link PentairHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Jeff James - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.pentair")
public class PentairHandlerFactory extends BaseThingHandlerFactory {
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(IP_BRIDGE_THING_TYPE)) {
return new PentairIPBridgeHandler((Bridge) thing);
} else if (thingTypeUID.equals(SERIAL_BRIDGE_THING_TYPE)) {
return new PentairSerialBridgeHandler((Bridge) thing);
} else if (thingTypeUID.equals(EASYTOUCH_THING_TYPE)) {
return new PentairEasyTouchHandler(thing);
} else if (thingTypeUID.equals(INTELLIFLO_THING_TYPE)) {
return new PentairIntelliFloHandler(thing);
} else if (thingTypeUID.equals(INTELLICHLOR_THING_TYPE)) {
return new PentairIntelliChlorHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,217 @@
/**
* 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.pentair.internal;
/**
* Generic class for the standard pentair package protocol. Includes helpers to generate checksum and extract key bytes
* from packet.
*
* @author Jeff James - initial contribution
*
*/
public class PentairPacket {
protected static final char[] HEXARRAY = "0123456789ABCDEF".toCharArray();
public static final int OFFSET = 2;
public static final int DEST = 0 + OFFSET;
public static final int SOURCE = 1 + OFFSET;
public static final int ACTION = 2 + OFFSET;
public static final int LENGTH = 3 + OFFSET;
public static final int STARTOFDATA = 4 + OFFSET;
protected boolean initialized;
public byte[] buf;
/**
* Constructor for PentairPacket basic packet.
*/
public PentairPacket() {
buf = new byte[6];
buf[0] = (byte) 0xA5;
}
/**
* Constructor for a PentairPackage with a byte array for the command. Typically used when generating a packet to
* send. Should include all bytes starting with A5. Do not include checksum bytes.
*
* @param buf Array of bytes to be used to populate packet.
*/
public PentairPacket(byte[] buf) {
this.buf = buf;
initialized = true;
}
/**
* Constructor to create a copy of PentairPacket p. Note references the same byte array as original. Used when
* coverting from a generic packet to a specialized packet.
*
* @param p PentairPacket to duplicate in new copy.
*/
public PentairPacket(PentairPacket p) {
this.buf = p.buf;
initialized = true;
}
/**
* Gets length of packet
*
* @return length of packet
*/
public int getLength() {
return buf[LENGTH];
}
/**
* Sets length of packet
*
* @param length length of packet
*/
public void setLength(int length) {
if (length > buf[LENGTH]) {
buf = new byte[length + 6];
}
buf[LENGTH] = (byte) length;
}
/**
* Gets action byte of packet
*
* @return action byte of packet
*/
public int getAction() {
return buf[ACTION];
}
/**
* Sets action byte of packet
*
* @param action
*/
public void setAction(int action) {
buf[ACTION] = (byte) action;
}
/**
* Gets source byte or packet
*
* @return source byte of packet
*/
public int getSource() {
return buf[SOURCE];
}
/**
* Sets source byte of packet
*
* @param source sets source byte of packet
*/
public void setSource(int source) {
buf[SOURCE] = (byte) source;
}
/**
* Gets destination byte of packet
*
* @return destination byte of packet
*/
public int getDest() {
return buf[DEST];
}
/**
* Sets destination byte of packet
*
* @param dest destination byte of packet
*/
public void setDest(int dest) {
buf[DEST] = (byte) dest;
}
/**
* Helper function to convert byte to hex representation
*
* @param b byte to re
* @return 2 charater hex string representing the byte
*/
public static String byteToHex(int b) {
char[] hexChars = new char[2];
hexChars[0] = HEXARRAY[b >>> 4];
hexChars[1] = HEXARRAY[b & 0x0F];
return new String(hexChars);
}
/**
* @param bytes array of bytes to convert to a hex string. Entire buf length is converted.
* @return hex string
*/
public static String bytesToHex(byte[] bytes) {
return bytesToHex(bytes, bytes.length);
}
/**
* @param bytes array of bytes to convert to a hex string.
* @param len Number of bytes to convert
* @return hex string
*/
public static String bytesToHex(byte[] bytes, int len) {
char[] hexChars = new char[len * 3];
for (int j = 0; j < len; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 3] = HEXARRAY[v >>> 4];
hexChars[j * 3 + 1] = HEXARRAY[v & 0x0F];
hexChars[j * 3 + 2] = ' ';
}
return new String(hexChars);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return bytesToHex(buf, getLength() + 6);
}
/**
* Used to extract a specific byte from the packet
*
* @param n number of byte (0 based)
* @return byte of packet
*/
public int getByte(int n) {
return buf[n];
}
/**
* Calculate checksum of the representative packet.
*
* @return checksum of packet
*/
public int calcChecksum() {
int checksum = 0, i;
for (i = 0; i < getLength() + 6; i++) {
checksum += buf[i] & 0xFF;
}
return checksum;
}
}

View File

@@ -0,0 +1,72 @@
/**
* 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.pentair.internal;
/**
* Pentair heat set point packet specialization of a PentairPacket. Includes public variables for many of the reverse
* engineered
* packet content.
*
* @author Jeff James - initial contribution
*
*/
public class PentairPacketHeatSetPoint extends PentairPacket {
protected static final int POOLTEMP = 5 + OFFSET;
protected static final int AIRTEMP = 6 + OFFSET;
protected static final int POOLSETPOINT = 7 + OFFSET;
protected static final int SPASETPOINT = 8 + OFFSET;
protected static final int HEATMODE = 9 + OFFSET;
protected static final int SOLARTEMP = 12 + OFFSET;
protected final String[] heatmodestrs = { "Off", "Heater", "Solar Pref", "Solar" };
/** pool temperature set point */
public int poolsetpoint;
/** pool heat mode - 0=Off, 1=Heater, 2=Solar Pref, 3=Solar */
public int poolheatmode;
/** pool heat mode as a string */
public String poolheatmodestr;
/** spa temperature set point */
public int spasetpoint;
/** spa heat mode - 0=Off, 1=Heater, 2=Solar Pref, 3=Solar */
public int spaheatmode;
/** spa heat mode as a string */
public String spaheatmodestr;
/**
* Constructor to create a specialized packet representing the generic packet. Note, the internal buffer array is
* not
* duplicated. Fills in public class members appropriate with the correct values.
*
* @param p Generic PentairPacket to create specific Status packet
*/
public PentairPacketHeatSetPoint(PentairPacket p) {
super(p);
poolsetpoint = p.buf[POOLSETPOINT];
poolheatmode = p.buf[HEATMODE] & 0x03;
poolheatmodestr = heatmodestrs[poolheatmode];
spasetpoint = p.buf[SPASETPOINT];
spaheatmode = (p.buf[HEATMODE] >> 2) & 0x03;
spaheatmodestr = heatmodestrs[spaheatmode];
}
/**
* Constructure to create an empty status packet
*/
public PentairPacketHeatSetPoint() {
super();
}
}

View File

@@ -0,0 +1,105 @@
/**
* 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.pentair.internal;
/**
* Pentair Intellichlor specialation of a PentairPacket. Includes public variables for many of the reverse engineered
* packet content. Note, Intellichlor packet is of a different format and all helper functions in the base PentairPacket
* may not apply.
*
* This packet can be a 3 or 4 data byte packet.
*
* 10 02 50 00 00 62 10 03
* 10 02 00 01 00 00 13 10 03
*
* @author Jeff James - initial contribution
*
*/
public class PentairPacketIntellichlor extends PentairPacket { // 29 byte packet format
protected static final int CMD = 3; // not sure what this is, needs to be 11 for SALT_OUTPUT or SALINITY to be valid
// 3 Length command
protected static final int SALTOUTPUT = 4;
// 4 Length command
protected static final int SALINITY = 4;
/** length of the packet - 3 or 4 data bytes */
protected int length;
/** for a saltoutput packet, represents the salt output percent */
public int saltoutput;
/** for a salinity packet, is value of salinity. Must be multiplied by 50 to get the actual salinity value. */
public int salinity;
/**
* Constructor for Intellichlor packet. Does not call super constructure since the Intellichlor packet is structure
* so differently
*
* @param buf
* @param length
*/
public PentairPacketIntellichlor(byte[] buf, int length) {
this.buf = buf;
this.length = length;
if (length == 3) {
saltoutput = buf[SALTOUTPUT];
} else if (length == 4) {
salinity = buf[SALINITY] & 0xFF; // make sure it is positive
}
}
/**
* Constructor for empty Intellichlor packet
*/
public PentairPacketIntellichlor() {
super();
}
/*
* (non-Javadoc)
*
* @see org.openhab.binding.pentair.PentairPacket#getLength()
*/
@Override
public int getLength() {
return length;
}
/*
* (non-Javadoc)
*
* @see org.openhab.binding.pentair.PentairPacket#setLength(int)
*/
@Override
public void setLength(int length) {
if (length != this.length) {
buf = new byte[length + 2];
}
this.length = length;
}
/**
* Gets the command byte for this packet
*
* @return command
*/
public int getCmd() {
return buf[CMD];
}
@Override
public String toString() {
return bytesToHex(buf, length + 5);
}
}

View File

@@ -0,0 +1,89 @@
/**
* 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.pentair.internal;
/**
* Pentair pump status packet specialation of a PentairPacket. Includes public variables for many of the reverse
* engineered packet content.
*
* @author Jeff James - initial contribution
*
*/
public class PentairPacketPumpStatus extends PentairPacket { // 15 byte packet format
protected static final int RUN = 4 + OFFSET;
protected static final int MODE = 5 + OFFSET; // Mode in pump status. Means something else in pump write/response?
protected static final int DRIVESTATE = 6 + OFFSET; // ?? Drivestate in pump status. Means something else in pump
// write/response
protected static final int WATTSH = 7 + OFFSET;
protected static final int WATTSL = 8 + OFFSET;
protected static final int RPMH = 9 + OFFSET;
protected static final int RPML = 10 + OFFSET;
protected static final int PPC = 11 + OFFSET; // ??
protected static final int ERR = 13 + OFFSET;
protected static final int TIMER = 14 + OFFSET; // ?? Have to explore
protected static final int HOUR = 17 + OFFSET;
protected static final int MIN = 18 + OFFSET;
/** pump is running */
public boolean run;
/** pump mode (1-4) */
public int mode;
/** pump drivestate - not sure what this specifically represents. */
public int drivestate;
/** pump power - in KW */
public int power;
/** pump rpm */
public int rpm;
/** pump ppc? */
public int ppc;
/** byte in packet indicating an error condition */
public int error;
/** current timer for pump */
public int timer;
/** hour or packet (based on Intelliflo time setting) */
public int hour;
/** minute of packet (based on Intelliflo time setting) */
public int min;
/**
* Constructor to create a specialized packet representing the generic packet. Note, the internal buffer array is
* not
* duplicated. Fills in public class members appropriate with the correct values.
*
* @param p Generic PentairPacket to create specific Status packet
*/
public PentairPacketPumpStatus(PentairPacket p) {
super(p);
run = (buf[RUN] == (byte) 0x0A);
mode = buf[MODE];
drivestate = buf[DRIVESTATE];
power = (buf[WATTSH] << 8) + buf[WATTSL];
rpm = (buf[RPMH] << 8) + buf[RPML];
ppc = buf[PPC];
error = buf[ERR];
timer = buf[TIMER];
hour = buf[HOUR];
min = buf[MIN];
}
/**
* Constructure to create an empty status packet
*/
public PentairPacketPumpStatus() {
super();
}
}

View File

@@ -0,0 +1,121 @@
/**
* 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.pentair.internal;
/**
* Pentair status packet specialation of a PentairPacket. Includes public variables for many of the reverse engineered
* packet content.
*
* @author Jeff James - initial contribution
*
*/
public class PentairPacketStatus extends PentairPacket { // 29 byte packet format
protected static final int HOUR = 4 + OFFSET;
protected static final int MIN = 5 + OFFSET;
protected static final int EQUIP1 = 6 + OFFSET;
protected static final int EQUIP2 = 7 + OFFSET;
protected static final int EQUIP3 = 8 + OFFSET;
protected static final int UOM = 13 + OFFSET; // Celsius (0x04) or Farenheit
protected static final int VALVES = 14 + OFFSET; // Not sure what this actually is? Doesn't seem to be valves
protected static final int UNKNOWN = 17 + OFFSET; // Something to do with heat?
protected static final int POOL_TEMP = 18 + OFFSET;
protected static final int SPA_TEMP = 19 + OFFSET;
protected static final int HEATACTIVE = 20 + OFFSET; // Does not seem to toggle for my system
protected static final int AIR_TEMP = 22 + OFFSET;
protected static final int SOLAR_TEMP = 23 + OFFSET;
protected static final int HEATMODE = 26 + OFFSET;
/** hour byte of packet */
public int hour;
/** minute byte of packet */
public int min;
/** Individual boolean values representing whether a particular ciruit is on or off */
public boolean pool, spa, aux1, aux2, aux3, aux4, aux5, aux6, aux7;
public boolean feature1, feature2, feature3, feature4, feature5, feature6, feature7, feature8;
/** Unit of Measure - Celsius = true, Farenheit = false */
public boolean uom;
/** pool temperature */
public int pooltemp;
/** spa temperature */
public int spatemp;
/** air temperature */
public int airtemp;
/** solar temperature */
public int solartemp;
/** spa heat mode - 0 = Off, 1 = Heater, 2 = Solar Pref, 3 = Solar */
public int spaheatmode;
/** pool heat mode - 0 = Off, 1 = Heater, 2 = Solar Pref, 3 = Solar */
public int poolheatmode;
/** Heat is currently active - note this does not work for my system, but has been documented on the internet */
public int heatactive;
/** used to store packet value for reverse engineering, not used in normal operation */
public int diag;
/**
* Constructor to create a specialized packet representing the generic packet. Note, the internal buffer array is
* not
* duplicated. Fills in public class members appropriate with the correct values.
*
* @param p Generic PentairPacket to create specific Status packet
*/
public PentairPacketStatus(PentairPacket p) {
super(p);
hour = buf[HOUR];
min = buf[MIN];
pool = (buf[EQUIP1] & 0x20) != 0;
spa = (buf[EQUIP1] & 0x01) != 0;
aux1 = (buf[EQUIP1] & 0x02) != 0;
aux2 = (buf[EQUIP1] & 0x04) != 0;
aux3 = (buf[EQUIP1] & 0x08) != 0;
aux4 = (buf[EQUIP1] & 0x10) != 0;
aux5 = (buf[EQUIP1] & 0x40) != 0;
aux6 = (buf[EQUIP1] & 0x80) != 0;
aux7 = (buf[EQUIP2] & 0x01) != 0;
feature1 = (buf[EQUIP2] & 0x04) != 0;
feature2 = (buf[EQUIP2] & 0x08) != 0;
feature3 = (buf[EQUIP2] & 0x10) != 0;
feature4 = (buf[EQUIP2] & 0x20) != 0;
feature5 = (buf[EQUIP2] & 0x40) != 0;
feature6 = (buf[EQUIP2] & 0x80) != 0;
feature7 = (buf[EQUIP3] & 0x01) != 0;
feature8 = (buf[EQUIP3] & 0x02) != 0;
uom = (buf[UOM] & 0x04) != 0;
diag = buf[HEATACTIVE];
pooltemp = buf[POOL_TEMP];
spatemp = buf[SPA_TEMP];
airtemp = buf[AIR_TEMP];
solartemp = buf[SOLAR_TEMP];
spaheatmode = (buf[HEATMODE] >> 2) & 0x03;
poolheatmode = buf[HEATMODE] & 0x03;
heatactive = buf[HEATACTIVE];
}
/**
* Constructure to create an empty status packet
*/
public PentairPacketStatus() {
super();
}
}

View File

@@ -0,0 +1,36 @@
/**
* 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.pentair.internal.config;
import org.apache.commons.lang.builder.ToStringBuilder;
/**
* Configuration parameters for IP Bridge
*
* @author Jeff James - initial contribution
*
*/
public class PentairIPBridgeConfig {
/** IP address of destination */
public String address;
/** Port of destination */
public Integer port;
/** ID to use when sending commands on the Pentair RS485 bus. */
public Integer id;
@Override
public String toString() {
return new ToStringBuilder(this).append("address", address).append("port", port).append("id", id).toString();
}
}

View File

@@ -0,0 +1,33 @@
/**
* 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.pentair.internal.config;
import org.apache.commons.lang.builder.ToStringBuilder;
/**
* Configuration parameters for Serial Bridge
*
* @author Jeff James - initial contribution
*
*/
public class PentairSerialBridgeConfig {
/** path or name of serial port, usually /dev/ttyUSB0 format for linux/mac, COM1 for windows */
public String serialPort;
/** ID to use when sending commands on the Pentair RS485 bus. */
public Integer id;
@Override
public String toString() {
return new ToStringBuilder(this).append("serialPort", serialPort).append("id", id).toString();
}
}

View File

@@ -0,0 +1,453 @@
/**
* 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.pentair.internal.handler;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.INTELLIFLO_THING_TYPE;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.pentair.internal.PentairPacket;
import org.openhab.binding.pentair.internal.PentairPacketIntellichlor;
import org.openhab.core.thing.Bridge;
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.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract class for all common functions for different bridge implementations. Use as superclass for IPBridge and
* SerialBridge implementations.
*
* - Implements parsing of packets on Pentair bus and dispositions to appropriate Thing
* - Periodically sends query to any {@link PentairIntelliFloHandler} things
* - Provides function to write packets
*
* @author Jeff James - Initial contribution
*
*/
public abstract class PentairBaseBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(PentairBaseBridgeHandler.class);
/** input stream - subclass needs to assign in connect function */
protected BufferedInputStream reader;
/** output stream - subclass needs to assing in connect function */
protected BufferedOutputStream writer;
/** thread for parser - subclass needs to create/assign connect */
protected Thread thread;
/** parser object - subclass needs to create/assign during connect */
protected Parser parser;
/** polling job for pump status */
protected ScheduledFuture<?> pollingjob;
/** ID to use when sending commands on Pentair bus - subclass needs to assign based on configuration parameter */
protected int id;
/** array to keep track of IDs seen on the Pentair bus that do not correlate to a configured Thing object */
protected ArrayList<Integer> unregistered = new ArrayList<>();
/**
* Gets pentair bus id
*
* @return id
*/
public int getId() {
return id;
}
private enum ParserState {
WAIT_SOC,
CMD_PENTAIR,
CMD_INTELLICHLOR
}
/**
* Constructor
*
* @param bridge
*/
PentairBaseBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
logger.debug("Bridge received refresh command");
}
}
@Override
public void initialize() {
logger.debug("initializing Pentair Bridge handler.");
connect();
pollingjob = scheduler.scheduleWithFixedDelay(new PumpStatus(), 10, 120, TimeUnit.SECONDS);
}
@Override
public void dispose() {
logger.debug("Handler disposed.");
pollingjob.cancel(true);
disconnect();
}
/**
* Abstract method for creating connection. Must be implemented in subclass.
*/
protected abstract void connect();
/**
* Abstract method for disconnect. Must be implemented in subclass
*/
protected abstract void disconnect();
/**
* Helper function to find a Thing assigned to this bridge with a specific pentair bus id.
*
* @param id Pentiar bus id
* @return Thing object. null if id is not found.
*/
public Thing findThing(int id) {
List<Thing> things = getThing().getThings();
for (Thing t : things) {
PentairBaseThingHandler handler = (PentairBaseThingHandler) t.getHandler();
if (handler != null && handler.getPentairID() == id) {
return t;
}
}
return null;
}
/**
* Class for throwing an End of Buffer exception, used in getByte when read returns a -1. This is used to signal an
* exit from the parser.
*
* @author Jeff James - initial contribution
*
*/
public class EOBException extends Exception {
private static final long serialVersionUID = 1L;
}
/**
* Gets a single byte from reader input stream
*
* @param s used during debug to identify proper state transitioning
* @return next byte from reader
* @throws EOBException
* @throws IOException
*/
private int getByte(ParserState s) throws EOBException, IOException {
int c = 0;
c = reader.read();
if (c == -1) {
// EOBException is thrown if no more bytes in buffer. This exception is used to exit the parser when full
// packet is not in buffer
throw new EOBException();
}
return c;
}
/**
* Gets a specific number of bytes from reader input stream
*
* @param buf byte buffer to store bytes
* @param start starting index to store bytes
* @param n number of bytes to read
* @return number of bytes read
* @throws EOBException
* @throws IOException
*/
private int getBytes(byte[] buf, int start, int n) throws EOBException, IOException {
int i;
int c;
for (i = 0; i < n; i++) {
c = reader.read();
if (c == -1) {
// EOBException is thrown if no more bytes in buffer. This exception is used to exit the parser when
// full packet is not in buffer
throw new EOBException();
}
buf[start + i] = (byte) c;
}
return i;
}
/**
* Job to send pump query status packages to all Intelliflo Pump things in order to see the status.
* Note: From the internet is seems some FW versions of EasyTouch controllers send this automatically and this the
* pump status packets can just be snooped, however my controller version does not do this. No harm in sending.
*
* @author Jeff James
*
*/
class PumpStatus implements Runnable {
@Override
public void run() {
List<Thing> things = getThing().getThings();
// FF 00 FF A5 00 60 10 07 00 01 1C
byte[] packet = { (byte) 0xA5, (byte) 0x00, (byte) 0x00, (byte) id, (byte) 0x07, (byte) 0x00 };
PentairPacket p = new PentairPacket(packet);
for (Thing t : things) {
if (!t.getThingTypeUID().equals(INTELLIFLO_THING_TYPE)) {
continue;
}
p.setDest(((PentairIntelliFloHandler) t.getHandler()).id);
writePacket(p);
try {
Thread.sleep(300); // make sure each pump has time to respond
} catch (InterruptedException e) {
break;
}
}
}
}
/**
* Implements the thread to read and parse the input stream. Once a packet can be indentified, it locates the
* representive sending Thing and dispositions the packet so it can be further processed.
*
* @author Jeff James - initial implementation
*
*/
class Parser implements Runnable {
@Override
public void run() {
logger.debug("parser thread started");
byte buf[] = new byte[40];
int c;
int chksum, i, length;
Thing thing;
PentairBaseThingHandler thinghandler;
ParserState parserstate = ParserState.WAIT_SOC;
try {
while (!Thread.currentThread().isInterrupted()) {
c = getByte(parserstate);
switch (parserstate) {
case WAIT_SOC:
if (c == 0xFF) { // for CMD_PENTAIR, we need at lease one 0xFF
do {
c = getByte(parserstate);
} while (c == 0xFF); // consume all 0xFF
if (c == 0x00) {
parserstate = ParserState.CMD_PENTAIR;
}
}
if (c == 0x10) {
parserstate = ParserState.CMD_INTELLICHLOR;
}
break;
case CMD_PENTAIR:
parserstate = ParserState.WAIT_SOC; // any break will go back to WAIT_SOC
if (c != 0xFF) {
logger.debug("FF00 !FF");
break;
}
if (getBytes(buf, 0, 6) != 6) { // read enough to get the length
logger.debug("Unable to read 6 bytes");
break;
}
if (buf[0] != (byte) 0xA5) {
logger.debug("FF00FF !A5");
break;
}
length = buf[5];
if (length == 0) {
logger.debug("Command length of 0");
}
if (length > 34) {
logger.debug("Received packet longer than 34 bytes: {}", length);
break;
}
if (getBytes(buf, 6, length) != length) { // read remaining packet
break;
}
chksum = 0;
for (i = 0; i < length + 6; i++) {
chksum += buf[i] & 0xFF;
}
c = getByte(parserstate) << 8;
c += getByte(parserstate);
if (c != chksum) {
logger.debug("Checksum error: {}", PentairPacket.bytesToHex(buf, length + 6));
break;
}
PentairPacket p = new PentairPacket(buf);
thing = findThing(p.getSource());
if (thing == null) {
if ((p.getSource() >> 8) == 0x02) { // control panels are 0x3*, don't treat as an
// unregistered device
logger.trace("Command from control panel device ({}): {}", p.getSource(), p);
} else if (!unregistered.contains(p.getSource())) { // if not yet seen, print out log
// message once
logger.info("Command from unregistered device ({}): {}", p.getSource(), p);
unregistered.add(p.getSource());
} else {
logger.trace("Command from unregistered device ({}): {}", p.getSource(), p);
}
break;
}
thinghandler = (PentairBaseThingHandler) thing.getHandler();
if (thinghandler == null) {
logger.debug("Thing handler = null");
break;
}
logger.trace("Received pentair command: {}", p);
thinghandler.processPacketFrom(p);
break;
case CMD_INTELLICHLOR:
parserstate = ParserState.WAIT_SOC;
buf[0] = 0x10; // 0x10 is included in checksum
if (c != (byte) 0x02) {
break;
}
buf[1] = 0x2;
length = 3;
// assume 3 byte command, plus 1 checksum, plus 0x10, 0x03
if (getBytes(buf, 2, 6) != 6) {
break;
}
// Check to see if this is a 3 or 4 byte command
if ((buf[6] != (byte) 0x10 || buf[7] != (byte) 0x03)) {
length = 4;
buf[8] = (byte) getByte(parserstate);
if ((buf[7] != (byte) 0x10) && (buf[8] != (byte) 0x03)) {
logger.debug("Invalid Intellichlor command: {}",
PentairPacket.bytesToHex(buf, length + 6));
break; // invalid command
}
}
chksum = 0;
for (i = 0; i < length + 2; i++) {
chksum += buf[i] & 0xFF;
}
c = buf[length + 2] & 0xFF;
if (c != (chksum & 0xFF)) { // make sure it matches chksum
logger.debug("Invalid Intellichlor checksum: {}",
PentairPacket.bytesToHex(buf, length + 6));
break;
}
PentairPacketIntellichlor pic = new PentairPacketIntellichlor(buf, length);
thing = findThing(0);
if (thing == null) {
if (!unregistered.contains(0)) { // if not yet seen, print out log message
logger.info("Command from unregistered Intelliflow: {}", pic);
unregistered.add(0);
} else {
logger.trace("Command from unregistered Intelliflow: {}", pic);
}
break;
}
thinghandler = (PentairBaseThingHandler) thing.getHandler();
if (thinghandler == null) {
logger.debug("Thing handler = null");
break;
}
thinghandler.processPacketFrom(pic);
break;
}
}
} catch (IOException e) {
logger.trace("I/O error while reading from stream: {}", e.getMessage());
disconnect();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
} catch (EOBException e) {
// EOB Exception is used to exit the parser loop if full message is not in buffer.
}
logger.debug("msg reader thread exited");
}
}
/**
* Method to write a package on the Pentair bus. Will add preamble and checksum to bytes written
*
* @param p {@link PentairPacket} to write
*/
public void writePacket(PentairPacket p) {
try { // FF 00 FF A5 00 60 10 07 00 01 1C
byte[] preamble = { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0xFF };
byte[] buf = new byte[5 + p.getLength() + 8]; // 5 is preamble, 8 is 6 bytes for header and 2 for checksum
p.setSource(id);
System.arraycopy(preamble, 0, buf, 0, 5);
System.arraycopy(p.buf, 0, buf, 5, p.getLength() + 6);
int checksum = p.calcChecksum();
buf[p.getLength() + 11] = (byte) ((checksum >> 8) & 0xFF);
buf[p.getLength() + 12] = (byte) (checksum & 0xFF);
logger.debug("Writing packet: {}", PentairPacket.bytesToHex(buf));
writer.write(buf, 0, 5 + p.getLength() + 8);
writer.flush();
} catch (IOException e) {
logger.trace("I/O error while writing stream", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}

View File

@@ -0,0 +1,48 @@
/**
* 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.pentair.internal.handler;
import org.openhab.binding.pentair.internal.PentairPacket;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.BaseThingHandler;
/**
* Abstract class for all Pentair Things.
*
* @author Jeff James - Initial contribution
*
*/
public abstract class PentairBaseThingHandler extends BaseThingHandler {
/** ID of Thing on Pentair bus */
protected int id;
public PentairBaseThingHandler(Thing thing) {
super(thing);
}
/**
* Gets Pentair bus ID of Thing
*
* @return
*/
public int getPentairID() {
return id;
}
/**
* Abstract function to be implemented by Thing to dispose/parse a received packet
*
* @param p
*/
public abstract void processPacketFrom(PentairPacket p);
}

View File

@@ -0,0 +1,497 @@
/**
* 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.pentair.internal.handler;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import java.math.BigDecimal;
import java.util.List;
import org.openhab.binding.pentair.internal.PentairBindingConstants;
import org.openhab.binding.pentair.internal.PentairPacket;
import org.openhab.binding.pentair.internal.PentairPacketHeatSetPoint;
import org.openhab.binding.pentair.internal.PentairPacketStatus;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairEasyTouchHandler} is responsible for implementation of the EasyTouch Controller. It will handle
* commands sent to a thing and implements the different channels. It also parses/disposes of the packets seen on the
* bus from the controller.
*
* @author Jeff James - Initial contribution
*/
public class PentairEasyTouchHandler extends PentairBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PentairEasyTouchHandler.class);
/**
* current/last status packet recieved, used to compare new packet values to determine if status needs to be updated
*/
protected PentairPacketStatus p29cur = new PentairPacketStatus();
/** current/last heat set point packet, used to determine if status in framework should be updated */
protected PentairPacketHeatSetPoint phspcur = new PentairPacketHeatSetPoint();
public PentairEasyTouchHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.debug("Initializing EasyTouch - Thing ID: {}.", this.getThing().getUID());
id = ((BigDecimal) getConfig().get("id")).intValue();
// make sure there are no exisitng EasyTouch controllers
PentairBaseBridgeHandler bh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
List<Thing> things = bh.getThing().getThings();
for (Thing t : things) {
if (t.getUID().equals(this.getThing().getUID())) {
continue;
}
if (t.getThingTypeUID().equals(EASYTOUCH_THING_TYPE)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Another EasyTouch controller is already configured.");
return;
}
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void dispose() {
logger.debug("Thing {} disposed.", getThing().getUID());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// When channel gets a refresh request, sending a null as the PentairPacket to updateChannel will force an
// updateState, regardless of previous packet value
if (command instanceof RefreshType) {
logger.debug("EasyTouch received refresh command");
updateChannel(channelUID.getId(), null);
return;
}
if (command instanceof OnOffType) {
boolean state = ((OnOffType) command) == OnOffType.ON;
switch (channelUID.getId()) {
case EASYTOUCH_POOL:
circuitSwitch(6, state);
break;
case EASYTOUCH_SPA:
circuitSwitch(1, state);
break;
case EASYTOUCH_AUX1:
circuitSwitch(2, state);
break;
case EASYTOUCH_AUX2:
circuitSwitch(3, state);
break;
case EASYTOUCH_AUX3:
circuitSwitch(4, state);
break;
case EASYTOUCH_AUX4:
circuitSwitch(5, state);
break;
case EASYTOUCH_AUX5:
circuitSwitch(7, state);
break;
case EASYTOUCH_AUX6:
circuitSwitch(8, state);
break;
case EASYTOUCH_AUX7: // A5 01 10 20 86 02 09 01
circuitSwitch(9, state);
break;
case EASYTOUCH_FEATURE1:
circuitSwitch(11, state);
break;
case EASYTOUCH_FEATURE2:
circuitSwitch(12, state);
break;
case EASYTOUCH_FEATURE3:
circuitSwitch(13, state);
break;
case EASYTOUCH_FEATURE4:
circuitSwitch(14, state);
break;
case EASYTOUCH_FEATURE5:
circuitSwitch(15, state);
break;
case EASYTOUCH_FEATURE6:
circuitSwitch(16, state);
break;
case EASYTOUCH_FEATURE7:
circuitSwitch(17, state);
break;
case EASYTOUCH_FEATURE8:
circuitSwitch(18, state);
break;
}
} else if (command instanceof DecimalType) {
int sp = ((DecimalType) command).intValue();
switch (channelUID.getId()) {
case EASYTOUCH_SPASETPOINT:
setPoint(false, sp);
break;
case EASYTOUCH_POOLSETPOINT:
setPoint(true, sp);
break;
}
}
}
/**
* Method to turn on/off a circuit in response to a command from the framework
*
* @param circuit circuit number
* @param state
*/
public void circuitSwitch(int circuit, boolean state) {
byte[] packet = { (byte) 0xA5, (byte) 0x01, (byte) id, (byte) 0x00 /* source */, (byte) 0x86, (byte) 0x02,
(byte) circuit, (byte) ((state) ? 1 : 0) };
PentairPacket p = new PentairPacket(packet);
PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
bbh.writePacket(p);
}
/**
* Method to set heat point for pool (true) of spa (false)
*
* @param Pool pool=true, spa=false
* @param temp
*/
public void setPoint(boolean pool, int temp) {
// [16,34,136,4,POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
// [165, preambleByte, 16, 34, 136, 4, currentHeat.poolSetPoint, parseInt(req.params.temp), updateHeatMode, 0]
int spaset = (!pool) ? temp : phspcur.spasetpoint;
int poolset = (pool) ? temp : phspcur.poolsetpoint;
int heatmode = (phspcur.spaheatmode << 2) | phspcur.poolheatmode;
byte[] packet = { (byte) 0xA5, (byte) 0x01, (byte) id, (byte) 0x00 /* source */, (byte) 0x88, (byte) 0x04,
(byte) poolset, (byte) spaset, (byte) heatmode, (byte) 0 };
logger.info("Set {} temperature: {}", (pool) ? "Pool" : "Spa", temp);
PentairPacket p = new PentairPacket(packet);
PentairBaseBridgeHandler bbh = (PentairBaseBridgeHandler) this.getBridge().getHandler();
bbh.writePacket(p);
}
@Override
public void processPacketFrom(PentairPacket p) {
switch (p.getAction()) {
case 1: // Write command to pump
logger.trace("Write command to pump (unimplemented): {}", p);
break;
case 2:
if (p.getLength() != 29) {
logger.debug("Expected length of 29: {}", p);
return;
}
/*
* Save the previous state of the packet (p29cur) into a temp variable (p29old)
* Update the current state to the new packet we just received.
* Then call updateChannel which will compare the previous state (now p29old) to the new state (p29cur)
* to determine if updateState needs to be called
*/
PentairPacketStatus p29Old = p29cur;
p29cur = new PentairPacketStatus(p);
updateChannel(EASYTOUCH_POOL, p29Old);
updateChannel(EASYTOUCH_POOLTEMP, p29Old);
updateChannel(EASYTOUCH_SPATEMP, p29Old);
updateChannel(EASYTOUCH_AIRTEMP, p29Old);
updateChannel(EASYTOUCH_SOLARTEMP, p29Old);
updateChannel(EASYTOUCH_HEATACTIVE, p29Old);
updateChannel(EASYTOUCH_POOL, p29Old);
updateChannel(EASYTOUCH_SPA, p29Old);
updateChannel(EASYTOUCH_AUX1, p29Old);
updateChannel(EASYTOUCH_AUX2, p29Old);
updateChannel(EASYTOUCH_AUX3, p29Old);
updateChannel(EASYTOUCH_AUX4, p29Old);
updateChannel(EASYTOUCH_AUX5, p29Old);
updateChannel(EASYTOUCH_AUX6, p29Old);
updateChannel(EASYTOUCH_AUX7, p29Old);
updateChannel(EASYTOUCH_FEATURE1, p29Old);
updateChannel(EASYTOUCH_FEATURE2, p29Old);
updateChannel(EASYTOUCH_FEATURE3, p29Old);
updateChannel(EASYTOUCH_FEATURE4, p29Old);
updateChannel(EASYTOUCH_FEATURE5, p29Old);
updateChannel(EASYTOUCH_FEATURE6, p29Old);
updateChannel(EASYTOUCH_FEATURE7, p29Old);
updateChannel(EASYTOUCH_FEATURE8, p29Old);
updateChannel(DIAG, p29Old);
break;
case 4: // Pump control panel on/off
// No action - have not verified these commands, here for documentation purposes and future enhancement
logger.trace("Pump control panel on/of {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA));
break;
case 5: // Set pump mode
// No action - have not verified these commands, here for documentation purposes and future enhancement
logger.trace("Set pump mode {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA));
break;
case 6: // Set run mode
// No action - have not verified these commands, here for documentation purposes and future enhancement
logger.trace("Set run mode {}: {}", p.getDest(), p.getByte(PentairPacket.STARTOFDATA));
break;
case 7:
// No action - have not verified these commands, here for documentation purposes and future enhancement
logger.trace("Pump request status (unseen): {}", p);
break;
case 8: // A5 01 0F 10 08 0D 4B 4B 4D 55 5E 07 00 00 58 00 00 00 
if (p.getLength() != 0x0D) {
logger.debug("Expected length of 13: {}", p);
return;
}
/*
* Save the previous state of the packet (phspcur) into a temp variable (phspOld)
* Update the current state to the new packet we just received.
* Then call updateChannel which will compare the previous state (now phspold) to the new state
* (phspcur) to determine if updateState needs to be called
*/
PentairPacketHeatSetPoint phspOld = phspcur;
phspcur = new PentairPacketHeatSetPoint(p);
updateChannel(EASYTOUCH_POOLSETPOINT, phspOld);
updateChannel(EASYTOUCH_SPASETPOINT, phspOld);
updateChannel(EASYTOUCH_SPAHEATMODE, phspOld);
updateChannel(EASYTOUCH_SPAHEATMODESTR, phspOld);
updateChannel(EASYTOUCH_POOLHEATMODE, phspOld);
updateChannel(EASYTOUCH_POOLHEATMODESTR, phspOld);
logger.debug("Heat set point: {}, {}, {}", p, phspcur.poolsetpoint, phspcur.spasetpoint);
break;
case 10:
logger.debug("Get Custom Names (unseen): {}", p);
break;
case 11:
logger.debug("Get Ciruit Names (unseen): {}", p);
break;
case 17:
logger.debug("Get Schedules (unseen): {}", p);
break;
case 134:
logger.debug("Set Circuit Function On/Off (unseen): {}", p);
break;
default:
logger.debug("Not Implemented: {}", p);
break;
}
}
/**
* Helper function to compare and update channel if needed. The class variables p29_cur and phsp_cur are used to
* determine the appropriate state of the channel.
*
* @param channel name of channel to be updated, corresponds to channel name in {@link PentairBindingConstants}
* @param p Packet representing the former state. If null, no compare is done and state is updated.
*/
public void updateChannel(String channel, PentairPacket p) {
PentairPacketStatus p29 = null;
PentairPacketHeatSetPoint phsp = null;
if (p != null) {
if (p.getLength() == 29) {
p29 = (PentairPacketStatus) p;
} else if (p.getLength() == 13) {
phsp = (PentairPacketHeatSetPoint) p;
}
}
switch (channel) {
case EASYTOUCH_POOL:
if (p29 == null || (p29.pool != p29cur.pool)) {
updateState(channel, (p29cur.pool) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_SPA:
if (p29 == null || (p29.spa != p29cur.spa)) {
updateState(channel, (p29cur.spa) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_AUX1:
if (p29 == null || (p29.aux1 != p29cur.aux1)) {
updateState(channel, (p29cur.aux1) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_AUX2:
if (p29 == null || (p29.aux2 != p29cur.aux2)) {
updateState(channel, (p29cur.aux2) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_AUX3:
if (p29 == null || (p29.aux3 != p29cur.aux3)) {
updateState(channel, (p29cur.aux3) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_AUX4:
if (p29 == null || (p29.aux4 != p29cur.aux4)) {
updateState(channel, (p29cur.aux4) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_AUX5:
if (p29 == null || (p29.aux5 != p29cur.aux5)) {
updateState(channel, (p29cur.aux5) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_AUX6:
if (p29 == null || (p29.aux6 != p29cur.aux6)) {
updateState(channel, (p29cur.aux6) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_AUX7:
if (p29 == null || (p29.aux7 != p29cur.aux7)) {
updateState(channel, (p29cur.aux7) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_FEATURE1:
if (p29 == null || (p29.feature1 != p29cur.feature1)) {
updateState(channel, (p29cur.feature1) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_FEATURE2:
if (p29 == null || (p29.feature2 != p29cur.feature2)) {
updateState(channel, (p29cur.feature2) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_FEATURE3:
if (p29 == null || (p29.feature3 != p29cur.feature3)) {
updateState(channel, (p29cur.feature3) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_FEATURE4:
if (p29 == null || (p29.feature4 != p29cur.feature4)) {
updateState(channel, (p29cur.feature4) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_FEATURE5:
if (p29 == null || (p29.feature5 != p29cur.feature5)) {
updateState(channel, (p29cur.feature5) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_FEATURE6:
if (p29 == null || (p29.feature6 != p29cur.feature6)) {
updateState(channel, (p29cur.feature6) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_FEATURE7:
if (p29 == null || (p29.feature7 != p29cur.feature7)) {
updateState(channel, (p29cur.feature7) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_FEATURE8:
if (p29 == null || (p29.feature8 != p29cur.feature8)) {
updateState(channel, (p29cur.feature8) ? OnOffType.ON : OnOffType.OFF);
}
break;
case EASYTOUCH_POOLTEMP:
if (p29 == null || (p29.pooltemp != p29cur.pooltemp)) {
if (p29cur.pool) {
updateState(channel, new DecimalType(p29cur.pooltemp));
} else {
updateState(channel, UnDefType.UNDEF);
}
}
break;
case EASYTOUCH_SPATEMP:
if (p29 == null || (p29.spatemp != p29cur.spatemp)) {
if (p29cur.spa) {
updateState(channel, new DecimalType(p29cur.spatemp));
} else {
updateState(channel, UnDefType.UNDEF);
}
}
break;
case EASYTOUCH_AIRTEMP:
if (p29 == null || (p29.airtemp != p29cur.airtemp)) {
updateState(channel, new DecimalType(p29cur.airtemp));
}
break;
case EASYTOUCH_SOLARTEMP:
if (p29 == null || (p29.solartemp != p29cur.solartemp)) {
updateState(channel, new DecimalType(p29cur.solartemp));
}
break;
case EASYTOUCH_SPAHEATMODE:
if (phsp == null || (phsp.spaheatmode != phspcur.spaheatmode)) {
updateState(channel, new DecimalType(phspcur.spaheatmode));
}
break;
case EASYTOUCH_SPAHEATMODESTR:
if (phsp == null || (phsp.spaheatmodestr != phspcur.spaheatmodestr)) {
if (phspcur.spaheatmodestr != null) {
updateState(channel, new StringType(phspcur.spaheatmodestr));
}
}
break;
case EASYTOUCH_POOLHEATMODE:
if (phsp == null || (phsp.poolheatmode != phspcur.poolheatmode)) {
updateState(channel, new DecimalType(phspcur.poolheatmode));
}
break;
case EASYTOUCH_POOLHEATMODESTR:
if (phsp == null || (phsp.poolheatmodestr != phspcur.poolheatmodestr)) {
if (phspcur.poolheatmodestr != null) {
updateState(channel, new StringType(phspcur.poolheatmodestr));
}
}
break;
case EASYTOUCH_HEATACTIVE:
if (p29 == null || (p29.heatactive != p29cur.heatactive)) {
updateState(channel, new DecimalType(p29cur.heatactive));
}
break;
case EASYTOUCH_POOLSETPOINT:
if (phsp == null || (phsp.poolsetpoint != phspcur.poolsetpoint)) {
updateState(channel, new DecimalType(phspcur.poolsetpoint));
}
break;
case EASYTOUCH_SPASETPOINT:
if (phsp == null || (phsp.spasetpoint != phspcur.spasetpoint)) {
updateState(channel, new DecimalType(phspcur.spasetpoint));
}
break;
case DIAG:
if (p29 == null || (p29.diag != p29cur.diag)) {
updateState(channel, new DecimalType(p29cur.diag));
}
break;
}
}
}

View File

@@ -0,0 +1,118 @@
/**
* 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.pentair.internal.handler;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import org.openhab.binding.pentair.internal.config.PentairIPBridgeConfig;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler for the IPBridge. Implements the connect and disconnect abstract methods of {@link PentairBaseBridgeHandler}
*
* @author Jeff James - Initial contribution
*
*/
public class PentairIPBridgeHandler extends PentairBaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(PentairIPBridgeHandler.class);
/** Socket object for connection */
protected Socket socket;
public PentairIPBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
protected synchronized void connect() {
PentairIPBridgeConfig configuration = getConfigAs(PentairIPBridgeConfig.class);
id = configuration.id;
try {
socket = new Socket(configuration.address, configuration.port);
reader = new BufferedInputStream(socket.getInputStream());
writer = new BufferedOutputStream(socket.getOutputStream());
logger.info("Pentair IPBridge connected to {}:{}", configuration.address, configuration.port);
} catch (UnknownHostException e) {
String msg = String.format("unknown host name: %s", configuration.address);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
return;
} catch (IOException e) {
String msg = String.format("cannot open connection to %s", configuration.address);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
return;
}
parser = new Parser();
thread = new Thread(parser);
thread.start();
if (socket != null && reader != null && writer != null) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Unable to connect");
}
}
@Override
protected synchronized void disconnect() {
updateStatus(ThingStatus.OFFLINE);
if (thread != null) {
try {
thread.interrupt();
thread.join(); // wait for thread to complete
} catch (InterruptedException e) {
// do nothing
}
thread = null;
parser = null;
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error in closing reader");
}
reader = null;
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error in closing writer");
}
writer = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
logger.error("error when closing socket ", e);
}
socket = null;
}
}
}

View File

@@ -0,0 +1,124 @@
/**
* 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.pentair.internal.handler;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import org.openhab.binding.pentair.internal.PentairBindingConstants;
import org.openhab.binding.pentair.internal.PentairPacket;
import org.openhab.binding.pentair.internal.PentairPacketIntellichlor;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairIntelliChlorHandler} is responsible for implementation of the Intellichlor Salt generator. It will
* process
* Intellichlor commands and set the appropriate channel states. There are currently no commands implemented for this
* Thing to receive from the framework.
*
* @author Jeff James - Initial contribution
*/
public class PentairIntelliChlorHandler extends PentairBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PentairIntelliChlorHandler.class);
protected PentairPacketIntellichlor pic3cur = new PentairPacketIntellichlor();
protected PentairPacketIntellichlor pic4cur = new PentairPacketIntellichlor();
public PentairIntelliChlorHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.debug("Initializing IntelliChlor - Thing ID: {}.", this.getThing().getUID());
id = 0; // Intellichlor doesn't have ID
updateStatus(ThingStatus.ONLINE);
}
@Override
public void dispose() {
logger.debug("Thing {} disposed.", getThing().getUID());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
logger.debug("IntelliChlor received refresh command");
updateChannel(channelUID.getId(), null);
}
}
@Override
public void processPacketFrom(PentairPacket p) {
PentairPacketIntellichlor pic = (PentairPacketIntellichlor) p;
switch (pic.getLength()) {
case 3:
if (pic.getCmd() != 0x11) { // only packets with 0x11 have valid saltoutput numbers.
break;
}
PentairPacketIntellichlor pic3Old = pic3cur;
pic3cur = pic;
updateChannel(INTELLICHLOR_SALTOUTPUT, pic3Old);
break;
case 4:
if (pic.getCmd() != 0x12) {
break;
}
PentairPacketIntellichlor pic4Old = pic4cur;
pic4cur = pic;
updateChannel(INTELLICHLOR_SALINITY, pic4Old);
break;
}
logger.debug("Intellichlor command: {}", pic);
}
/**
* Helper function to compare and update channel if needed. The class variables p29_cur and phsp_cur are used to
* determine the appropriate state of the channel.
*
* @param channel name of channel to be updated, corresponds to channel name in {@link PentairBindingConstants}
* @param p Packet representing the former state. If null, no compare is done and state is updated.
*/
public void updateChannel(String channel, PentairPacket p) {
PentairPacketIntellichlor pic = (PentairPacketIntellichlor) p;
switch (channel) {
case INTELLICHLOR_SALINITY:
if (pic == null || (pic.salinity != pic4cur.salinity)) {
updateState(channel, new DecimalType(pic4cur.salinity));
}
break;
case INTELLICHLOR_SALTOUTPUT:
if (pic == null || (pic.saltoutput != pic3cur.saltoutput)) {
updateState(channel, new DecimalType(pic3cur.saltoutput));
}
break;
}
}
}

View File

@@ -0,0 +1,189 @@
/**
* 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.pentair.internal.handler;
import static org.openhab.binding.pentair.internal.PentairBindingConstants.*;
import java.math.BigDecimal;
import org.openhab.binding.pentair.internal.PentairBindingConstants;
import org.openhab.binding.pentair.internal.PentairPacket;
import org.openhab.binding.pentair.internal.PentairPacketPumpStatus;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PentairIntelliFloHandler} is responsible for implementation of the Intelliflo Pump. This will
* parse/dispose of
* status packets to set the stat for various channels.
*
* @author Jeff James - Initial contribution
*/
public class PentairIntelliFloHandler extends PentairBaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(PentairIntelliFloHandler.class);
protected PentairPacketPumpStatus ppscur = new PentairPacketPumpStatus();
public PentairIntelliFloHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.debug("Initializing Intelliflo - Thing ID: {}.", this.getThing().getUID());
id = ((BigDecimal) getConfig().get("id")).intValue();
updateStatus(ThingStatus.ONLINE);
}
@Override
public void dispose() {
logger.debug("Thing {} disposed.", getThing().getUID());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
logger.debug("Intellflo received refresh command");
updateChannel(channelUID.getId(), null);
}
}
@Override
public void processPacketFrom(PentairPacket p) {
switch (p.getAction()) {
case 1: // Pump command - A5 00 10 60 01 02 00 20
logger.trace("Pump command (ack): {}: ", p);
break;
case 4: // Pump control panel on/off
logger.trace("Turn pump control panel (ack) {}: {} - {}", p.getSource(),
p.getByte(PentairPacket.STARTOFDATA), p);
break;
case 5: // Set pump mode
logger.trace("Set pump mode (ack) {}: {} - {}", p.getSource(), p.getByte(PentairPacket.STARTOFDATA), p);
break;
case 6: // Set run mode
logger.trace("Set run mode (ack) {}: {} - {}", p.getSource(), p.getByte(PentairPacket.STARTOFDATA), p);
break;
case 7: // Pump status (after a request)
if (p.getLength() != 15) {
logger.debug("Expected length of 15: {}", p);
return;
}
/*
* P: A500 d=10 s=60 c=07 l=0f 0A0602024A08AC120000000A000F22 <028A>
* RUN 0a Started
* MOD 06 Feature 1
* PMP 02 ? drive state
* PWR 024a 586 WATT
* RPM 08ac 2220 RPM
* GPM 12 18 GPM
* PPC 00 0 %
* b09 00 ?
* ERR 00 ok
* b11 0a ?
* TMR 00 0 MIN
* CLK 0f22 15:34
*/
logger.debug("Pump status: {}", p);
/*
* Save the previous state of the packet (p29cur) into a temp variable (p29old)
* Update the current state to the new packet we just received.
* Then call updateChannel which will compare the previous state (now p29old) to the new state (p29cur)
* to determine if updateState needs to be called
*/
PentairPacketPumpStatus ppsOld = ppscur;
ppscur = new PentairPacketPumpStatus(p);
updateChannel(INTELLIFLO_RUN, ppsOld);
updateChannel(INTELLIFLO_MODE, ppsOld);
updateChannel(INTELLIFLO_DRIVESTATE, ppsOld);
updateChannel(INTELLIFLO_POWER, ppsOld);
updateChannel(INTELLIFLO_RPM, ppsOld);
updateChannel(INTELLIFLO_PPC, ppsOld);
updateChannel(INTELLIFLO_ERROR, ppsOld);
updateChannel(INTELLIFLO_TIMER, ppsOld);
break;
default:
logger.debug("Unhandled Intelliflo command: {}", p.toString());
break;
}
}
/**
* Helper function to compare and update channel if needed. The class variables p29_cur and phsp_cur are used to
* determine the appropriate state of the channel.
*
* @param channel name of channel to be updated, corresponds to channel name in {@link PentairBindingConstants}
* @param p Packet representing the former state. If null, no compare is done and state is updated.
*/
public void updateChannel(String channel, PentairPacket p) {
// Only called from this class's processPacketFrom, so we are confident this will be a PentairPacketPumpStatus
PentairPacketPumpStatus pps = (PentairPacketPumpStatus) p;
switch (channel) {
case INTELLIFLO_RUN:
if (pps == null || (pps.run != ppscur.run)) {
updateState(channel, (ppscur.run) ? OnOffType.ON : OnOffType.OFF);
}
break;
case INTELLIFLO_MODE:
if (pps == null || (pps.mode != ppscur.mode)) {
updateState(channel, new DecimalType(ppscur.mode));
}
break;
case INTELLIFLO_DRIVESTATE:
if (pps == null || (pps.drivestate != ppscur.drivestate)) {
updateState(channel, new DecimalType(ppscur.drivestate));
}
break;
case INTELLIFLO_POWER:
if (pps == null || (pps.power != ppscur.power)) {
updateState(channel, new DecimalType(ppscur.power));
}
break;
case INTELLIFLO_RPM:
if (pps == null || (pps.rpm != ppscur.rpm)) {
updateState(channel, new DecimalType(ppscur.rpm));
}
break;
case INTELLIFLO_PPC:
if (pps == null || (pps.ppc != ppscur.ppc)) {
updateState(channel, new DecimalType(ppscur.ppc));
}
break;
case INTELLIFLO_ERROR:
if (pps == null || (pps.error != ppscur.error)) {
updateState(channel, new DecimalType(ppscur.error));
}
break;
case INTELLIFLO_TIMER:
if (pps == null || (pps.timer != ppscur.timer)) {
updateState(channel, new DecimalType(ppscur.timer));
}
break;
}
}
}

View File

@@ -0,0 +1,144 @@
/**
* 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.pentair.internal.handler;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import org.openhab.binding.pentair.internal.config.PentairSerialBridgeConfig;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
/**
* Handler for the IPBridge. Implements the connect and disconnect abstract methods of {@link PentairBaseBridgeHandler}
*
* @author Jeff James - initial contribution
*
*/
public class PentairSerialBridgeHandler extends PentairBaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(PentairSerialBridgeHandler.class);
/** SerialPort object representing the port where the RS485 adapter is connected */
SerialPort port;
public PentairSerialBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
protected synchronized void connect() {
PentairSerialBridgeConfig configuration = getConfigAs(PentairSerialBridgeConfig.class);
try {
CommPortIdentifier ci = CommPortIdentifier.getPortIdentifier(configuration.serialPort);
CommPort cp = ci.open("openhabpentairbridge", 10000);
if (cp == null) {
throw new IllegalStateException("cannot open serial port!");
}
if (cp instanceof SerialPort) {
port = (SerialPort) cp;
} else {
throw new IllegalStateException("unknown port type");
}
port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
port.disableReceiveFraming();
port.disableReceiveThreshold();
reader = new BufferedInputStream(port.getInputStream());
writer = new BufferedOutputStream(port.getOutputStream());
logger.info("Pentair Bridge connected to serial port: {}", configuration.serialPort);
} catch (PortInUseException e) {
String msg = String.format("cannot open serial port: %s", configuration.serialPort);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
return;
} catch (UnsupportedCommOperationException e) {
String msg = String.format("got unsupported operation %s on port %s", e.getMessage(),
configuration.serialPort);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
return;
} catch (NoSuchPortException e) {
String msg = String.format("got no such port for %s", configuration.serialPort);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
return;
} catch (IllegalStateException e) {
String msg = String.format("receive IllegalStateException for port %s", configuration.serialPort);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
return;
} catch (IOException e) {
String msg = String.format("IOException on port %s", configuration.serialPort);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
return;
}
parser = new Parser();
thread = new Thread(parser);
thread.start();
if (port != null && reader != null && writer != null) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Unable to connect");
}
}
@Override
protected synchronized void disconnect() {
updateStatus(ThingStatus.OFFLINE);
if (thread != null) {
try {
thread.interrupt();
thread.join(); // wait for thread to complete
} catch (InterruptedException e) {
// do nothing
}
thread = null;
parser = null;
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.trace("IOException when closing serial reader", e);
}
reader = null;
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
logger.trace("IOException when closing serial writer", e);
}
writer = null;
}
if (port != null) {
port.close();
port = null;
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="pentair" 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>Pentair Binding</name>
<description>This is the binding for Pentair pool systems.</description>
<author>Jeff James</author>
</binding:binding>

View File

@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="pentair"
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="easytouch">
<supported-bridge-type-refs>
<bridge-type-ref id="ip_bridge"/>
<bridge-type-ref id="pentair_serial_bridge"/>
</supported-bridge-type-refs>
<label>EasyTouch Controller</label>
<description>Pentair EasyTouch Controller</description>
<channels>
<channel id="pooltemp" typeId="pooltemp"/>
<channel id="spatemp" typeId="spatemp"/>
<channel id="airtemp" typeId="airtemp"/>
<channel id="solartemp" typeId="solartemp"/>
<channel id="spaheatmode" typeId="heatmode"/>
<channel id="poolheatmode" typeId="heatmode"/>
<channel id="spaheatmodestr" typeId="heatmodestr"/>
<channel id="poolheatmodestr" typeId="heatmodestr"/>
<channel id="heatactive" typeId="heatactive"/>
<channel id="poolsetpoint" typeId="poolsetpoint"/>
<channel id="spasetpoint" typeId="spasetpoint"/>
<channel id="pool" typeId="auxswitch"/>
<channel id="spa" typeId="auxswitch"/>
<channel id="aux1" typeId="auxswitch"/>
<channel id="aux2" typeId="auxswitch"/>
<channel id="aux3" typeId="auxswitch"/>
<channel id="aux4" typeId="auxswitch"/>
<channel id="aux5" typeId="auxswitch"/>
<channel id="aux6" typeId="auxswitch"/>
<channel id="aux7" typeId="auxswitch"/>
<channel id="aux8" typeId="auxswitch"/>
<channel id="feature1" typeId="featureswitch"/>
<channel id="feature2" typeId="featureswitch"/>
<channel id="feature3" typeId="featureswitch"/>
<channel id="feature4" typeId="featureswitch"/>
<channel id="feature5" typeId="featureswitch"/>
<channel id="feature6" typeId="featureswitch"/>
<channel id="feature7" typeId="featureswitch"/>
<channel id="feature8" typeId="featureswitch"/>
</channels>
<config-description>
<parameter name="id" type="integer" required="false">
<label>ID</label>
<description>The ID of the device (in decimal, not hex)</description>
<default>16</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="pooltemp">
<item-type>Number</item-type>
<label>Pool Water Temperature</label>
<description>Pool water temperature. Only valid when pool pump is running and in pool mode.</description>
</channel-type>
<channel-type id="spatemp">
<item-type>Number</item-type>
<label>Spa Water Temperature</label>
<description>Spa water temperature. Only valide when in spa mode.</description>
</channel-type>
<channel-type id="airtemp">
<item-type>Number</item-type>
<label>Air Temperature</label>
<description>Air temperature.</description>
</channel-type>
<channel-type id="solartemp" advanced="true">
<item-type>Number</item-type>
<label>Solar Temperature</label>
<description>Solar temperature.</description>
</channel-type>
<channel-type id="auxswitch" advanced="true">
<item-type>Switch</item-type>
<label>Auxillary Switch</label>
<description>Auxillary Switch</description>
</channel-type>
<channel-type id="featureswitch" advanced="true">
<item-type>Switch</item-type>
<label>Feature Switch</label>
<description>Feature Switch</description>
</channel-type>
<channel-type id="heatmode">
<item-type>Number</item-type>
<label>Heat Mode</label>
<description>Heat mode</description>
<state readOnly="true" pattern="%s">
<options>
<option value="0">None</option>
<option value="1">Heater</option>
<option value="2">Solar Preferred</option>
<option value="3">Solar</option>
</options>
</state>
</channel-type>
<channel-type id="heatactive">
<item-type>Number</item-type>
<label>Heat Active</label>
<description>Heat active state</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="poolsetpoint">
<item-type>Number</item-type>
<label>Pool Temperature Set Point</label>
<description>Pool temperature set point</description>
</channel-type>
<channel-type id="spasetpoint">
<item-type>Number</item-type>
<label>Spa Temperature Set Point</label>
<description>Spa temperature set point</description>
</channel-type>
<channel-type id="heatmodestr">
<item-type>String</item-type>
<label>Heat Mode Text</label>
<description>Heat mode string</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="pentair"
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="intellichlor">
<supported-bridge-type-refs>
<bridge-type-ref id="ip_bridge"/>
<bridge-type-ref id="pentair_serial_bridge"/>
</supported-bridge-type-refs>
<label>Intellichlor IC40</label>
<description>Pentair Intellichlor IC40</description>
<channels>
<channel id="saltoutput" typeId="saltoutput"/>
<channel id="salinity" typeId="salinity"/>
</channels>
<config-description>
<parameter name="id" type="integer" required="false">
<label>ID</label>
<description>The ID of the device (in decimal, not hex)</description>
<default>96</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="saltoutput">
<item-type>Number</item-type>
<label>Salt Output (%)</label>
<description>Current salt output setting for the chlorinator (%).</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="salinity">
<item-type>Number</item-type>
<label>Salinity (PPM)</label>
<description>Current salt content reading of the water (PPM).</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="pentair"
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="intelliflo">
<supported-bridge-type-refs>
<bridge-type-ref id="ip_bridge"/>
<bridge-type-ref id="pentair_serial_bridge"/>
</supported-bridge-type-refs>
<label>Intelliflo Pump</label>
<description>Pentair Intelliflo Pump</description>
<channels>
<channel id="run" typeId="runswitch"/>
<channel id="mode" typeId="pumpmode"/>
<channel id="rpm" typeId="rpm"/>
<channel id="power" typeId="power"/>
<channel id="ppc" typeId="ppc"/>
<channel id="error" typeId="pumperror"/>
</channels>
<config-description>
<parameter name="id" type="integer" required="false">
<label>ID</label>
<description>The ID of the device (in decimal, not hex)</description>
<default>96</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="pumpmode">
<item-type>Number</item-type>
<label>Pump Mode</label>
<description>Pump mode</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="runswitch">
<item-type>Switch</item-type>
<label>Pump Running</label>
<description>Indicator on whether the pump is running or not.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="rpm">
<item-type>Number</item-type>
<label>RPM</label>
<description>Pump RPM</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="power">
<item-type>Number</item-type>
<label>Power</label>
<description>Pump power</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="ppc">
<item-type>Number</item-type>
<label>PPC</label>
<description>Pump PPC</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="pumperror">
<item-type>Number</item-type>
<label>Pump Error</label>
<description>Pump Error</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="pentair"
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">
<bridge-type id="ip_bridge">
<label>IP Bridge</label>
<description>This bridge is for use over a network interface.</description>
<config-description>
<parameter name="address" type="text" required="true">
<label>IP Address</label>
<description>The IP address to connect to.</description>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer" required="false">
<label>Port</label>
<description>The port to connect to.</description>
<default>10000</default>
</parameter>
<parameter name="id" type="integer" required="false">
<label>Pentair ID</label>
<description>The ID to use to send commands on the Pentair bus (default: 34)</description>
<default>34</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="pentair"
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">
<bridge-type id="pentair_serial_bridge">
<label>Serial Bridge</label>
<description>This bridge is used when using a USB->RS485 interface.</description>
<config-description>
<parameter name="serialPort" type="text" required="true">
<label>Serial Port</label>
<description>The serial port name. Valid values are e.g. COM1 for Windows and /dev/ttyS0 or /dev/ttyUSB0 for Linux.</description>
</parameter>
<parameter name="id" type="integer" required="false">
<label>Pentair ID</label>
<description>The ID to use to send commands on the Pentair bus (default: 34)</description>
<default>34</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>