added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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()));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user