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,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>